Trying to get MicroElements.Swashbuckle.FluentValidation to work using the Command Handler pattern from https://learn.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/microservice-application-layer-implementation-web-api
Using ASP .Net Core 2.2
MicroElements.Swashbuckle.FluentValidation v3.0.0-alpha.1 (as an assembly not a package ref)
Swashbuckle.AspNetCore 5.0.0-rc2
I have this is Startup.cs
return services.AddSwaggerGen(setup =>
{
setup.AddFluentValidationRules();
});
using fluent Validation
This does not extract the Fluent validations into schema for the request body object.
public class AddModelsCommandValidator : AbstractValidator<AddModelsCommand>
{
public AddModelsCommandValidator()
{
//1. validate request
RuleFor(e => e.Model).InvalidRequestValidation();
When(x => x.Model != null, () =>
{
//2. validate request body
RuleFor(e => e.Model.ModelCode).StringRequiredValidation();
RuleFor(e => e.Model.ModelCode).StringMaxLengthValidation(5);
RuleFor(e => e.Model.ProgramName).StringRequiredValidation();
RuleFor(e => e.Model.ProgramName).StringMaxLengthValidation(50);
});
}
}
public class AddModelsCommand : IRequest<AddModelsCommandResult>
{
public Model Model { get; }
public AddModelsCommand(Model model)
{
Model = model;
}
}
public class Model
{
/// <summary>
/// Unique code of the Model
/// </summary>
public string ModelCode { get; set; }
/// <summary>
/// The name of the Program
/// </summary>
public string ProgramName { get; set; }
}
The following code does extract the Fluent validations into schema for the request body object. (because 1. the AbstractValidator is on the Model not the Command, and 2. I have removed the conditional When() validation)
public class AddModelsCommandValidator : AbstractValidator<Model>
{
public AddModelsCommandValidator()
{
//2. validate request body
RuleFor(e => e.ModelCode).StringRequiredValidation();
RuleFor(e => e.ModelCode).StringMaxLengthValidation(5);
RuleFor(e => e.ProgramName).StringRequiredValidation();
RuleFor(e => e.ProgramName).StringMaxLengthValidation(50);
}
}
Is there way to call AddFluentValidationRules and also use the Command Handler pattern?
The issue here is AbstractValidator<Model> has to be used..
AbstractValidator<Command> does not really make sense
Related
I have seen many examples of using FluentValidation here but none seem to fit my need. I have an existing server side implementation and based on answers here, I am convinced I have to change my implementation to get it work client side. One reason is because I can't set a ValidationType which seems required for the client side. I am having trouble converting the code so I can use it client side as well. I am submitting a list of File objects and want client side validation that the file extensions are either .pdf or .doc.
Global - Many examples here show a much more complicated Configure
protected void Application_Start()
{
FluentValidationModelValidatorProvider.Configure();
}
Model - I've simplified my model to show that I have at least one property and a collection
[Validator(typeof(MyCustomValidator))]
public class MyCustomModel
{
public DateTime SubmitDate { get; set; }
public List<HttpPostedFileBase> MyFiles { get; set; }
}
Model Validator - I have a separate validator for the collection
public class MyCustomModelValidator : AbstractValidator<MyCustomModel>
{
public MyCustomModelValidator()
{
RuleFor(x => x.SubmitDate)
.NotEmpty()
.WithMessage("Date Required");
RuleFor(x => x.MyFiles)
.SetCollectionValidator(new MyFileValidator())
.Where(x => x != null);
}
}
Collection Validator - This should check a file for a valid extension
public class MyFileValidator : AbstractValidator<HttpPostedFileBase>
{
public MyFileValidator()
{
RuleFor(x => x)
.Must(x => x.IsValidFileType())
.WithMessage("Invalid File Type")
}
}
public static bool IsValidFileType(this HttpPostedFileBase file)
{
var extensions = { ".pdf", ".doc" };
return extensions.Contains(Path.GetExtension(file.FileName.ToLower()));
}
Controller - Just showing the basics
[HttpGet]
public ActionResult Index(DefaultParameters parameters)
{
var model = new MyCustomModel();
return this.View(model);
}
[HttpPost]
public ActionResult Submit(MyCustomModel model)
{
if (!ModelState.IsValid)
{
return this.View("Index", model);
}
}
View - I am allowing 5 uploads per submission
#Html.TextBoxFor(m => m.SubmitDate)
#Html.ValidationMessageFor(m => m.TextBoxFor
#for (int i = 0; i < 5; i++)
{
#Html.TextBoxFor(m => m.FileSubmissions[i], new { type = "file" })
#Html.ValidationMessageFor(m => m.FileSubmissions[i])
}
Im not sure why you would change it to clientside? Not all validations should run clientside. Maybe you can get it working using the regex validation method instead of the Must method which is performed clientside according to the docs.
In my ASP.NET MVC 4 project I have validator for one of my view models, that contain rules definition for RuleSets. Edit ruleset used in Post action, when all client validation passed. Url and Email rule sets rules used in Edit ruleset (you can see it below) and in special ajax actions that validate only Email and only Url accordingly.
My problem is that view doesn't know that it should use Edit rule set for client html attributes generation, and use default rule set, which is empty. How can I tell view to use Edit rule set for input attributes generation?
Model:
public class ShopInfoViewModel
{
public long ShopId { get; set; }
public string Name { get; set; }
public string Url { get; set; }
public string Description { get; set; }
public string Email { get; set; }
}
Validator:
public class ShopInfoViewModelValidator : AbstractValidator<ShopInfoViewModel>
{
public ShopInfoViewModelValidator()
{
var shopManagementService = ServiceLocator.Instance.GetService<IShopService>();
RuleSet("Edit", () =>
{
RuleFor(x => x.Name)
.NotEmpty().WithMessage("Enter name.")
.Length(0, 255).WithMessage("Name length should not exceed 255 chars.");
RuleFor(x => x.Description)
.NotEmpty().WithMessage("Enter name.")
.Length(0, 10000).WithMessage("Name length should not exceed 10000 chars.");
ApplyUrlRule(shopManagementService);
ApplyEmailRule(shopManagementService);
});
RuleSet("Url", () => ApplyUrlRule(shopManagementService));
RuleSet("Email", () => ApplyEmailRule(shopManagementService));
}
private void ApplyUrlRule(IShopService shopService)
{
RuleFor(x => x.Url)
.NotEmpty().WithMessage("Enter url.")
.Length(4, 30).WithMessage("Length between 4 and 30 chars.")
.Matches(#"[a-z\-\d]").WithMessage("Incorrect format.")
.Must((model, url) => shopService.Available(url, model.ShopId)).WithMessage("Shop with this url already exists.");
}
private void ApplyEmailRule(IShopService shopService)
{
// similar to url rule: not empty, length, regex and must check for unique
}
}
Validation action example:
public ActionResult ValidateShopInfoUrl([CustomizeValidator(RuleSet = "Url")]
ShopInfoViewModel infoViewModel)
{
return Validation(ModelState);
}
Get and Post actions for ShopInfoViewModel:
[HttpGet]
public ActionResult ShopInfo()
{
var viewModel = OwnedShop.ToViewModel();
return PartialView("_ShopInfo", viewModel);
}
[HttpPost]
public ActionResult ShopInfo(CustomizeValidator(RuleSet = "Edit")]ShopInfoViewModel infoViewModel)
{
var success = false;
if (ModelState.IsValid)
{
// save logic goes here
}
}
View contains next code:
#{
Html.EnableClientValidation(true);
Html.EnableUnobtrusiveJavaScript(true);
}
<form class="master-form" action="#Url.RouteUrl(ManagementRoutes.ShopInfo)" method="POST" id="masterforminfo">
#Html.TextBoxFor(x => x.Name)
#Html.TextBoxFor(x => x.Url, new { validationUrl = Url.RouteUrl(ManagementRoutes.ValidateShopInfoUrl) })
#Html.TextAreaFor(x => x.Description)
#Html.TextBoxFor(x => x.Email, new { validationUrl = Url.RouteUrl(ManagementRoutes.ValidateShopInfoEmail) })
<input type="submit" name="asdfasfd" value="Сохранить" style="display: none">
</form>
Result html input (without any client validation attributes):
<input name="Name" type="text" value="Super Shop"/>
After digging in FluentValidation sources I found solution. To tell view that you want to use specific ruleset, decorate your action, that returns view, with RuleSetForClientSideMessagesAttribute:
[HttpGet]
[RuleSetForClientSideMessages("Edit")]
public ActionResult ShopInfo()
{
var viewModel = OwnedShop.ToViewModel();
return PartialView("_ShopInfo", viewModel);
}
If you need to specify more than one ruleset — use another constructor overload and separate rulesets with commas:
[RuleSetForClientSideMessages("Edit", "Email", "Url")]
public ActionResult ShopInfo()
{
var viewModel = OwnedShop.ToViewModel();
return PartialView("_ShopInfo", viewModel);
}
If you need to decide about which ruleset would be used directly in action — you can hack FluentValidation by putting array in HttpContext next way (RuleSetForClientSideMessagesAttribute currently is not designed to be overriden):
public ActionResult ShopInfo(validateOnlyEmail)
{
var emailRuleSet = new[]{"Email"};
var allRuleSet = new[]{"Edit", "Url", "Email"};
var actualRuleSet = validateOnlyEmail ? emailRuleSet : allRuleSet;
HttpContext.Items["_FV_ClientSideRuleSet"] = actualRuleSet;
return PartialView("_ShopInfo", viewModel);
}
Unfortunately, there are no info about this attribute in official documentation.
UPDATE
In newest version we have special extension method for dynamic ruleset setting, that you should use inside your action method or inside OnActionExecuting/OnActionExecuted/OnResultExecuting override methods of controller:
ControllerContext.SetRulesetForClientsideMessages("Edit", "Email");
Or inside custom ActionFilter/ResultFilter:
public class MyFilter: ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
((Controller)context.Controller).ControllerContext.SetRulesetForClientsideMessages("Edit", "Email");
//same syntax for OnActionExecuted/OnResultExecuting
}
}
Adding to this as the library has been updated to account for this situation...
As of 7.4.0, it's possible to dynamically select one or multiple rule sets based on your specific conditions;
ControllerContext.SetRulesetForClientsideMessages("ruleset1", "ruleset2" /*...etc*);
Documentation on this can be found in the latest FluentValidation site:
https://fluentvalidation.net/aspnet#asp-net-mvc-5
Adding the CustomizeValidator attribute to the action will apply the ruleset within the pipeline when the validator is being initialized and the model is being automatically validated.
public ActionResult Save([CustomizeValidator(RuleSet="MyRuleset")] Customer cust) {
// ...
}
I have this model for an MVC WEB API controller.
What will be the corresponding JSON to match this model structure?
namespace CarEvaluator.Models
{
[DataContract]
public class Record
{
[DataMember]
public List<Boolean> CHits { get; set; }
[DataMember]
public List<Boolean> MHits { get; set; }
}
}
public void Post(Record record)
{
}
Structure:
{"CHits":[true,false],"MHits":[true,false]}
Example:
var postObject = new Object();
// Initialize CHits and MHits as arrays
postObject.CHits = [];
postObject.MHits = [];
// push some items into the arrays
postObject.CHits.push(true);
postObject.CHits.push(false);
postObject.MHits.push(true);
postObject.MHits.push(false);
// serialize data to post
// this is what you set as data property in for instance jquery ajax
$.ajax({
//other params, content type etc
type: 'POST',
data: JSON.stringify(postObject),
...
});
if your parameter is null, you should try to add the [FromBody] attribute and decorate the method with httppost
[HttpPost]
public void Post([FromBody]Record record)
{
}
I have model:
[Validator(typeof(RegisterValidator))]
public class RegisterModel
{
public string Name { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public string ListOfCategoriess { get; set; }
}
And validator for model:
public class RegisterValidator:AbstractValidator<RegisterModel>
{
public RegisterValidator(IUserService userService)
{
RuleFor(x => x.Name).NotEmpty().WithMessage("User name is required.");
RuleFor(x => x.Email).NotEmpty().WithMessage("Email is required.");
RuleFor(x => x.Email).EmailAddress().WithMessage("Invalid email format.");
RuleFor(x => x.Password).NotEmpty().WithMessage("Password is required.");
RuleFor(x => x.ConfirmPassword).NotEmpty().WithMessage("Please confirm your password.");
}
}
I have validator factory, that should resolve dependency:
public class WindsorValidatorFactory : ValidatorFactoryBase
{
private readonly IKernel kernel;
public WindsorValidatorFactory(IKernel kernel)
{
this.kernel = kernel;
}
public override IValidator CreateInstance(Type validatorType)
{
if (validatorType == null)
throw new Exception("Validator type not found.");
return (IValidator) kernel.Resolve(validatorType);
}
}
I have IUserService, that has methods IsUsernameUnique(string name) and IsEmailUnique(string email)` and want to use it in my validator class (model should be valid only if it have unique username and email).
how to use my service for validation?
is it possible to register multiple Regular Expression Rules with different error messages? will it work on client side? (if no, how to create custom validation logic for it?)
is validation on server side will work automatically before model pass in action method, and it is enough to call ModelState.IsValid property, or I need to do something more?
UPDATE
is it possible to access to all properties of model when validate some property? (for example I want to compare Password and ConfirmPassword when register)
1) how to use my service for validation?
You could use the Must rule:
RuleFor(x => x.Email)
.NotEmpty()
.WithMessage("Email is required.")
.EmailAddress()
.WithMessage("Invalid email format.")
.Must(userService.IsEmailUnique)
.WithMessage("Email already taken");
2) is it possible to register multiple Regular Expression Rules with different error messages? will it work on client side? (if no, how to create custom validation logic for it?)
No, you can have only one validation type per property
if no, how to create custom validation logic for it?
You could use the Must rule:
RuleFor(x => x.Password)
.Must(password => SomeMethodContainingCustomLogicThatMustReturnBoolean(password))
.WithMessage("Sorry password didn't satisfy the custom logic");
3) is validation on server side will work automatically before model pass in action method, and it is enough to call ModelState.IsValid property, or I need to do something more?
Yes, absolutely. Your controller action could look like this:
[HttpPost]
public ActionResult Register(RegisterModel model)
{
if (!ModelState.IsValid)
{
// validation failed => redisplay the view so that the user
// can fix his errors
return View(model);
}
// at this stage the model is valid => process it
...
return RedirectToAction("Success");
}
UPDATE:
4) is it possible to access to all properties of model when validate some property? (for example I want to compare Password and ConfirmPassword when register)
Yes, of course:
RuleFor(x => x.ConfirmPassword)
.Equal(x => x.Password)
.WithMessage("Passwords do not match");
a nicer variant is to use a RuleBuilderExtension:
public static class RuleBuilderExtensions
{
public static IRuleBuilder<T, string> Password<T>(this IRuleBuilder<T, string> ruleBuilder, int minimumLength = 14)
{
var options = ruleBuilder
.NotEmpty().WithMessage(ErrorMessages.PasswordEmpty)
.MinimumLength(minimumLength).WithMessage(ErrorMessages.PasswordLength)
.Matches("[A-Z]").WithMessage(ErrorMessages.PasswordUppercaseLetter)
.Matches("[a-z]").WithMessage(ErrorMessages.PasswordLowercaseLetter)
.Matches("[0-9]").WithMessage(ErrorMessages.PasswordDigit)
.Matches("[^a-zA-Z0-9]").WithMessage(ErrorMessages.PasswordSpecialCharacter);
return options;
}
This way it gets trivial to use:
RuleFor(x => x.Password).Password();
OK...I've spent too much time floundering on this one, so I'm passing it on to the experts -> YOU.
My VERY simple ASP.NET MVC (v3 with Razor View Engine) page uses a Telerik Rad Grid control to show some type lists and then I have the associated codes showing in the DetailsView of the grid.
Doing the population is easy. I have a ViewModel for my TypeCodeList type and send it to the strongly typed view to populate the grid. This works GREAT...and the grid looks great - thanks Telerik. However, I added the DetailsView to then populate the child TypeCodes in the same manner. The bad thing is that when my grid populates, I select the triangle on the left to expand the tree and see the child records, nothing is there. BUT, if I select the "Refresh" button on the bottom of the grid, THEN I can hit the triangle and the child records display.
So (in summary), the child records do not show up on the initial load. Only when I select an AJAX refresh of the grid I get the children. Otherwise, it works as required.
I have been trying to see if I can programmatically kick off the refresh via javascrip upon page load. OR if I can get the thing to populate by itself when selected without doing a refresh first - that would be preferable.
Below is my code:
Pertinent Controller Code (I've taken out the update, delete, insert, logging and data access methods)
[HandleErrorWithElmah]
public partial class HostController : Controller
{
/// <summary>
/// Index - Home for HostController
/// </summary>
/// <returns></returns>
public ActionResult Index()
{
return View();
}
#region Type List Section
/// <summary>
/// Gets the list of TypeLists - yea...say that again
/// </summary>
[GridAction]
public ActionResult TypeCodeList()
{
var model = GetActiveTypeLists();
// Get all of the type lists and send them to the view
return View(model);
}
/// <summary>
/// The ajaxified Select
/// </summary>
/// <returns></returns>
[AcceptVerbs(HttpVerbs.Post)]
[GridAction]
public ActionResult _TypeCodeList()
{
var model = GetActiveTypeLists();
return Json(new GridModel(model));
}
/// <summary>
/// Simply a wrapper to get all of the current type list values.
/// </summary>
/// <returns></returns>
private IEnumerable<TypeCodeListViewModel> GetActiveTypeLists()
{
var model = from p in entityRepository.Find<TypeList>(p => p.IsActive == true)
select new TypeCodeListViewModel
{
TypeListId = p.TypeListId,
Name = p.Name,
Description = p.Description,
IsActive = p.IsActive
};
return model;
}
#endregion
#region Type Code Section
[AcceptVerbs(HttpVerbs.Post)]
[GridAction]
public ActionResult _TypeCodeForTypeListAjax(int typeListId)
{
var model = GetActiveTypeCodes(typeListId);
return Json(new GridModel(model));
}
/// <summary>
/// Simply a wrapper to get all of the current type Code values.
/// </summary>
/// <returns></returns>
private IEnumerable<TypeCodeViewModel> GetAllActiveTypeCodes()
{
var model = from p in entityRepository.Find<OurLeaguePlay.Models.TypeCode>(p => p.IsActive == true).OrderBy(ord => ord.CodeName)
select new TypeCodeViewModel
{
TypeCodeId = p.TypeCodeId,
TypeListId = p.TypeListId,
CodeName = p.CodeName,
CodeValue = p.CodeValue,
Description = p.Description,
IsActive = p.IsActive
};
return model;
}
/// <summary>
/// Simply a wrapper to get all of the current type Code values.
/// </summary>
/// <returns></returns>
private IEnumerable<TypeCodeViewModel> GetActiveTypeCodes(int typeListId)
{
var model = from p in entityRepository.Find<OurLeaguePlay.Models.TypeCode>(p => p.IsActive == true &&
p.TypeListId == typeListId).OrderBy(ord => ord.CodeName)
select new TypeCodeViewModel
{
TypeCodeId = p.TypeCodeId,
TypeListId = p.TypeListId,
CodeName = p.CodeName,
CodeValue = p.CodeValue,
Description = p.Description,
IsActive = p.IsActive
};
return model;
}
#endregion
}
Here is my View Code:
(I've taken out all of my failed javascript attempts to try and force the load on page load.)
#model IEnumerable<TypeCodeListViewModel>
#using Telerik.Web.Mvc.UI
#using Telerik.Web.Mvc
#using OurLeaguePlay.ViewModels
#{Html.Telerik().Grid<TypeCodeListViewModel>(Model)
.Name("TypeLists")
.DetailView(details => details.ClientTemplate(
Html.Telerik().Grid<TypeCodeViewModel>()
.Name("TypeCode_<#= TypeListId #>")
.DataKeys(keys => keys.Add(k => k.TypeCodeId))
.Columns(columns =>
{
columns.Bound(o => o.CodeName).Width(40);
columns.Bound(o => o.CodeValue).ReadOnly(true).Width(40);
columns.Bound(o => o.Description).Width(100);
})
.DataBinding(dataBinding =>
{
dataBinding.Ajax().Select("_TypeCodeForTypeListAjax", "Host", new { typeListId = "<#= TypeListId #>" })
.Enabled(true);
}
)
.Pageable()
.Sortable()
.NoRecordsTemplate("No Type Codes exist for the selected Type List")
.ToHtmlString()
)
)
.DataKeys(keys => keys.Add(k => k.TypeListId))
.Columns(columns =>
{
columns.Bound(o => o.Name).Width(100);
columns.Bound(o => o.Description).Width(150);
columns.Command(commands =>
{
commands.Edit().ButtonType(GridButtonType.Image);
commands.Delete().ButtonType(GridButtonType.Image);
}
).Width(30);
})
.DataBinding(dataBinding =>
{
dataBinding.Ajax().Select("_TypeCodeList", "Host")
.Update("UpdateTypeList", "Host")
.Insert("InsertTypeList", "Host")
.Delete("DeleteTypeList", "Host")
.Enabled(true);
dataBinding.Server().Select("TypeCodeList", "Host", new { ajax = ViewData["ajax"] });
}
)
.Editable(editable => editable.Enabled(true).Mode(GridEditMode.InLine))
.Pageable(page => page.PageSize(10))
.Sortable()
.Selectable()
.Scrollable(scroll => scroll.Enabled(false))
.NoRecordsTemplate("No Type Lists can be retrieved from the database")
.ToolBar(commands => commands.Insert())
.Render();
}
Finally...here are the ViewModel classes:
public class TypeCodeListViewModel
{
[ScaffoldColumn(false)]
public int TypeListId { get; set; }
[Required(ErrorMessage = "Required")]
[StringLength(25, ErrorMessage = "Max Length is 25")]
public string Name { get; set; }
[Required(ErrorMessage = "Required")]
[StringLength(25, ErrorMessage="Max Length is 25")]
public string Description { get; set; }
[ScaffoldColumn(false)]
public bool IsActive { get; set; }
}
public class TypeCodeViewModel
{
[ScaffoldColumn(false)]
public int TypeCodeId { get; set; }
[ScaffoldColumn(false)]
public int TypeListId { get; set; }
[Required(ErrorMessage = "Required")]
[StringLength(25, ErrorMessage = "Max Length is 25")]
[DisplayName("Name")]
public string CodeName { get; set; }
[Required(ErrorMessage = "Required")]
[StringLength(25, ErrorMessage = "Max Length is 25")]
[DisplayName("Value")]
public string CodeValue { get; set; }
[StringLength(500, ErrorMessage = "Max Length is 500")]
public string Description { get; set; }
[ScaffoldColumn(false)]
public bool IsActive { get; set; }
}
Well...I think I figured it out on my own...it was as simple as just letting the grid bind itself and not forcing data into it via the non-ajax method that gets called upon initial display of the page.
The
Public ActionResult TypeCodeList()
function should simply be updated to the following:
Public ActionResult TypeCodeList()
{
return View();
}
with no [GridAction] decorator.
If you don't force values into the grid, it will bind itself using the Ajax method and then the child grids will populate upon expansion.