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;
Related
I have an API controller for performing autosaves on an application I am developing. It uses the same viewmodel as the view, which has a number of required fields. The autosave controller may need to save a model that is not considered valid if the user has not completed the form when it is saved. By default, an .NET Core controller declared with the [ApiController] attribute will automatically force validation. I know I can disable this like so in Startup.cs:
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
But this will apply to all API controllers in the project. Is it possible to disable this default validation for only one controller or action? Everything I've found so far has directed me to use the code above, but that doesn't accomplish what I'm looking for.
You can override the default InvalidModelStateResponseFactory:
services.Configure<ApiBehaviorOptions>(options =>
{
options.InvalidModelStateResponseFactory =
AllowingServerSideValidationToBeDisabledInvalidModelStateResponseFactoryHelper.InvalidModelStateResponseFactory;
});
The InvalidModelStateResponseFactory below checks for OptionalValidationAttribute on a controller action, and searches for a form/query parameter flag which controls the validation is enabled/disabled:
// Code taken from https://github.com/dotnet/aspnetcore/blob/5747cb36f2040d12e75c4b5b3f49580ef7aac5fa/src/Mvc/Mvc.Core/src/DependencyInjection/ApiBehaviorOptionsSetup.cs#L23
// and is modified to optionally disable validation for controller action methods decorated with OptionalValidationAttribute
public static class AllowingServerSideValidationToBeDisabledInvalidModelStateResponseFactoryHelper
{
public static Func<ActionContext, IActionResult> InvalidModelStateResponseFactory => actionContext =>
{
var shouldEnableDataValidationarameterName = ((OptionalValidationAttribute)((ControllerActionDescriptor)actionContext.ActionDescriptor)
.MethodInfo.GetCustomAttributes(typeof(OptionalValidationAttribute), true)
.SingleOrDefault())?.ShouldEnableDataValidationParameterName;
var isValidationEnabled = true;
if (shouldEnableDataValidationarameterName != null)
{
var httpContextRequest = actionContext.HttpContext.Request;
var shouldEnableDataValidationValue = httpContextRequest.Form[shouldEnableDataValidationarameterName]
.Union(httpContextRequest.Query[shouldEnableDataValidationarameterName]).FirstOrDefault();
isValidationEnabled = shouldEnableDataValidationValue?.ToLower() == bool.TrueString.ToLower();
}
if (!isValidationEnabled)
{
return null;
}
var problemDetailsFactory = actionContext.HttpContext.RequestServices.GetRequiredService<ProblemDetailsFactory>();
var problemDetails = problemDetailsFactory.CreateValidationProblemDetails(actionContext.HttpContext, actionContext.ModelState);
ObjectResult result;
if (problemDetails.Status == 400)
{
// For compatibility with 2.x, continue producing BadRequestObjectResult instances if the status code is 400.
result = new BadRequestObjectResult(problemDetails);
}
else
{
result = new ObjectResult(problemDetails)
{
StatusCode = problemDetails.Status,
};
}
result.ContentTypes.Add("application/problem+json");
result.ContentTypes.Add("application/problem+xml");
return result;
};
}
The OptionalValidationAttribute:
[AttributeUsage(AttributeTargets.Method)]
public class OptionalValidationAttribute : Attribute
{
public OptionalValidationAttribute(string shouldEnableDataValidationParameterName)
{
ShouldEnableDataValidationParameterName = shouldEnableDataValidationParameterName;
}
public string ShouldEnableDataValidationParameterName { get; }
}
Example usage on an controller action:
[HttpPost]
[OptionalValidation(shouldEnableDataValidationParameterName: nameof(shouldEnableDataValidation))] // C# 11 needed to use nameof for a method parameter
public async Task<IActionResult> Update(
[FromForm] int id,
[FromForm] string name,
[FromForm] bool shouldEnableDataValidation
)
{
...
}
I would suggest you to approach this differently: Disabling the model validation will mean that there isn’t any validation for this action; not now, not later. Just because you do not require validation right now that doesn’t mean that you won’t need some kind of validation later on.
If you used some custom handling to disable the validation altogether for that action, then all you are doing is creating an actual exception into your application which will make it more complex. Developers looking at this later might not expect this behavior and could spend a good amount of time trying to figure out why the validation isn’t running when it’s working for every other action.
So instead, consider just duplicating the model so that each action has its own model: Action A has the original model with the validation attributes, requiring the values to be filled. And action B has a copy of that model without any validation attributes.
While this may seem wasteful, this does give you a few more benefits:
If you later require validation for some fields on action B, you could just add some validation attributes back. You didn’t have to disable the automatic validation completely, so individual validation attributes will just continue to work if you add them to the model.
Having separate models allows both actions to evolve independently. There’s already a good indicator that the actions do two different things: One requires the values, the other doesn’t. So it’s not unlikely that the models might need to diverge further in the future. For example, you might want to add a property to only one model but not the other.
As already mentioned above, you can stick to the default behavior and keep a consistent development experience.
Similar to Poke's answer, I would recommend using a different model for the action you want not to be validated. Instead of creating a copy of the model, however, I would just derive from the validated model and add the [ValidateNever] attribute, e.g.
[ValidateNever]
public class MyUnvalidatedModel : MyValidatedModel {
}
This will allow you to avoid a lot of duplication, while still giving you an unvalidated version of your model.
I have a asp .net mvc application. For all my other attributes, I've used data annotations to verify the attributes. But I would like to have a custom validation. I was reading online that a custom data annotation validation may be the approach to take.
Following this link here ASP.NET MVC: Custom Validation by DataAnnotation it looks like a great solution. The only issue I'm coming across is how do I access my DBContext if it's not within a controller.
What I have so far
This is the code I typicalled used in controllers to grab the current user and db context.
Controller code
private ApplicationDbContext _dbContext => HttpContext.GetOwinContext().Get<ApplicationDbContext>();
private ApplicationUserManager _userManager;
public ApplicationUserManager UserManager
{
get
{
return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
private set
{
_userManager = value;
}
}
ViewModel
[HasNoRedemption]
public string code {get; set;}
HasNoRedemption.cs
public class HasNoRedemption : ValidationAttribute
{
public override bool IsValid(object value)
{
//check if user has ever claimed the code, return true is good
}
}
If I may get some assistance in making a custom validation such that I'll be able to check the db or a suggestion for a better approach.
1) I would not recommend using data annotation attributes to implement your business logic inside. Validation attributes are ought to contain pure functions. The intention behind the validation attributes is to narrow down the range of the input data that is considered to be correct. Not to implement business rules of your application
2) If you really wish to do what you want (for curiosity sake), check the following instructions: https://andrewlock.net/injecting-services-into-validationattributes-in-asp-net-core/
public class CustomValidationAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(
object value, ValidationContext validationContext)
{
// validationContext.GetService() ...
}
}
I am trying to override the RequiredAttribute in .net core and does not seem to work on asp.net core 1.1
Here is the test code
public class CustomRequiredAttribute : RequiredAttribute
{
public CustomRequiredAttribute():base()
{
}
public override string FormatErrorMessage(string name)
{
return base.FormatErrorMessage(name);
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
return base.IsValid(value, validationContext);
}
}
Once used on my model I am expecting the normal result like field is required as I have not customized it yet and just calling base methods.
This does not seem to work as expected and just bypasses the required on both the client and server side.
The purpose of this is to add a validation message pulled from a db to the ErrorMessage property.
Your problem is that the ValidationAttributeAdapterProvider, which is the default implementation of IValidationAttributeAdapterProvider, checks for specific types only. Thus, using custom implementations leads to missing "adapter providers", which leads to missing data attributes.
Solution: provide your own implementation of IValidationAttributeAdapterProvider, which can forward to the default implementation for non custom stuff...
public class CustomValidationAttributeAdapterProvider : IValidationAttributeAdapterProvider
{
private IValidationAttributeAdapterProvider innerProvider = new ValidationAttributeAdapterProvider();
public IAttributeAdapter GetAttributeAdapter(ValidationAttribute attribute, IStringLocalizer stringLocalizer)
{
if (attribute == null)
throw new ArgumentNullException(nameof(attribute));
var type = attribute.GetType();
if (type == typeof(CustomRequiredAttribute))
return new RequiredAttributeAdapter((RequiredAttribute)attribute, stringLocalizer);
return innerProvider.GetAttributeAdapter(attribute, stringLocalizer);
}
}
...and register it as a singleton.
services.AddSingleton<IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider>();
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));
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)