How can we validate data based on action being taken over particular entity? What other more advanced alternatives are there to data annotations model validation? Possibly pluggable into Asp.net MVC and WebAPI, so validation is still being done automatically.
Example
Suppose a user join form of a web application.
public class User
{
// required when providing user as input
// not provided when creating new instance
public int Id { get; set; }
// required when user joins and of specific format AND IS UNIQUE based on data store users
// optional when providing user as input
public string Email { get; set; }
...
}
Maybe object inheritance could help but as much as I think of it, inheritance would only be as a hack. Base class would hardly have any properties and we could end up with several extremely similar (properties) classes but with different annotations just to use data annotations. And that's not good.
Desired implementation
I was thinking of validation based on action being taken over particular entity. So we'd be able to define something like:
public class User
{
[Required(Action = ValidationAction.Provide)] // or whatever action we'd define
public int Id { get; set; }
[Required(Action = ValidationAction.Create)]
[IsUnique(Action = ValidationAction.Create)] // custom DataAnnotations validator
[EmailAddress]
public string Email { get; set; }
...
}
Asp.net MVC and WebAPI controller actions would require some sort of attribute to provide information what's being done with particular entities a parameters
[HttpPost]
[ValidateForAction("user", ValidationAction.Create)]
[ValidateForAction("user.InvitedBy", ValidationAction.Provide)]
public ActionResult Join(User user)
{
...
}
or set it uniformly for all parameters (and their object entities in subtrees)
[HttpPost]
[ValidateForAction(ValidationAction.Create)]
public ActionResult Join(User user)
{
...
}
When ValidateForActionAttribute isn't present on controller action validation should only check validation action independent annotations (like the EmailAddressAttribute set above on my entity example).
A similar example could be Stackoverflow scenario of adding an answer where posted answer details would be validated by create action, and related question entity (a property inside an answer) would be validated per provide action because we'd mainly just need its Id.
Is there any such validation library? Anybody done something similar?
How would you go about doing such validation?
This sounds like its similar to a requiredif validator where the validation is dependent upon another property. However, model validation will not work here since the model is "supposed" to be independent of views or controllers.
Assume though that you have a view model associated with individual actions on a controller, then the view model could use data annotations consistent with the requirements of the view. See ASP.Net MVC and MVVM for more detail on the MVVM pattern.
One last comment with regard to the Id. Not sure a Required attribute will work since the default for an int is a valid value. Perhaps a regex? ([1-9]|[0-9]{2,10})
public class RegistrationController
[HttpPost]
public ActionResult Provide(UserProvideViewModel user)
{
...
}
[HttpPost]
public ActionResult Join(UserJoinViewModel user)
{
...
}
}
[MetadataType(typeof(UserProvideViewModel_Validation))]
public partial class UserProvideViewModel : User
{
// properties unique to the view model
}
public class UserProvideViewModel_Validation
{
[RegularExpression(#"^([1-9]|\d{2,10})$")]
public Id { get; set; }
}
[MetadataType(typeof(UserJoinViewModel_Validation))]
public partial class UserJoinViewModel : User
{
// properties unique to the view model
}
public class UserJoinViewModel_Validation
{
[Required]
[EmailAddress]
public Email { get; set; }
}
Related
I have a model described as below:
public class Projet
{
public int ProjetId { get; set; }
public int SeqNumber{ get; set; }
public string Code{ get; set; }
public string Description { get; set; }
public bool Std{ get; set; }
public bool Archive { get; set; }
}
I have a create view to let a user create a new project, and I must let the SeqNumber field free so the user can input whatever number he wants.
However, I must return an error if the SeqNumber choosen is already taken.
I'm not sure how to implement this. Should I do some validation in my ProjetsController/Create action ? From what I can find, validating stuff in the controller is a bad practise, but I can't think of where to implement validation when it depends on other members of the same class.
Would coding a custom validator for MVC considered a good practise?
Thanks !
You can use this in your http post action method which handles the form submit
[HttpPost]
public ActionResult Create(Projet model)
{
var exist = db.Projects.Any(s=>s.SeqNumber===model.SeqNumber
&& s.ProjectId!=model.ProjectId);
if(exist)
{
ModelState.AddModelError(string.empty,"Sequence number is already in use");
return View(model);
}
// to do : Continue with your save
}
Now when user submits the form, if the sequence number is being used for any other project, it will throw a validation message. Make sure you are using the Validation summary helper in your view to render the validation error message.
#model Project
#Html.ValidationSummary(false)
#using(Html.BeginForm())
{
<!-- your existing code goes here-->
}
Now to give a nice user experience, you may take advantage of the Remote validation feature. What it does is, when user takes the focus out from the input, it makes an ajax call to server to check whether your SequenceNumber exist in db. Based on the result, the validation messages will be shown to the user.
To do this, decorate your property with the Remote attribute.
[Required]
[System.Web.Mvc.Remote("CheckSeqNumber", "Project",
ErrorMessage = "SeqNumber is already used!")]
public int SeqNumber { get; set; }
Now make sure you have an action method called CheckSeqNumber which returns either true or false as a json response.
public ActionResult CheckTagName(int SeqNumber)
{
var exist= !db.Projects.Any(g => g.SeqNumber == SeqNumber);
return Json(exist,JsonRequestBehavior.AllowGet);
}
Also make sure you have the relevant javascript files loaded to do this unobtrusive validation.
<script src="~/Scripts/jquery.validate.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.js"></script>
Another option is to write a custom validation attribute which does this check for you (if you do not like that check in the action method). Here is an SO post explaining how to do it.
I have a model ModelA with a member toBeRemoteChecked and a model MapToA with a member valueToMap. Whenever I create an instance of ModelA, I also need an instance of MapToA, so I have a model CreateModelA which includes a member modelA and a member valueToMap. When the form is submitted, I add the modelA to the database table ModelA and create and add an instance to MapToA which consists of an id of modelA and the valueToMap. In Terms of code
public class ModelA
{
[Key]
public int ID { get; set; }
[Required, Remote("isValid", "MyController", ErrorMessage = "not valid")]
public string toBeRemoteChecked { get; set; }
}
public class MapToA
{
[Key]
public int Map_ID { get; set; }
[Required]
public int modelAID { get; set; }
[Required]
public int valueToMap { get; set; }
}
public class CreateModelA
{
public ModelA modelA { get; set; };
public int valueToMap { get; set; };
}
When I edit an instance of ModelA, values in MapToA don't matter (and in most cases there's more than one instance of mapToA with the same modelA id), but the remote validation of toBeRemoteChecked remains important.
My Problem: binding for the validation method:
public ActionResult isValid(string toBeRemoteChecked) { ... }
If I leave it as it is, it is working when editing a ModelA, but not when I'm creating a ModelA via CreateModelA (I always get null value in toBeRemoteChecked). When I use the BindPrefix
public ActionResult isValid([Bind(Prefix = "modelA.toBeRemoteChecked")] string toBeRemoteChecked) { ... }
it is working when I create a ModelA, but not when I'm editing it.
When I try to change the "name" in the Create.cshtml by adding a ... #Name = "toBeRemoteChecked" ... (instead of the modelA.toBeRemoteChecked that's created by the HTML helper) in the htmlAttributes of the #Html.TextBoxFor, then validation is working, but the binding of the value to the table get's lost and I get the error when the values are saved to the database (null value).
So, how do I achieve the different binding for creating and editing?
So far, my workaround is to make ModelA and CreateModelA : IValidatableObject and check the member toBeRemoteChecked in my public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) method. But that one displays the error messages on top of the form and not at the place of the TextFor box.
So: best solution: how to do the binding that the remote validation works in both cases?
Second best: how to display the error messages of IValidatableObject near the object where it belongs to (and get the error messages right at hand, not after submitting)
Different ideas or solutions: welcome.
Thanks.
An interesting issue, and similar to this question, which as an result I reported a issue at Codeplex, but it has not been resolved yet. The link includes a suggested modification to the jquery.validate.js file which would solve this (it strips the prefix) but that means you would need to maintain it whenever you update the script so not really desirable.
One option would be to change CreateModelA to inherit from ModelA and just add the int valueToMap property so that you never have a prefix - your always using #Html.TextBoxFor(m => m.toBeRemoteChecked) instead of #Html.TextBoxFor(m => m.modelA.toBeRemoteChecked)
Also, [Remote] is client side only validation, which means you still need to perform the validation in the server when you post. So you could just accept that you don't have client side validation for the property, and instead add a ModelState error in the POST methods(s) for the property and return the view so that its displayed in the associated ValidationMessageFor() element
Side note: The fact your model has a [Key] attribute suggests this is a data model, not a view model, and [Remote] is a view specific attribute. You should be using view models, especially when editing data. (refer What is ViewModel in MVC?)
I found a solution without inheritance (and without view models) that solves my binding problem with just little change to my code.
There's two ways of binding for remote validation, you can either just pass the member that has to be remote checked
public ActionResult isValid(string toBeRemoteChecked) { ... }
or you can pass the instance of the class of that member.
public ActionResult isValid(ModelA modelA) { ... }
Inside the second variant, of course, you have to replace toBeRemoteChecked with modelA.toBeRemoteChecked. On this second version the binding works in both cases - when editing and also when creating my instance of ModelA in the context above. In order to make the binding work, it's crucial that the parameter name of the remote validation method matches the member name in the CreateModelA, i.e. modelA in my case.
In case you have a very complex model, you can just initialize the parameter modelA with the members you want to use by using bind/include, i.e. in my case I'd use
public ActionResult isValid([Bind(Include = "toBeRemoteChecked")] ModelA modelA) { ... }
By default (without Include), all other members will remain null or have a default value - so you need to use Include only if you need other members for validation as well - in my case, I would have the same when omitting the Include)
I've been googling like crazy without result, maybe I'm just missing the correct keywords.
I have a class with a custom validation attribute on a property. I want to "clean" the value before validation, removing the white-space and special characters that we accept but that we don't want to save to the database.
public class PersonViewModel
{
[SocialSecurityNumberLuhn(ErrorMessage = "Incorrect social security number")]
public string SocialSecurityNumber { get; set; }
}
I would want to do something like this:
public class PersonViewModel
{
[CleanWhiteSpace]
[SocialSecurityNumberLuhn(ErrorMessage = "Incorrect social security number")]
public string SocialSecurityNumber { get; set; }
}
For example 1985-03-15-1234 should be saved and validated as 19850315-1234.
Any suggestions? What's the neatest approach?
If you change the auto-implemented property into a manual-implemented property then you can perform the "cleaning" step when the value is set, so it can only be stored in the model in a "clean" state. Something like this:
public class PersonViewModel
{
private string _socialSecurityNumber;
[SocialSecurityNumberLuhn(ErrorMessage = "Incorrect social security number")]
public string SocialSecurityNumber
{
get { return _socialSecurityNumber; }
set
{
_socialSecurityNumber = CleanSocialSecurityNumber(value);
}
}
}
The recommended approach here is to use a service layer. View models should not include any logic. With a service layer Your controller would call a method on its associated service and this method would return your view model with the clean SSN.
In this tutorial you will see how the service layer can be structured and you could adapt the pattern to clean your data before validation.
http://www.asp.net/mvc/tutorials/older-versions/models-(data)/validating-with-a-service-layer-cs
The use of a ValidationAttribute in your example would be an incorrect usage also.
Controller where you would inject / instantiate the service class
public ActionResult GetPerson(int PersonId){
return _personService.GetPerson(personId);
}
The service method
public PersonViewModel GetPerson(int Id){
// get the data (maybe from DAL) and clean returning view model
return new PersonViewModel(){SocialSecurityNumber = Clean(...)};
}
Hope this gives you some direction.
I'm new at ASP.NET MVC web framework. My database is compound of a single model class("Movie"). I need to validate user's from entering existing data, for instance, a database row could be:
Title - "Indiana Jones and the lost Arc"
Price - $10.00
If another user tries to insert into the database the same data above, provide an error message and prevent from submitting the form collection.
First of all, I think that your question need a "program as answer" but I'll try to suggest you from where to start for working with validation. Suppose you have this model:
class Movie {
public Guid Id { get; set; }
[Required(ErrorMessage="Title is required.")]
[Remote("UniqueTitle", "Validation")]
public String Title { get; set; }
[Required(ErrorMessage="Price is required.")]
public float Price { get; set; }
}
You can decorate it for "simple validation" using Data Annotation. I've used a specific attribute, called Remote.
This attribute allow you to define a custom, server-side, logic to validate the model.
Now, you can create a validation controller where check that provided value is not already in use:
class ValidationController : Controller {
private IDbContext db = ...;
public ActionResult UniqueTitle(String title) {
var item = db.Movies.FirstOrDefault(m => m.Title.Equals(title));
return Json(item == null, JsonRequestBehavior.AllowGet);
}
}
Now you are ready to validate your model.
I hope this can help.
I'm trying to use DataAnnotations to add validation to my models in asp.NET MVC 2 RC2, using TryUpdateModel
var user = UserManager.Find(id);
this.TryUpdateModel<IProvisioningObject>(user, form.ToValueProvider());
This updates the model, but the validation is never called. I tried using TryUpdateModel as well (which is the direct type of user), not using the form value provider, using ProvisioningObject directly (which has the validation metadata), to no avail.
Googling for examples only gives me ways to use DataAnnotations by binding through a parameter
public ActionResult Update(User user)
Which I dislike for update scenarios.
Any tips and/or solutions?
EDIT
My objects are auto-generated objects from a WCF service.
I made partials to be able to add DataAnnotations.
I call TryUpdateModel three times because it apparently doesn't support inheritance, which I think is also my problem with DataAnnotations. I specify the validation attributes for ProvisioningObject, and the binding doesn't look for inherited stuff like that.
[MetadataType(typeof(ProvisioningObjectMetadata))]
public partial class ProvisioningObject : IProvisioningObject
{
public string DisplayNameInvariant { get { return string.IsNullOrEmpty(this.DisplayName) ? this.Name : this.DisplayName; } }
}
[MetadataType(typeof(UserMetadata))]
public partial class User : IUser
{
}
public class ProvisioningObjectMetadata
{
[DisplayName("Country")]
public string CountryIsoCode { get; set; }
[Required(ErrorMessageResourceType = typeof(Properties.Validation), ErrorMessageResourceName = "DisplayNameIsRequired")]
[TempValidator]
public string DisplayName { get; set; }
}
public class UserMetadata
{
[DisplayName("Username")]
public string Name { get; set; }
}
// Controller action
public ActionResult Update(string id, FormCollection form)
{
var user = UserManager.Find(id);
this.TryUpdateModel<IUser>(user.User, form.ToValueProvider());
this.TryUpdateModel<IPerson>(user.User, form.ToValueProvider());
this.TryUpdateModel<IProvisioningObject>(user.User, form.ToValueProvider());
if (ModelState.IsValid) // always true
{
return Redirect;
}
else
{
return View();
}
}
If I add the metadata for DisplayName in UserMetadata, it works as expected, but that seems very redundant for nothing. And it would mean I would also have to copy/paste all my inherited interfaces so TryUpdateModel behaves appropriately.
I guess I'm looking for a way that doesn't require me to copy and paste my validation attributes to inherited classes.
New Answer:
"My objects are auto-generated objects from a WCF service."
Autogenerated objects won't have any attributes on them. Are you defining your objects and their attributes on the server side or on the client side?
Old Answer:
If your metadata is not on IProvisioningObject then no validation will be called. The MVC2 default model binder only knows how to find "extra" [MetadataType(buddyClass)] validation information.
For update scenarios bind against DTOs and then map the DTOs, if IsValid() to your main model classes.
Implement IDataErrorInfo interface in your partial class
You will have to write custom validation for each field(where you can use data annotation class to validate each required property)
If you need code example then let me know. I will write it for you!
source: http://www.asp.net/(S(pdfrohu0ajmwt445fanvj2r3))/learn/mvc/tutorial-37-cs.aspx
How do you know that the validation is not being called? Are you checking ModelState.IsValid in your update controller and finding that it is erroneously coming back true?
A typical update pattern is:
UpdateModel(model);
if(!ModelState.IsValid) return View(model);
return RedirectToAction("Index");
If you are expecting some "IsValid" on your model to automatically be called, that will not happen. The data annotations work behind the scenes with the ModelState dictionary on the Controller base class.