Client-side validation for custom StringLength validation attribute - c#

I have the following custom validation attribute, which derives from StringLengthAttribute:
public class StringLengthLocalizedAttribute : StringLengthAttribute
{
public StringLengthLocalizedAttribute(int maximumLength) : base(maximumLength)
{
var translator = DependencyResolver.Current.GetService<ITranslator();
var translatedValue = translator.Translate("MaxLengthTranslationKey", ErrorMessage);
ErrorMessage = translatedValue.Replace("{MaxLength}", maximumLength.ToString());
}
}
The only purpose of this custom attribute is to localize the ErrorMessage. The problem is, when I use this in my models it does not generate any client-side validation, but the standard StringLength attribute does.
I don't see how my attribute differs in any way - since it derives from the StringLength attribute I shouldn't have to implement any additional functionality to get client side validation working?

If you look at the source code for DataAnnotationsModelValidatorProvider, you'll see in the method BuildAttributeFactoriesDictionary that specific types of attributes are registered for client side validation - you have created a new type, hence no client side validation.
Thankfully, this also has a public method to add your own adapter and is easy to use in the simple case you give:
Firstly, you need an adapter that will provide the client validation rules:
public class MyStringLengthAdapter : DataAnnotationsModelValidator<MyStringLengthAttribute>
{
public MyStringLengthAdapter(ModelMetadata metadata, ControllerContext context, MyStringLengthAttribute attribute)
: base(metadata, context, attribute)
{
}
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
return new[] { new ModelClientValidationStringLengthRule(ErrorMessage, Attribute.MinimumLength, Attribute.MaximumLength) };
}
}
You then need to register this in the Application_Start method in Global.asax.cs like so:
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof (MyStringLengthAttribute), typeof (MyStringLengthAdapter));

Related

Custom Validate Attribute not triggering jQuery validate

I'm struggling to figure out an issue with a custom validation attribute and why this is not acting the same as a non-custom validation attribute.
I've setup a really basic custom validation attribute as an example:
public class AlwaysFalse : ValidationAttribute, IClientValidatable
{
public IEnumerable<ModelClientValidationRule>
GetClientValidationRules(ModelMetadata metadata, ControllerContext
context)
{
yield return new ModelClientValidationRule
{
ErrorMessage = this.ErrorMessage,
ValidationType = "alwaysfalse"
};
}
protected override ValidationResult IsValid(object value,
ValidationContext validationContext)
{
return new ValidationResult("Error");
}
}
I've applied the attribute to a property in my Model.
The custom js I have written is as follows:
jQuery.validator.addMethod('falseMethod', function (value, element,
params) {
return false;
}, '');
// and an unobtrusive adapter
jQuery.validator.unobtrusive.adapters.add('alwaysfalse', {}, function
(options) {
options.rules['falseMethod'] = true;
options.messages['falseMethod'] = options.message;
});
I have followed the advice from the following post:
Perform client side validation for custom attribute
When I call my Controllers POST function, the validation occurs fine. However I want it to be triggered similar to other validationAttributes. For example, i've also setup a Range validation Attribute on one of my models properties and as soon as I enter invalid information into this field, the class "input-validation-error" is assigned to the input field in question.
Any help/assistance would be greatly appreciated.
Please let me know if I can provide more information.
Ok, now answered this.
I'm quite new to C# and didn't realise the difference between have asp.NET core as my target framework and trying to use a .Net class.
I also had warnings surrounding the Web.MVC class not working as expected.
The forum post resolved my issue:
ASP.Net Core MVC - Client-side validation for custom attribute
Changed to using IClientModelValidator class using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;

Access model data in AddValidation method asp.net core custom validation

I am following this example: https://learn.microsoft.com/en-us/aspnet/core/mvc/models/validation and trying to implement my own custom attribute for validation.
Now, the viewmodel has two fields that I wish to access from inside this method, so that they can be rendered with the "data-val" attributes. My question is, how can I get say a property called "Myprop" from context here? When I debug I can se the information under context.ActionContext.ViewData.Model but I have no way of getting that info other then during the debug when I use Visual Studio "quick watch" feature. The custom attributes are on properties that are on the viewmodel.
public void AddValidation(ClientModelValidationContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
MergeAttribute(context.Attributes, "data-val", "true");
MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage());
var year = _year.ToString(CultureInfo.InvariantCulture);
MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
}
I ran into a similar problem. The short answer is that you can't from there. You only have access to the metadata from there, not the actual model. The reason for this is that your model metadata and validation based on it are done on first time use, and then cached. So you'll never be able to change what validation rules to return based on the model via an attribute decorator.
If you need to dynamically decide which client side data-val-* attributes to render based off the instance/content of your model, you would need to inherit from DefaultValidationHtmlAttributeProvider, instead of using attributes, and override the AddValidationAttributes method. It's the only way I've found to do this so far. This is because inside of this method, you have access to the ModelExplorer
public class CustomValidationHtmlAttributeProvider : DefaultValidationHtmlAttributeProvider
{
private readonly IModelMetadataProvider metadataProvider;
public CustomValidationHtmlAttributeProvider(IOptions<MvcViewOptions> optionsAccessor, IModelMetadataProvider metadataProvider, ClientValidatorCache clientValidatorCache)
: base(optionsAccessor, metadataProvider, clientValidatorCache)
{
this.metadataProvider = metadataProvider;
}
public override void AddValidationAttributes(ViewContext viewContext, ModelExplorer modelExplorer, IDictionary<string, string> attributes)
{
//base implimentation
base.AddValidationAttributes(viewContext, modelExplorer, attributes);
//re-create the validation context (since it's encapsulated inside of the base implimentation)
var context = new ClientModelValidationContext(viewContext, modelExplorer.Metadata, metadataProvider, attributes);
//Only proceed if it's the model you need to do custom logic for
if (!(modelExplorer.Container.Model is MyViewModelClass model) || !modelExplorer.Metadata.PropertyName == "Myprop") return;
//Do stuff!
var validationAttributeAdapterProvider = viewContext.HttpContext.RequestServices.GetRequiredService<IValidationAttributeAdapterProvider>();
if (model.Myprop)
{
var validationAdapter = (RequiredAttributeAdapter)validationAttributeAdapterProvider.GetAttributeAdapter(new RequiredAttribute(), null);
validationAdapter.Attribute.ErrorMessage = "You not enter right stuff!";
validationAdapter.AddValidation(context);
}
}
}
And then register this class in the ConfigureServices() of your Startup
public void ConfigureServices(IServiceCollection services)
{
//All your other DI stuff here
//register the new ValidationHtmlAttributeProvider
services.AddSingleton<ValidationHtmlAttributeProvider, PseudoAttributeValidationHtmlAttributeProvider>();
}
The downside, is that if you have multiple models you need to do this for, it gets really ugly really fast. If anyone has found a better method, I'd love to hear it :-)

Web API model binding on generic interface type

I have a generic interface that represents query options for entities:
public interface IQueryOptions<TEntity> {
IQueryable<TEntity> Apply(IQueryable<TEntity> query);
}
Now I'd like to pass the query options using a query string to a WebApi route, constructing the IQueryOptions with a custom model binder.
(the IQueryOptions can either be a SortingOptions<> or a PagingOptions<>, depending on the querystring passed to the api)
Api
public class DummyController : ApiController {
// optional parameter - query options are not required
public void Test([ModelBinder]IQueryOptions<MyEntity> queryOptions = null)
{
...
}
}
Model Binder
public class QueryOptionsModelBinder : IModelBinder {
public bool BindModel(HttpActionContext actionContext...
}
WebApi config
var queryProvider = new SimpleModelBinderProvider(typeof(IQueryOptions<>), new QueryOptionsModelBinder());
config.Services.Insert(typeof(ModelBinderProvider), 0, queryProvider);
I'm currently unable to get it working. The API throws the following error:
Cannot create an instance of an interface.
... which indicates that my custom model binder never get's called. Doesn't model binding work with generic types? I tried writing my own model binder provider, but this didn't work either.

Where should the validation go in CQRS with MVC?

This is my post method for creating a new user:
[HttpPost]
public ActionResult CreateUser(CreateUserViewModel createUserViewModel)
{
CreateSystemUserCommand createSystemUserCommand = new CreateSystemUserCommand()
{
Firstname = createUserViewModel.Forename,
Surname = createUserViewModel.Surname,
Username = createUserViewModel.Username,
Password = createUserViewModel.Password
};
CreateSystemUserCommandHandler handler = new CreateSystemUserCommandHandler();
handler.Execute(createSystemUserCommand);
return RedirectToAction("ViewUsers");
}
There is some validation on the view model already, required fields etc. so the UI will have validation on it.
However I'm wondering how to do it server side.
Should I create a method createSystemUserCommand.Validate();
or before handler.Execute(), do handler.Validate()?
And how should I translate those errors into the ModelState? I'm guessing CQRS is not connected with MVC therefore it'd make no sense to return specifically model errors.
Any thoughts welcome on this. My gut feeling is to do handler.Validate since it'll keep validation logic within a single class, and it feels right, but I am open to suggestions.
There are 2 types of validation here that you could potentially need:
One is simple ModelState validation which ensures that required fields are not missing, int is an int and so on. For that, using Data annotation attributes will do the trick.
The second type is business logic validation - something that may require accessing database or running some other validation logic to make sure that data integrity is not affected. That type of validation would be at the command level.
The best way to do that is to follow the decorator pattern - wrap your actual handler in a validating handler:
public class ValidationCommandHandlerDecorator<TCommand, TResult>
: ICommandHandler<TCommand, TResult>
where TCommand : ICommand<TResult>
{
private readonly ICommandHandler<TCommand, TResult> decorated;
public ValidationCommandHandlerDecorator(ICommandHandler<TCommand, TResult> decorated)
{
this.decorated = decorated;
}
[DebuggerStepThrough]
public TResult Handle(TCommand command)
{
var validationContext = new ValidationContext(command, null, null);
Validator.ValidateObject(command, validationContext, validateAllProperties: true);
return this.decorated.Handle(command);
}
}
An example of validator would be:
public class SomeCustomLogicValidator : IValidator {
void IValidator.ValidateObject(object instance) {
var context = new ValidationContext(instance, null, null);
// Throws an exception when instance is invalid.
Validator.ValidateObject(instance, context, validateAllProperties: true);
}
}
And then register it as:
// using SimpleInjector.Extensions;
container.RegisterDecorator(
typeof(ICommandHandler<>),
typeof(ValidationCommandHandlerDecorator<>));
You can wrap as many decorators as you wish or even make it specific to a predicate (exact syntax depends on what DI framework you use):
// another decorator
container.RegisterDecorator(
typeof(ICommandHandler<>),
typeof(TransactionCommandHandlerDecorator<>));
// specific decorator
container.RegisterDecorator(
typeof(ICommandHandler<>),
typeof(AccessValidationCommandHandlerDecorator<>),
context => !context.ImplementationType.Namespace.EndsWith("Admins"));
The example I have uses a DI framework which makes things simpler, but this idea can be extended without using any DI container as well.
I typically use FluentValidation in my application layer (like in the command handlers) and in domain layer. These validators all throw exceptions, which I catch in a global exception handler, which has the responsability to propagate them to the consumer in the correct format (for example as a fault in WCF). These messages are already in the correct language, based on the culture that was set on the thread (if you have multi lingual site).
On the site, the list of errors is then used. The error messages are simply displayed and based on the error keys I can add additional logic to disable controls etc.
So in my case validation is in most cases server side and only defined once in application and domain layer. On client side there can be some other small input validation, to restrict user input for example.
I'm not sure if you are using Data Annotations or not, but with Data Annotations it can be like this.
Also see additional attribute ValidateAntiForgeryToken (may be useful for you).
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CreateUser(CreateUserViewModel createUserViewModel)
{
if (ModelState.IsValid)
{
CreateSystemUserCommand createSystemUserCommand = new CreateSystemUserCommand()
{
Firstname = createUserViewModel.Forename,
Surname = createUserViewModel.Surname,
Username = createUserViewModel.Username,
Password = createUserViewModel.Password
};
CreateSystemUserCommandHandler handler = new CreateSystemUserCommandHandler();
handler.Execute(createSystemUserCommand);
return RedirectToAction("ViewUsers");
}
return View(createUserViewModel);
}
But if you need complex validation you can go with:
if (ModelState.IsValid && handler.Validate())
Or you can implement you own validation logic and then add errors to ModelState by using ModelState.AddModelError.

Custom data annotation attribute not being validated

I am trying to make a custom validation using data annotations.
Trying to make the attribute, I have followed the question:
How to create Custom Data Annotation Validators
My attribute looks like this
internal class ExcludeDefaultAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
return false;
}
}
and the validation is called by:
internal static class TypeValidator
{
static public bool Validate(object item)
{
List<ValidationResult> results = new List<ValidationResult>();
ValidationContext context = new ValidationContext(item);
if (Validator.TryValidateObject(item, context, results))
{
return true;
}
else
{
string message = string.Format("Error validating item");
throw new TypeInvalidException(results, message);
}
}
}
So, here is the issue. My custom validation, currently, should always return false. So validation should always fail. However, whenever I try to validate an object that has this attribute on a field, it passes validation, which suggests that my custom validation attribute isn't being evaluated. I don't want to make any actual logic in the validation until I know it is actually running.
Am I missing something? All my research says I simply need to inherit from ValidationAttribute, but it isn't working.
According to the MSDN article, the TryValidateObject method will do the following:
This method evaluates each ValidationAttribute instance that is attached to the object type. It also checks whether each property that is marked with RequiredAttribute is provided. It does not recursively validate the property values of the object.
I tested this and it behaved as advertised using the syntax provided.
Edit
Per the comment below, using the following overload results in proper validation of all properties including those using custom attributes:
TryValidateObject(object instance, ValidationContext validationContext, ICollection<ValidationResult> validationResults, bool validateAllProperties)

Categories

Resources