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; }
Related
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.
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 the request-response pattern in Asp.net (Webforms) the same as (or similar to) the 'model' being passed and returned in the MVC world. Basically, should there still be a need to use the request / response (i.e. creating seperate request and response classes) pattern if all the request/response classes are doing is containing some properties related to request or response and can actually be placed in the model, e.g.
LoginRequest.cs contains
string Username { get; set; }
LoginResponse.cs contains
string AuthenticationTicket { get; set; }
whereas LoginModel.cs would have
string Username { get; set; }
string AuthenticationTicket { get; set; }
Which is better in to use in the MVC world ?
Thanks.
One could argue that depending on the level you look at it, they are both the same since you have a request (postback with context parameters/model binded through model binder) and a response (changing the response object/rendering the view based on viewmodel) in every request, but on another level they are fundamentally different since webforms was conceived to be natural to windows forms developers and MVC is, IMO, a more mature and with better SoC architecture for web frameworks.
When dealing with MVC apps I usually go for one class that represents the view model and another one that make sense for the request so in a user details scenario I would have something like this:
public class LoginModel
{
public string Username {get;set;}
public string Password {get;set;}
}
public class LoginViewModel
{
public string Username {get;set;}
public string Fullname {get;set;}
public string LastLogin {get;set;}
}
public ActionResult Login(LoginModel model)
{
/* do whatever you need */
return new LoginViewModel { ... };
}
You still need the Request/Response DTOs when dealing with WCF Scenarios. The Request entity should be serializable and contains information that shd be discrete to the MVC View. On the other hand, the view model contains information bound to the relevant view. hope this help!
I'm designing a new website with ASP.NET MVC 4 (Beta), VS 11 (Beta), EF 5 (Beta), but this question suits for released versions of ASP.NET MVC 3, VS 2010, EF 4, too.
First step: I'm using Entity Framework Code First approach, for example, I've got following user model:
public class User
{
[Key]
public int UserId {get;set;}
public String LoginName { get; set; }
public String Password { get; set; }
}
Now, for registration I need another model, the registration model:
public class Registration
{
public String LoginName { get; set; }
public String Password { get; set; }
public String PasswordConfirm { get; set; }
}
This is where my problems begin: Where should I put my DataValidation Annotations? For example the password should be at minimum 10 characters long and the PasswordConfirmed must match Password and so on. Do I have to write this on every model which could do something with the password (I'm thinking of having a ChangePassword model, too)
Another thing is how to deal with the controller. When I display my Registration ViewModel and everything is fine, do I create a User model and assign the variables from Registration ViewModel to it?
Sometimes I've a lot of properties which go to database, but not shown to the user (foreign keys, calculated values etc.).
As thinkink on DRY, I don't want to repeat my self.
What is the best practice for this one?
To be clear: Annotations isn't a need. If there a better ways to validate, I will be glad, if you show them.
I can't say objectively which is 'the best practice', but here's how I see it.
If you're binding to the view model, verify the view model, so:
public class Registration
{
public String LoginName { get; set; }
[Required]
[StringLength(50, MinimumLength=10)]
public String Password { get; set; }
[Required]
[StringLength(50, MinimumLength=10)]
public String PasswordConfirm { get; set; }
}
You can either do the validation 'by hand' in the controller, check on the POST if the password and confirmation matches, if not add an entry to the ModelState (but that may cause code repetition and is a bit cumbersome) OR use nice IValidatableObject interface on the model:
public class Registration : IValidatableObject
{
public String LoginName { get; set; }
[Required]
[StringLength(50, MinimumLength=10)]
public String Password { get; set; }
[Required]
[StringLength(50, MinimumLength=10)]
public String PasswordConfirm { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext context)
{
if(Password != PasswordConfirm)
yield return new ValidationResult("Confirmation doesn't match", new[] {"PasswordConfirm"})
//etc.
}
}
Now with that, when you have your model bound after POST, the validation is done by simply calling ModelState.IsValid, and if it isn't valid, it returns the list of errors - including your custom errors.
Now of course you can put the DataAnnotations on the DB-model too as an additional measure, just 'in case' to avoid string truncation exceptions etc. if you somehow forgot and tried to push a longer string to the database anyway
As for the mapping, yes, after you have your model validated, at the end of the POST action you usually you map the properties from the model to either a new User instance (when adding to the DB) or to the existing one for update. You can use AutoMapper or write a naive mapper yourself using reflection - it's a relatively easy task, but it might be better to leave that as a stand-alone exercise, there is no point in reinventing the wheel.
You should create your entities only in domain layer. But when you need some DataValidation Annotations for your entity, you can use MvcExtensions for this. And if you have some composite or nested entities and you want to get them as a flatten objects, you should use automapper. This will be a best practice for you!
In my asp.net mvc application i have service layer, which operated with business object, pass its to repository layer and return to controller. No i can't decide where i need to validate object. First place - use data annotation validation with attribute of component model annotation in business objects class, for example:
[AcceptVerbs("POST")]
public ActionResult Edit(Source src)
{
if(!ModelState.IsValid){
return View("EditSource", src);
_sourceService.SaveSource(src);
return RedirectToAction("Index");
}
[MetadataType(typeof(Source.MetaSource))]
public class Source
{
private class MetaSource
{
[Required]
public string Name { set; get; }
[Required]
public string Url { set; get; }
}
public int? ID { set; get; }
public string Name { set; get; }
public string Url { set; get; }
public Source()
{
ID = null;
}
Second way - validate objects in service layer, by passing validation dictionary to service layer, for example:
[AcceptVerbs("POST")]
public ActionResult Edit(Source src)
{
if (!_sourceService.ValidateSource(src)){
return View("EditSource", src);
_sourceService.SaveSource(src);
return RedirectToAction("Index");
}
public bool ValidateSource(Source srcToValidate)
{
if (string.IsNullOrEmpty(srcToValidate.Name))
_validationDictionary.AddError("Name", "Name is required.");
else
if (srcToValidate.Name.Trim().Length == 0)
_validationDictionary.AddError("Name", "Name is required.");
if (string.IsNullOrEmpty(srcToValidate.Url))
_validationDictionary.AddError("Url", "Url is required.");
else
if (srcToValidate.Url.Trim().Length == 0)
_validationDictionary.AddError("Url", "Url is required.");
return _validationDictionary.IsValid;
}
I think of create client side validation, and add localization to validation errors, also i need create custom rules with calls to database, etc. What pros and cons of this 2 way, or maybe I need choose another way ?
The asp.net website offers guidance for three cases:
Validating with a service layer
Simple validation
Using IDataErrorInfo
These are probably worth reading before making any decisions.
Definitely worth reading up on the various options - choose whichever you think best suits your needs and style.
However, you will almost certainly end up creating a validation function on your service at some point to cope with business rules, so that may be the tie-breaker :-)
Heres a few extra links which may be useful too:
xVal Validation Framework
Data Annotations Model Binder Sample