I'm working in ServiceStack and using FluentValidation to handle incoming DTOs on requests. I've broken these out as follows, but my unit tests don't seem to be able to target specific rule sets. My code is as follows:
DTO / REQUEST CLASSES
public class VendorDto
{
public int Id { get; set; }
public string Name { get; set; }
}
public class DvrDto
{
public int Id { get; set; }
public string Name { get; set; }
public VendorDto Vendor { get; set; }
}
public class DvrRequest : IReturn<DvrResponse>
{
public DvrDto Dvr { get; set; }
}
VALIDATOR CLASSES
public class VendorValidator : AbstractValidator<VendorDto>
{
public VendorValidator()
{
RuleFor(v => v.Name).NotEmpty();
}
}
public class DvrValidator : AbstractValidator<DvrDto>
{
public DvrValidator()
{
RuleFor(dvr => dvr.Name).NotEmpty();
RuleFor(dvr => dvr.Vendor).NotNull().SetValidator(new VendorValidator());
}
}
public class DvrRequestValidator : AbstractValidator<DvrRequest>
{
public DvrRequestValidator()
{
RuleSet(HttpMethods.Post, () =>
{
RuleFor(req => req.Dvr).SetValidator(new DvrValidator());
});
RuleSet(HttpMethods.Patch, () =>
{
RuleFor(req => req.Dvr).SetValidator(new DvrValidator());
RuleFor(req => req.Dvr.Id).GreaterThan(0);
});
}
}
UNIT TEST
[TestMethod]
public void FailWithNullDtoInRequest()
{
// Arrange
var dto = new DvrRequest();
var validator = new DvrRequestValidator();
// Act
var result = validator.Validate(msg, ruleSet: HttpMethods.Post);
// Assert
Assert.IsTrue(result.IsValid);
}
I would prefer to be able to control what gets called depending on what the HttpMethod is that's being called. My thought here was, I want to validate all fields on the DvrDto (and child VendorDto) for both POST and PATCH, but only require a valid Id be set on PATCH. I am setting up my DvrRequestValidator to handle this. However, my unit test as written above (targeting the RuleSet for the POST verb) always finds the request to be valid, even though the validator should fail the request.
In fiddling with it, if I make the following changes:
VALIDATOR
public class DvrRequestValidator : AbstractValidator<DvrRequest>
{
public DvrRequestValidator()
{
RuleFor(req => req.Dvr).SetValidator(new DvrValidator());
RuleSet(HttpMethods.Patch, () =>
{
RuleFor(req => req.Dvr.Id).GreaterThan(0);
});
}
}
TEST CALL (removing the targeted verb)
// Act
var result = validator.Validate(msg); // , ruleSet: HttpMethods.Post);
The validator then works as I expect for a POST, but the PATCH rule set doesn't get executed. As a result, I seem to lose the granularity of what I want validated on a particular verb. It would appear to me that this is supported in examples I've seen both on StackOverflow and in the FluentValidation docs. Am I doing something wrong here? Or is this not possible?
The Validators are registered in ServiceStack's Global Request Filters so you'd typically use an Integration Test with a Service Client to test validation errors.
If you want to test the validator independently in a Unit Test you can execute a HTTP Method Result set with something like:
var req = new BasicRequest(requestDto);
var validationResult = validator.Validate(new ValidationContext(requestDto, null,
new MultiRuleSetValidatorSelector(HttpMethods.Patch)) {
Request = req
});
Unit Testing ServiceStack Features
Although note a lot of ServiceStack functionality assumes there's an AppHost is available, but in most cases you can just use an In Memory AppHost, e.g:
[Test]
public void My_unit_test()
{
using (new BasicAppHost().Init())
{
//test ServiceStack classes
}
}
Of if you prefer you can set it up once per test fixture with something like:
public class MyUnitTests
{
ServiceStackHost appHost;
public MyUnitTests() => appHost = new BasicAppHost().Init();
[OneTimeTearDown]
public void OneTimeTearDown() => appHost.Dispose();
[Test]
public void My_unit_test()
{
//test ServiceStack classes
}
}
Related
I'm developing an Azure Mobile App service to interface to my Xamarin application.
I've created, connected and successfully populated an SQL Database, but when I try to add some filters to my request, for example an orderby() or where() clauses, it returns me a Bad Request error.
For example, this request: https://myapp.azurewebsites.net/tables/Race?$orderby=iRound%20desc,iYear%20desc&$top=1&ZUMO-API-VERSION=2.0.0 gives me {"message":"The query specified in the URI is not valid. Could not find a property named 'IYear' on type 'MyType'."}.
My configuration method is this:
HttpConfiguration config = new HttpConfiguration();
new MobileAppConfiguration()
.AddTablesWithEntityFramework()
.ApplyTo(config);
config.MapHttpAttributeRoutes();
Database.SetInitializer(new CreateDatabaseIfNotExists<MainDataContext>());
app.UseWebApi(config);
and my DbContext is this:
public class MainDataContext : DbContext
{
private const string connectionStringName = "Name=MS_TableConnectionString";
public MainDataContext() : base(connectionStringName)
{
Database.Log = s => WriteLog(s);
}
public void WriteLog(string msg)
{
System.Diagnostics.Debug.WriteLine(msg);
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Add(
new AttributeToColumnAnnotationConvention<TableColumnAttribute, string>(
"ServiceTableColumn", (property, attributes) => attributes.Single().ColumnType.ToString()));
}
public DbSet<Race> Race { get; set; }
public DbSet ...ecc...
}
Following this guide, I added a migration after creating my TableControllers. So the TableController for the example type shown above is pretty standard:
[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.All)]
public class RaceController : TableController<Race>
{
protected override void Initialize(HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
MainDataContext context = new MainDataContext();
DomainManager = new EntityDomainManager<Race>(context, Request);
}
// GET tables/Race
[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.All)]
public IQueryable<Race> GetAllRace()
{
return Query();
}
// GET tables/Race/48D68C86-6EA6-4C25-AA33-223FC9A27959
public SingleResult<Race> GetRace(string id)
{
return Lookup(id);
}
// PATCH tables/Race/48D68C86-6EA6-4C25-AA33-223FC9A27959
public Task<Race> PatchRace(string id, Delta<Race> patch)
{
return UpdateAsync(id, patch);
}
// POST tables/Race
public async Task<IHttpActionResult> PostRace(Race item)
{
Race current = await InsertAsync(item);
return CreatedAtRoute("Tables", new { id = current.Id }, current);
}
// DELETE tables/Race/48D68C86-6EA6-4C25-AA33-223FC9A27959
public Task DeleteRace(string id)
{
return DeleteAsync(id);
}
}
As you can see, I already tried to add the EnableQuery attribute to my TableController, as seen on Google. I also tried to add these filters to the HttpConfiguration object, without any success:
config.Filters.Add(new EnableQueryAttribute
{
PageSize = 10,
AllowedArithmeticOperators = AllowedArithmeticOperators.All,
AllowedFunctions = AllowedFunctions.All,
AllowedLogicalOperators = AllowedLogicalOperators.All,
AllowedQueryOptions = AllowedQueryOptions.All
});
config.AddODataQueryFilter(new EnableQueryAttribute
{
PageSize = 10,
AllowedArithmeticOperators = AllowedArithmeticOperators.All,
AllowedFunctions = AllowedFunctions.All,
AllowedLogicalOperators = AllowedLogicalOperators.All,
AllowedQueryOptions = AllowedQueryOptions.All
});
I don't know what to investigate more, as things seems to be changing too fast for a newbie like me who's first got into Azure.
EDIT
I forgot to say that asking for the complete table, so for example https://myapp.azurewebsites.net/tables/Race?ZUMO-API-VERSION=2.0.0, returns correctly the entire dataset. The problem occurs only when adding some clauses to the request.
EDIT 2
My model is like this:
public class Race : EntityData
{
public int iRaceId { get; set; }
public int iYear { get; set; }
public int iRound { get; set; }
ecc..
}
and the database table that was automatically created is this, including all the properties inherited from EntityData:
Database table schema
Digging into the source code, Azure Mobile Apps sets up camelCase encoding of all requests and responses. It then puts them back after transmission accordign to rules - so iRaceId becomes IRaceId on the server.
The easiest solution to this is to bypass the auto-naming and use a JsonProperty attribute on each property within your server-side DTO and client-side DTO so that they match and will get encoding/decoded according to your rules.
So:
public class Race : EntityData
{
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("raceId")]
public int iRaceId { get; set; }
[JsonProperty("year")]
public int iYear { get; set; }
[JsonProperty("round")]
public int iRound { get; set; }
etc..
}
Although I am able to access the SchemaVersion using code below, I cannot access FormatDocID nested element.
Any ideas how can I easily get FormatDocID using ServiceStack and AutoQueryFeature (or similar)?
I put only relevant parts of code here
public override void Configure(Container container)
{
JsConfig.DateHandler = DateHandler.ISO8601;
SetupValidators(container);
SetupIOC(container);
SetupPlugins(container, log);
ContentTypes.Register("application/xml"
, CLXmlSerializer.Serialize, ServiceStack.Text.XmlSerializer.DeserializeFromStream);
SetupMetaDataRedirectionPath();
SetupGlobalResponseFilters();
}
Setup plugins
private void SetupPlugins(Container container)
{
Plugins.Add(new ValidationFeature());
Plugins.Add(new SwaggerFeature());
Plugins.Add(new AutoQueryFeature
{
MaxLimit = 1000,
EnableUntypedQueries = false,
IncludeTotal = true
});
Plugins.Add(new AutoQueryDataFeature {MaxLimit = 100}
.AddDataSource(ctx => ctx.MemorySource(new List<WordDocument>
{
new WordDocument()
{
SchemaVersion = "",
Format = new Word.DocumentFormat()
{
FormatDocID = 254
}
}
}))
);
typeof(RequestLogs).AddAttributes(new RestrictAttribute {VisibilityTo = RequestAttributes.None});
typeof(AssignRoles).AddAttributes(new RestrictAttribute {VisibilityTo = RequestAttributes.None});
typeof(UnAssignRoles).AddAttributes(new RestrictAttribute {VisibilityTo = RequestAttributes.None});
typeof(Authenticate).AddAttributes(new RestrictAttribute {VisibilityTo = RequestAttributes.None});
}
Serializable classes
public abstract class Document
{
public DocumentFormat Format;
public class DocumentFormat
{
[XmlAttribute] public int Version;
public int FormatDocID;
public string DocShortName;
}
}
public class WordDocument : Document
{
[XmlAttribute] public string SchemaVersion { get; set; } = "1.0";
}
Thanks in advance for the answers.
It's not clear what you're trying to achieve or why, AutoQuery creates Auto Queryable APIs where the Response is the API Response serialized in the specified Response Content Type.
If you want to intercept the Typed Response DTO before it's returned you can create a Custom AutoQuery Implementation and introspect the response that way, e.g:
public class MyQueryServices : Service
{
public IAutoQueryData AutoQuery { get; set; }
//Override with custom implementation
public object Any(MyQuery query)
{
var q = AutoQuery.CreateQuery(query, base.Request);
var response = AutoQuery.Execute(query, q);
return response;
}
}
But the AutoQuery Memory Data Source you're using lets you provide your own collection of Typed POCOs as the Data source so you already have access to them when you create it, but the source POCOs should be a flat Type with public properties (in contrast to your class with public fields and nested types) - it's not possible to query nested object graph values.
This is an example of a POCO that doesn't use nested classes, or public fields:
public abstract class Document
{
public int Version { get; set; }
public int FormatDocID { get; set; }
public string DocShortName { get; set; }
}
So the solution if you want to use AutoQuery would be to change your Data Source to use Flat POCOs with public properties otherwise you'd need to create the impl of your Service yourself.
I have a validator and I'm trying to use some session variables as part of the validation logic, however the base.Request is always coming back as NULL. I've added it in the lambda function as directed and also the documentation for Validation seems to be out of date as the tip in the Fluent validation for request dtos section mentions to use IRequiresHttpRequest, but the AbstractValidator class already implements IRequiresRequest.
This is my code:
public class UpdateContact : IReturn<UpdateContactResponse>
{
public Guid Id { get; set; }
public string Reference { get; set; }
public string Notes { get; set; }
public List<Accounts> Accounts { get; set; }
}
public class UpdateContactResponse : ResponseBase
{
public Guid ContactId { get; set; }
}
public class UpdateContactValidator : AbstractValidator<UpdateContact>
{
public UpdateContactValidator(IValidator<AccountDetail> accountDetailValidator)
{
RuleSet(ApplyTo.Post | ApplyTo.Put, () => {
var session = base.Request.GetSession() as CustomAuthSession;
RuleFor(c => c.Reference).Must(x => !string.IsNullOrEmpty(x) && session.Region.GetCountry() == RegionCodes.AU);
});
RuleFor(R => R.Accounts).SetCollectionValidator(accountDetailValidator);
}
}
Is there something I'm missing?
Access to injected dependencies can only be done from within a RuleFor() lambda, delegates in a RuleSet() are executed on constructor initialization to setup the rules for that RuleSet.
So you need to change your access to base.Request to within RuleFor() lambda:
RuleSet(ApplyTo.Post | ApplyTo.Put, () => {
RuleFor(c => c.Reference)
.Must(x => !string.IsNullOrEmpty(x) &&
(Request.GetSession() as CustomAuthSession).Region.GetCountry() == RegionCodes.AU);
});
I was playin' around with ServiceStack and was wondering if it supported this scenario. I'm using generics in my request types so that many DTOs that inherit from a common interface will support the same basic methods [ like... GetById(int Id) ].
Using a request type specific to a single kind of DTO works, but breaks the generics nice-ness...
var fetchedPerson = client.Get<PersonDto>(new PersonDtoGetById() { Id = person.Id });
Assert.That(person.Id, Is.EqualTo(fetchedPerson.Id)); //PASS
Mapping a route to the generic also works:
Routes.Add<DtoGetById<PersonDto>>("/persons/{Id}", ApplyTo.Get);
...
var fetchedPerson2 = client.Get<PersonDto>(string.Format("/persons/{0}", person.Id));
Assert.That(person.Id, Is.EqualTo(fetchedPerson2.Id)); //PASS
But using the end-to-end generic request type fails:
var fetchedPerson3 = client.Get<PersonDto>(new DtoGetById<PersonDto>() { Id = person.Id });
Assert.That(person.Id, Is.EqualTo(fetchedPerson3.Id)); //FAIL
I wonder if I'm just missing something, or if i'm trying to abstract just ooone layer too far... :)
Below is a complete, failing program using NUnit, default ServiceStack stuff:
namespace ssgenerics
{
using NUnit.Framework;
using ServiceStack.ServiceClient.Web;
using ServiceStack.ServiceHost;
using ServiceStack.ServiceInterface;
using ServiceStack.WebHost.Endpoints;
[TestFixture]
class Program
{
public static PersonDto GetNewTestPersonDto()
{
return new PersonDto()
{
Id = 123,
Name = "Joe Blow",
Occupation = "Software Developer"
};
}
static void Main(string[] args)
{}
[Test]
public void TestPutGet()
{
var listeningOn = "http://*:1337/";
var appHost = new AppHost();
appHost.Init();
appHost.Start(listeningOn);
try
{
var BaseUri = "http://localhost:1337/";
var client = new JsvServiceClient(BaseUri);
var person = GetNewTestPersonDto();
client.Put(person);
var fetchedPerson = client.Get<PersonDto>(new PersonDtoGetById() { Id = person.Id });
Assert.That(person.Id, Is.EqualTo(fetchedPerson.Id));
var fetchedPerson2 = client.Get<PersonDto>(string.Format("/persons/{0}", person.Id));
Assert.That(person.Id, Is.EqualTo(fetchedPerson2.Id));
Assert.That(person.Name, Is.EqualTo(fetchedPerson2.Name));
Assert.That(person.Occupation, Is.EqualTo(fetchedPerson2.Occupation));
var fetchedPerson3 = client.Get<PersonDto>(new DtoGetById<PersonDto>() { Id = person.Id });
Assert.That(person.Id, Is.EqualTo(fetchedPerson3.Id));
Assert.That(person.Name, Is.EqualTo(fetchedPerson3.Name));
Assert.That(person.Occupation, Is.EqualTo(fetchedPerson3.Occupation));
}
finally
{
appHost.Stop();
}
}
}
public interface IDto : IReturnVoid
{
int Id { get; set; }
}
public class PersonDto : IDto
{
public int Id { get; set; }
public string Name { get; set; }
public string Occupation { get; set; }
}
public class DtoGetById<T> : IReturn<T> where T : IDto { public int Id { get; set; } }
public class PersonDtoGetById : IReturn<PersonDto> { public int Id { get; set; } }
public abstract class DtoService<T> : Service where T : IDto
{
public abstract T Get(DtoGetById<T> Id);
public abstract void Put(T putter);
}
public class PersonService : DtoService<PersonDto>
{
public override PersonDto Get(DtoGetById<PersonDto> Id)
{
//--would retrieve from data persistence layer
return Program.GetNewTestPersonDto();
}
public PersonDto Get(PersonDtoGetById Id)
{
return Program.GetNewTestPersonDto();
}
public override void Put(PersonDto putter)
{
//--would persist to data persistence layer
}
}
public class AppHost : AppHostHttpListenerBase
{
public AppHost()
: base("Test HttpListener",
typeof(PersonService).Assembly
) { }
public override void Configure(Funq.Container container)
{
Routes.Add<DtoGetById<PersonDto>>("/persons/{Id}", ApplyTo.Get);
}
}
}
No, It's a fundamental concept in ServiceStack that each Service requires its own unique Request DTO, see this answer for more examples on this.
You could do:
[Route("/persons/{Id}", "GET")]
public class Persons : DtoGetById<Person> { ... }
But I strongly advise against using inheritance in DTOs. Property declaration is like a DSL for a service contract and its not something that should be hidden.
For more details see this answer on the purpose of DTO's in Services.
I am trying to implement validation feature in ServiceStack to validate my RequestDTO's before calling db operations.
When i try to validate request dto like
ValidationResult result = this.AddBookingLimitValidator.Validate(request);
the code automatically throws a validation error automatically.
I can not even debug service what is happening behind the scenes ? Can i change that behaviour or am i doing something wrong here.
Thanks.
My Request DTO :
[Route("/bookinglimit", "POST")]
[Authenticate]
public class AddBookingLimit : IReturn<AddBookingLimitResponse>
{
public int ShiftId { get; set; }
public DateTime Date { get; set; }
public int Limit { get; set; }
}
My Response DTO :
public class AddBookingLimitResponse
{
public int Id { get; set; }
public ResponseStatus ResponseStatus { get; set; }
}
Validation class :
public class AddBookingLimitValidator : AbstractValidator<AddBookingLimit>
{
public AddBookingLimitValidator()
{
RuleFor(r => r.Limit).GreaterThan(0).WithMessage("Limit 0 dan büyük olmalıdır");
}
}
Service Implementation :
public AddBookingLimitResponse Post(AddBookingLimit request)
{
ValidationResult result = this.AddBookingLimitValidator.Validate(request);
Shift shift = new ShiftRepository().Get(request.ShiftId);
BookingLimit bookingLimit = new BookingLimit
{
RestaurantId = base.UserSession.RestaurantId,
ShiftId = request.ShiftId,
StartDate = request.Date.AddHours(shift.StartHour.Hour).AddMinutes(shift.StartHour.Minute),
EndDate = request.Date.AddHours(shift.EndHour.Hour).AddMinutes(shift.EndHour.Minute),
Limit = request.Limit,
CreateDate = DateTime.Now,
CreatedBy = base.UserSession.UserId,
Status = (byte)Status.Active
};
return new AddBookingLimitResponse
{
Id = new BookingLimitRepository().Add(bookingLimit)
};
}
AppHost code :
container.RegisterValidators(typeof(AddBookingLimitValidator).Assembly);
Plugins.Add(new ValidationFeature());
And i consume the service in c# code:
try
{
AddBookingLimitResponse response = ClientHelper.JsonClient.Post(new AddBookingLimit
{
Date = DateTime.Parse(DailyBookingLimitDateTextBox.Text),
Limit = Convert.ToInt32(DailyBookingLimitTextBox.Text),
ShiftId = Convert.ToInt32(DailyDayTypeSelection.SelectedValue)
});
WebManager.ShowMessage(UserMessages.SaveSuccessful.FormatString(Fields.BookingLimit));
}
catch (WebServiceException ex)
{
WebManager.ShowMessage(ex.ResponseStatus.Message);
}
Right, ServiceStack validates the request DTO before the service gets called if the ValidationFeature is enabled.
To manually invoke the validator in the service, you have to remove this line from your AppHost first:
Plugins.Add(new ValidationFeature());
Please make sure that the validator property in your service has the type IValidator<>, otherwise it won't be injected by the IoC container if you register your validators with container.RegisterValidators(typeof(AddBookingLimitValidator).Assembly).
public class TestService : Service
{
public IValidator<Request> Validator { get; set; }
public RequestResponse Post(Request request)
{
Validator.Validate(request);
...
}
}