ASP.NET MVC 6 Core - Validation issue / bug? - c#

I've migrated an MVC4 app to MVC6 (both .NET 4.6.1) and am hitting numerous errors with the inbuilt model validation.
I have a number of complex models that are posted to controllers, and unless I disable validation on each model under configure services, they throw unnecessary exceptions relating to properties that are irrelevant to validation, or just hang after postback without reaching the controller action.
I have added the following line to MVC Configuration for all my affected classes, but I've now got a model that requires validation, so turning it off will cause numerous code changes.
options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(TestModel)));
I tried this with a test app and can replicate the issue:
Test Controller:
public IActionResult Index()
{
return View();
}
[HttpPost]
public IActionResult Index(TestModel model)
{
return View();
}
Test Model (for example)
public class TestModel
{
[Required]
public string Name { get; set; }
[Required]
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
public int NameLength
{
get
{
return Name.Length;
}
}
}
Without the validation attributes, the code works fine, but is not validated (obviously).
But when this model is posted, a NullReference exception is thrown by the NameLength property, even though no code references it, the property is read only, and the property it depends on is required. This validation happens before control is returned to the controller.
I've tried disabling this functionality in MvcOptions, but it doesn't have any effect:
options.MaxValidationDepth = null;
options.AllowValidatingTopLevelNodes = false;
options.AllowShortCircuitingValidationWhenNoValidatorsArePresent = true;
I don't know if there's a setting I'm missing, but I would expect the default functionality to ignore properties without validation attributes, or am I doing something wrong?.
Thanks in advance for your help.
Further to #Henks suggestion, I've added the ValidateNever attribute to the readonly properties of one class I was having problems with, which has worked, so the postback reaches the controller now, but its still calling the properties, it just seems to ignore the result:
[ValidateNever]
public Competition PrimaryCompetition
{
get
{
return GetCompetition(true);
}
}
This still triggers a null reference exception because it relies on another property that is [Required] but is not validated first.
I'm beginning to think this is a bug rather than an error on my part.

Why this happens
I haven't seen this issue with simple types (like in some of the example code you posted), but we just had a similar issue with complex types.
From looking at the source code, this has to do with how the complex model binder works. It steps through every public property getter that is a complex type (e.g. a class) when posted regardless of whether the property was used at all. I think this may be an intentional choice by Microsoft because it is possible that the underlying properties of a complex type could be settable.
For example if your Competition class and PrimaryCompetition property on another class (called Test here) looked like this:
public class Competition
{
public string Name { get; set; }
public List<string> Roster { get; set; } = new List<string>();
}
public class Test
{
public Competition PrimaryCompetition
{
get
{
return AllCompetitions.First();
}
}
public List<Competition> AllCompetitions { get; set; } = new List<Competition>();
}
Underlying properties of PrimaryCompetition can be modified even though it has no setter:
var competition = new Competition {
Name = "Soccer"
};
competition.Roster.Add("Sara");
var test = new Test();
// This code outputs "Sara"
test.AllCompetitions.Add(competition);
Console.WriteLine(test.PrimaryCompetition.Roster[0]);
// This code outputs "Amanda"
test.PrimaryCompetition.Roster[0] = "Amanda";
Console.WriteLine(test.PrimaryCompetition.Roster[0]);
Possible solutions
Make the property a method instead:
public Competition PrimaryCompetition() => GetCompetition(true);
Make the property internal instead of public:
internal Competition PrimaryCompetition
{
get
{
return GetCompetition(true);
}
}
Add the ValidateNever and BindNever attributes to the property:
[BindNever]
[ValidateNever]
public Competition PrimaryCompetition
{
get
{
return GetCompetition(true);
}
}
We decided to go with option 1 since in Microsoft's best practices they recommend not throwing exceptions from getters Property Design.
Property getters should be simple operations and should not have any preconditions. If a getter can throw an exception, it should probably be redesigned to be a method.

Related

Validation in a layered application ASP.NET Core

I'm designing a layered web application with an MVC, Service and Repository layer, however I'm having trouble knowing where to put validation logic that allows me to take advantage of .NET Core built in form validation (eg ModelStateDictionary), while following the DRY principle.
The first and most obvious approach is to use a ViewModel that has the appropriate data annotations:
public class VendorViewModel
{
public long Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Phone { get; set; }
[Required]
public string Email { get; set; }
[Required]
public string Address { get; set; }
public DateTime? VerifiedAt { get; set; }
}
Then my controller action would look like this
public async Task<IActionResult> Create([FromForm] VendorViewModel model)
{
await AuthorizePolicyAsync(AuthorizationPolicyTypes.Vendor.Create);
if (!ModelState.IsValid) //Validation problems, so re-display the form.
return View(model);
await _vendorservice.CreateVendorAsync(model.Name,model.Phone,model.Email,model.Address,null);
return RedirectToAction(nameof(Index));
}
This works fine, however there are a couple problems:
This only supports basic validation such as checking character length, etc. In the particular example above, I want to validate that model.Address is a valid address according to google maps and also contains a city that the application is aware of, which means this kind of validation should be moved to the service layer to keep the Controller "thin".
The service layer is now missing any validation logic, and assumes that it is always being passed valid data. This seems wrong to me since it seems like the service layer should be responsible for keeping the system in a consistent valid state. A solution to this would be to also add validation logic to the service layer, but that seems to violate the DRY principle in my opinion.
The second approach would be to move all of the validation logic to the service layer and move all my data annotations to the actual domain object Vendor. This way each operation could validate the model based on the data annotations, and also apply any more complex logic such as validating the address with google maps as previously mentioned. However, I'm not sure how I can validate an annotated object in the same manner that a MVC Controller does and pass back a dictionary to the controller. This functionality seems to be specific to MVC and would introduce a dependency on MVC in my service layer which is undesirable.
Is there anyway I can elegantly move validation logic to the service layer while
taking advantage of data annotations and MVC's built in ModelStateDictionary? How do I get the list of errors back to the controller? Do I throw an exception and catch it in the controller if any validation errors occur?
I have seen several questions asking a similar question, but I'm not satisfied with any of the answers. Other answers seem to involve writing validation logic manually and not taking advantage of data annotations. Is this what I should resort to?
You can create your own custom validation attributes in addition to what are available out of the box such as Required,Range,StringLength,etc.
I will provide an example below :
public class ValidateAddressAttribute : Attribute, IModelValidator
{
public bool IsRequired => true;
public string ErrorMessage { get; set; } = "Address is not valid";
public IEnumerable<ModelValidationResult>Validate(ModelValidationContext context)
{
List<ModelValidationResult> validationResults = new List<ModelValidationResult>();
string address = context.Model as string;
if(!IsAddressValid(address))
{
validationResults.Add(new ModelValidationResult("", ErrorMessage));
}
return validationResults;
}
private bool IsAddressValid(string address)
{
bool isAddressValid;
//set isAddressValid to true or false based on your validation logic
return isAddressValid;
}
}
You can now apply this attribute on your address property as follows :
[Required]
[ValidateAddress(ErrorMessage="Invalid Address")]
public string Address { get; set; }

Dynamic Validation in Web API

I am looking to validate a particular request depending on values in a database. It's a complex scenario, but I will try to simplify it in an example.
Say I have the following model:
public class CustomerModel
{
public int AgencyId { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
When a POST request comes in, I need to make a call to get certain requirements for the AgencyId being passed.
var requirements = _repository.GetRequirementsForAgency(model.AgencyId);
The information I would get back from the database would tell me which properties are required, which may be different for each agency. For instance, one agency might require Name and Age where as another one might only require Name. The requirements object would look something like this:
public class Requirement
{
public string PropertyName { get; set; }
public bool IsRequired { get; set; }
}
So, my question is what would be the best way to validate this model before it gets submitted to the database? Ideally, I would like to give the Agency the ability to change these requirements, therefore, I would like to avoid hard coding validation if possible.
My first thought was to call a list of requirements and then do a foreach over each requirement searching by PropertyName and then checking to see if there was a value or not, but I wasn't sure if this was the best way.
I then looked into Data Annotations, but did not find a way to add attributes at run time.
You can use Fluent Validation library and implement custom validator
public class CustomerModelValidator : AbstractValidator<CustomerModel>
{
private readonly IRepository _repository;
public RegisterModelValidator(IRepository repository)
{
this._repository= repository;
RuleFor(x => x.AgencyId).GreaterThan(0).WithMessage("Invalid AgencyId");
RuleFor(x => x.Age).GreaterThan(0).WithMessage("Invalid Age");
Custom(c =>
{
var requirements = _repository.GetRequirementsForAgency(model.AgencyId);
\\validate each property according to requirements object.
\\if (Validation fails for some property)
return new ValidationFailure("property", "message");
\\else
return null;
});
}
}
If you use dependency injection in your project (which i strongly advice), you will have to inject relevant IRepository into an attribute. Otherwise you can just create/use a specific repository in your attribute.
A really nice thing is when you properly register your validator you will be able to validate you model with default if (ModelState.IsValid) check

How do I prevent hidden fields from interfering with server side validation in MVC?

I have a partial view that displays a number of inputs based on a view model. In some situations, some of those inputs are not rendered by the partial view, but are still decorated with [Required] attributes in the view model. As a result, when the form is posted back to my controller, ModelState.IsValid returns false. Is there a way to bypass this?
You can use Foolproof to validate your fields conditionally. This way, they'll be required only when they need to, as you can see in the example of the link.
private class Person
{
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
public bool Married { get; set; }
[RequiredIfTrue("Married")]
public string MaidenName { get; set; }
}
In this example, MaidenName will only change your ModelState.IsValid to false if Married == true
I'd recommend separating your validation from your base model.
public class MyModel
{
public string MyString { get; set; }
public string MyHiddenField { get; set; }
}
public interface IMyModel_ValidateMystringOnly
{
[Required]
string MyString { get; set; }
}
[MetadataType(TypeOf(IMyModel_ValidateMystringOnly))]
public class MyModel_ValidateMystringOnly : MyModel
This allows you to create any number of validation types, and only validate what you want when you want.
public ActionResult ShowMyModel()
{
var model = new MyModel(); // or Respository.GetMyModel() whatever..
View(model);
}
public ActionResult ValidateModel(MyModel_ValidateMystringOnly model)
{
if (ModelState.IsValid)
{
// Hey Validation!
}
// MyModel_ValidateMyStringOnly is a MyModel
// so it can be passed to the same view!
return View("ShowMyModel", model);
}
This is just an example, but should be clear on how-to reuse the same model with or without validation.
I have used method at times where the form changes slightly based on specific DropDown or Radio Button selections.
Inside your Action method before you check ModelState.IsValid you can do something like ModelState.Remove("Object.PropertyName")
Note: The property name should be the same as the ID rendered to the client. Use a "." for any underscores.
If isSomeCondition Then
ModelState.Remove("Property1")
ModelState.Remove("Property2")
End If
If ModelState.IsValid() Then
...
End If
You should always separate your VIEW model from your DOMAIN model. There is a very good reason for this and it has to do with security. When you use your domain models as your view models you are vulnerable to an overposting and/or underposting attacks. You can read more about it on these pages:
http://odetocode.com/blogs/scott/archive/2012/03/12/complete-guide-to-mass-assignment-in-asp-net-mvc.aspx
http://blogs.msdn.com/b/rickandy/archive/2012/03/23/securing-your-asp-net-mvc-4-app-and-the-new-allowanonymous-attribute.aspx
https://hendryluk.wordpress.com/tag/asp-net-mvc/
In short if you don't need a field then it should not be in your view model. You should convert - map your view models to domain models. Although it can be tedious it makes your application much more secure. There are libraries you can use to help you with mapping such as Automapper.
EDIT: Since my original answer, I have come to a conclusion that the easiest way to deal with this type of scenario is to have your view model implement IValidatableObject interface and then write your validation logic inside the Validate method. It does not give you client side validation but it is the most effective and clean way to accomplish custom/scenario based validation without writing your own custom filters.
You can read more about it here: http://weblogs.asp.net/scottgu/class-level-model-validation-with-ef-code-first-and-asp-net-mvc-3

Is this an MVC anti-pattern?

I’m very new to any kind of .NET web development (thus far I’ve worked primarily with Winforms and services.) I’ve started to work on an existing MVC3 project with two other developers. I’m conceptually familiar with MVC, and am trying to catch up on how it’s used in this project.
We have an AccountDto class to represent Accounts. There is a Response class that is inherited by another class for each Entity, i.e. AccountResponse:
public class Response
{
[DataMember]
public bool IsSuccess{get;set;}
[DataMember]
public string DisplayMessage { get; set; }
[DataMember]
public string DetailedMessage { get; set; }
[DataMember]
public ErrorType ErrorType { get; set; }
public Response(){
this.IsSuccess=true;
this.ErrorType = ErrorType.None;
}
}
public partial class AccountResponse : Response
{
[DataMember]
public IList<AccountDto> AccountList { get; set; }
}
There’s an AccountService which will return an AccountResponse to the Controller, with a list of the AccountDto object:
public AccountResponse GetAccountByAccountId(Int64 accountId)
{
_logger.Info("Executing GetAccountByAccountId()");
AccountResponse response = new AccountResponse();
try
{
Account item = AccountPersistence.GetAccountByAccountId(accountId);
AccountDto dto = Mapper.Map<AccountDto>(item);
response.AccountList = new List<AccountDto>() { dto };
response.IsSuccess = true;
}
catch (Exception ex)
{
response.IsSuccess = false;
response.ErrorType = ErrorType.GeneralFault;
response.DetailedMessage = ex.ExceptionMessageBuilder();
response.DisplayMessage = "System Failure: Failed to get Account by AccountId";
_logger.Error(ex);
}
return response;
}
I was told the Response thing is implemented to be able to handle success/failure messages. So in a controller, there’s code like the following (doesn’t happen to do anything special if a failure):
public ActionResult ToBeCalled(int id)
{
AccountDto dto = null;
var response = _accountService.GetAccountByAccountId(Convert.ToInt64(id));
if (response.IsSuccess)
{
dto = response.AccountList[0];
return View(dto);
}
return View(dto);
}
This made sense to me though I wasn’t sure where the success/error messages were going to be utilized. However, they now want to switch from using the DTO in views to using the Response, so success/failure will have to be handled in the views:
public ActionResult ToBeCalled(int id)
{
var response = _accountService.GetAccountByAccountId(Convert.ToInt64(id));
return View(response);
}
This seems off to me – instead of coding against a DTO as the model, I have to do something like the following for each page:
#{
if (Model.IsSuccess)
{
var account = Model.AccountList.FirstOrDefault();
if (account != null)
{
#Html.HiddenFor(x => account.AccountNumber)
}
}
The controllers’ ActionResult / HttpPost methods then have to also parse the DTO from these Response objects. This seems like an anti-pattern to me; are approaches like this normal?
Apologies if this is too lengthy, please migrate if it belongs on Code Review or another site.
I agree with you that this would be an anti-pattern. The View is supposed to be quite ignorant, especially of logic like this.
I can see why this would be tempting, if the difference between success and failure is a minor part of the UI, but imagine if that were to change. A view has little ability (without unnecessary nesting of partials) to switch to an entirely different view. It has no ability to issue a redirect or other error codes. In the event that you decide to change your UI, you may have to go back and rewrite your Controller yet again.
If the reasoning behind moving the logic to the view was to remove the response.IsSuccess logic from the Controller (and to be honest, that seems fine to me; it's pretty much the same as the classic Model.IsValid), you could consider another approach: refactor your Response class to inherit from ActionResult. Then you could move that logic into the ExecuteResult() method and it would be separate from your Controller.
Just use the coalesce operator and you can get rid of a whole lot of cruft (like that strange Response base class (which should be marked abstract if it continues to exist)) and avoid null-checking.
public ActionResult ToBeCalled(int id)
{
var response = _accountService.GetAccountByAccountId(id) ??
new AccountResponse();
return View(response);
}
Better yet, migrate that logic into your service class so that it guarantees return of an object (it doesn't necessarily make sense for a repository to do this when there's no backing entity, but it does for a service).
Either way, you don't need to include unsightly null-checking or if/else logic on your view. Move as much logic to places that you can test it as you can and you'll be happier.

Disable Required validation attribute under certain circumstances

I was wondering if it is possible to disable the Required validation attribute in certain controller actions. I am wondering this because on one of my edit forms I do not require the user to enter values for fields that they have already specified previously. However I then implement logic that when they enter a value it uses some special logic to update the model, such as hashing a value etc.
Any sugestions on how to get around this problem?
EDIT:
And yes client validation is a problem here to, as it will not allow them to submit the form without entering a value.
This problem can be easily solved by using view models. View models are classes that are specifically tailored to the needs of a given view. So for example in your case you could have the following view models:
public UpdateViewView
{
[Required]
public string Id { get; set; }
... some other properties
}
public class InsertViewModel
{
public string Id { get; set; }
... some other properties
}
which will be used in their corresponding controller actions:
[HttpPost]
public ActionResult Update(UpdateViewView model)
{
...
}
[HttpPost]
public ActionResult Insert(InsertViewModel model)
{
...
}
If you just want to disable validation for a single field in client side then you can override the validation attributes as follows:
#Html.TextBoxFor(model => model.SomeValue,
new Dictionary<string, object> { { "data-val", false }})
I know this question has been answered a long time ago and the accepted answer will actually do the work. But there's one thing that bothers me: having to copy 2 models only to disable a validation.
Here's my suggestion:
public class InsertModel
{
[Display(...)]
public virtual string ID { get; set; }
...Other properties
}
public class UpdateModel : InsertModel
{
[Required]
public override string ID
{
get { return base.ID; }
set { base.ID = value; }
}
}
This way, you don't have to bother with client/server side validations, the framework will behave the way it's supposed to. Also, if you define a [Display] attribute on the base class, you don't have to redefine it in your UpdateModel.
And you can still use these classes the same way:
[HttpPost]
public ActionResult Update(UpdateModel model)
{
...
}
[HttpPost]
public ActionResult Insert(InsertModel model)
{
...
}
You can remove all validation off a property with the following in your controller action.
ModelState.Remove<ViewModel>(x => x.SomeProperty);
#Ian's comment regarding MVC5
The following is still possible
ModelState.Remove("PropertyNameInModel");
Bit annoying that you lose the static typing with the updated API. You could achieve something similar to the old way by creating an instance of HTML helper and using NameExtensions Methods.
Client side
For disabling validation for a form, multiple options based on my research is given below. One of them would would hopefully work for you.
Option 1
I prefer this, and this works perfectly for me.
(function ($) {
$.fn.turnOffValidation = function (form) {
var settings = form.validate().settings;
for (var ruleIndex in settings.rules) {
delete settings.rules[ruleIndex];
}
};
})(jQuery);
and invoking it like
$('#btn').click(function () {
$(this).turnOffValidation(jQuery('#myForm'));
});
Option 2
$('your selector here').data('val', false);
$("form").removeData("validator");
$("form").removeData("unobtrusiveValidation");
$.validator.unobtrusive.parse("form");
Option 3
var settings = $.data($('#myForm').get(0), 'validator').settings;
settings.ignore = ".input";
Option 4
$("form").get(0).submit();
jQuery('#createForm').unbind('submit').submit();
Option 5
$('input selector').each(function () {
$(this).rules('remove');
});
Server Side
Create an attribute and mark your action method with that attribute. Customize this to adapt to your specific needs.
[AttributeUsage(AttributeTargets.All)]
public class IgnoreValidationAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var modelState = filterContext.Controller.ViewData.ModelState;
foreach (var modelValue in modelState.Values)
{
modelValue.Errors.Clear();
}
}
}
A better approach has been described here Enable/Disable mvc server side validation dynamically
Personally I would tend to use the approach Darin Dimitrov showed in his solution.
This frees you up to be able to use the data annotation approach with validation AND have separate data attributes on each ViewModel corresponding to the task at hand.
To minimize the amount of work for copying between model and viewmodel you should look at AutoMapper or ValueInjecter. Both have their individual strong points, so check them both.
Another possible approach for you would be to derive your viewmodel or model from IValidatableObject. This gives you the option to implement a function Validate.
In validate you can return either a List of ValidationResult elements or issue a yield return for each problem you detect in validation.
The ValidationResult consists of an error message and a list of strings with the fieldnames. The error messages will be shown at a location near the input field(s).
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if( NumberField < 0 )
{
yield return new ValidationResult(
"Don't input a negative number",
new[] { "NumberField" } );
}
if( NumberField > 100 )
{
yield return new ValidationResult(
"Don't input a number > 100",
new[] { "NumberField" } );
}
yield break;
}
The cleanest way here I believe is going to disable your client side validation and on the server side you will need to:
ModelState["SomeField"].Errors.Clear (in your controller or create an action filter to remove errors before the controller code is executed)
Add ModelState.AddModelError from your controller code when you detect a violation of your detected issues.
Seems even a custom view model here wont solve the problem because the number of those 'pre answered' fields could vary. If they dont then a custom view model may indeed be the easiest way, but using the above technique you can get around your validations issues.
this was someone else's answer in the comments...but it should be a real answer:
$("#SomeValue").removeAttr("data-val-required")
tested on MVC 6 with a field having the [Required] attribute
answer stolen from https://stackoverflow.com/users/73382/rob above
I was having this problem when I creating a Edit View for my Model and I want to update just one field.
My solution for a simplest way is put the two field using :
<%: Html.HiddenFor(model => model.ID) %>
<%: Html.HiddenFor(model => model.Name)%>
<%: Html.HiddenFor(model => model.Content)%>
<%: Html.TextAreaFor(model => model.Comments)%>
Comments is the field that I only update in Edit View, that not have Required Attribute.
ASP.NET MVC 3 Entity
AFAIK you can not remove attribute at runtime, but only change their values (ie: readonly true/false) look here for something similar .
As another way of doing what you want without messing with attributes I will go with a ViewModel for your specific action so you can insert all the logic without breaking the logic needed by other controllers.
If you try to obtain some sort of wizard (a multi steps form) you can instead serialize the already compiled fields and with TempData bring them along your steps. (for help in serialize deserialize you can use MVC futures)
What #Darin said is what I would recommend as well. However I would add to it (and in response to one of the comments) that you can in fact also use this method for primitive types like bit, bool, even structures like Guid by simply making them nullable. Once you do this, the Required attribute functions as expected.
public UpdateViewView
{
[Required]
public Guid? Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public int? Age { get; set; }
[Required]
public bool? IsApproved { get; set; }
//... some other properties
}
As of MVC 5 this can be easily achieved by adding this in your global.asax.
DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
I was looking for a solution where I can use the same model for an insert and update in web api. In my situation is this always a body content. The [Requiered] attributes must be skipped if it is an update method.
In my solution, you place an attribute [IgnoreRequiredValidations] above the method. This is as follows:
public class WebServiceController : ApiController
{
[HttpPost]
public IHttpActionResult Insert(SameModel model)
{
...
}
[HttpPut]
[IgnoreRequiredValidations]
public IHttpActionResult Update(SameModel model)
{
...
}
...
What else needs to be done?
An own BodyModelValidator must becreated and added at the startup.
This is in the HttpConfiguration and looks like this: config.Services.Replace(typeof(IBodyModelValidator), new IgnoreRequiredOrDefaultBodyModelValidator());
using Owin;
using your_namespace.Web.Http.Validation;
[assembly: OwinStartup(typeof(your_namespace.Startup))]
namespace your_namespace
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
Configuration(app, new HttpConfiguration());
}
public void Configuration(IAppBuilder app, HttpConfiguration config)
{
config.Services.Replace(typeof(IBodyModelValidator), new IgnoreRequiredOrDefaultBodyModelValidator());
}
...
My own BodyModelValidator is derived from the DefaultBodyModelValidator. And i figure out that i had to override the 'ShallowValidate' methode. In this override i filter the requierd model validators.
And now the IgnoreRequiredOrDefaultBodyModelValidator class and the IgnoreRequiredValidations attributte class:
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Web.Http.Controllers;
using System.Web.Http.Metadata;
using System.Web.Http.Validation;
namespace your_namespace.Web.Http.Validation
{
public class IgnoreRequiredOrDefaultBodyModelValidator : DefaultBodyModelValidator
{
private static ConcurrentDictionary<HttpActionBinding, bool> _ignoreRequiredValidationByActionBindingCache;
static IgnoreRequiredOrDefaultBodyModelValidator()
{
_ignoreRequiredValidationByActionBindingCache = new ConcurrentDictionary<HttpActionBinding, bool>();
}
protected override bool ShallowValidate(ModelMetadata metadata, BodyModelValidatorContext validationContext, object container, IEnumerable<ModelValidator> validators)
{
var actionContext = validationContext.ActionContext;
if (RequiredValidationsIsIgnored(actionContext.ActionDescriptor.ActionBinding))
validators = validators.Where(v => !v.IsRequired);
return base.ShallowValidate(metadata, validationContext, container, validators);
}
#region RequiredValidationsIsIgnored
private bool RequiredValidationsIsIgnored(HttpActionBinding actionBinding)
{
bool ignore;
if (!_ignoreRequiredValidationByActionBindingCache.TryGetValue(actionBinding, out ignore))
_ignoreRequiredValidationByActionBindingCache.TryAdd(actionBinding, ignore = RequiredValidationsIsIgnored(actionBinding.ActionDescriptor as ReflectedHttpActionDescriptor));
return ignore;
}
private bool RequiredValidationsIsIgnored(ReflectedHttpActionDescriptor actionDescriptor)
{
if (actionDescriptor == null)
return false;
return actionDescriptor.MethodInfo.GetCustomAttribute<IgnoreRequiredValidationsAttribute>(false) != null;
}
#endregion
}
[AttributeUsage(AttributeTargets.Method, Inherited = true)]
public class IgnoreRequiredValidationsAttribute : Attribute
{
}
}
Sources:
Using string debug = new StackTrace().ToString() to find out who is
handeling the model validation.
https://learn.microsoft.com/en-us/aspnet/web-api/overview/advanced/configuring-aspnet-web-api to know how set my own validator.
https://github.com/ASP-NET-MVC/aspnetwebstack/blob/master/src/System.Web.Http/Validation/DefaultBodyModelValidator.cs to figure out what this validator is doing.
https://github.com/Microsoft/referencesource/blob/master/System.Web/ModelBinding/DataAnnotationsModelValidator.cs to figure out why the IsRequired property is set on true. Here you can also find the original Attribute as a property.
If you don't want to use another ViewModel you can disable client validations on the view and also remove the validations on the server for those properties you want to ignore. Please check this answer for a deeper explanation https://stackoverflow.com/a/15248790/1128216
In my case the same Model was used in many pages for re-usability purposes. So what i did was i have created a custom attribute which checks for exclusions
public class ValidateAttribute : ActionFilterAttribute
{
public string Exclude { get; set; }
public string Base { get; set; }
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!string.IsNullOrWhiteSpace(this.Exclude))
{
string[] excludes = this.Exclude.Split(',');
foreach (var exclude in excludes)
{
actionContext.ModelState.Remove(Base + "." + exclude);
}
}
if (actionContext.ModelState.IsValid == false)
{
var mediaType = new MediaTypeHeaderValue("application/json");
var error = actionContext.ModelState;
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.OK, error.Keys, mediaType);
}
}
}
and in your controller
[Validate(Base= "person",Exclude ="Age,Name")]
public async Task<IHttpActionResult> Save(User person)
{
//do something
}
Say the Model is
public class User
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Range(18,99)]
public string Age { get; set; }
[MaxLength(250)]
public string Address { get; set; }
}
This one worked for me:
$('#fieldId').rules('remove', 'required');
Yes it is possible to disable Required Attribute. Create your own custom class attribute (sample code called ChangeableRequired) to extent from RequiredAtribute and add a Disabled Property and override the IsValid method to check if it is disbaled. Use reflection to set the disabled poperty, like so:
Custom Attribute:
namespace System.ComponentModel.DataAnnotations
{
public class ChangeableRequired : RequiredAttribute
{
public bool Disabled { get; set; }
public override bool IsValid(object value)
{
if (Disabled)
{
return true;
}
return base.IsValid(value);
}
}
}
Update you property to use your new custom Attribute:
class Forex
{
....
[ChangeableRequired]
public decimal? ExchangeRate {get;set;}
....
}
where you need to disable the property use reflection to set it:
Forex forex = new Forex();
// Get Property Descriptor from instance with the Property name
PropertyDescriptor descriptor = TypeDescriptor.GetProperties(forex.GetType())["ExchangeRate"];
//Search for Attribute
ChangeableRequired attrib = (ChangeableRequired)descriptor.Attributes[typeof(ChangeableRequired)];
// Set Attribute to true to Disable
attrib.Disabled = true;
This feels nice and clean?
NB: The validation above will be disabled while your object instance is alive\active...

Categories