I'm trying to validate a model containing other objects with validation rules using the TryUpdateModel:
public class Post
{
public User User;
}
public class User : IValidatableObject
{
public string Captcha;
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (/* check if captcha valid */)
{
yield return new
ValidationResult("Captcha invalid.",
new[] { "Captcha" });
}
}
public ActionResult Edit(int postId, string title)
{
var post = postsRepository.Get(postId);
if (TryUpdateModel(post))
{
/* save */
}
}
The problem is that nested user is also validated but this is updating of the post and there is no captcha field in the form so modelstate always is invalid. How can I validate only value-type properties of the post?
I don't particularly agree with having a CAPTCHA check on a user model unless you require a CAPTCHA everywhere you use the User model.
That being said, you could...
Create a flag that must be set in order to actually check the CAPTCHA, and return valid otherwise.
Create another model which matches the current form exactly
Remove the CAPTCHA from the model and just take it in as a parameter to actions that require it
Set a sentinel value for CAPTCHA as a default which always returns valid
tons of other ideas...
Related
I have a model in mvc as below
public class person
{
[Required(ErrorMessage = "Please enter First Name.")]
public string first_name {get;set;}
[Required(ErrorMessage = "Please enter last Name")]
public string last_name {get;set;}
[Required(ErrorMessage = "Please enter |DOB")]
public DateTime DOB {get;set;}
}
post method
[HttpPost]
public ActionResult save_person(person per)
{
if(per.first_name == null || per.first_name =="")
per.first_name ="demo_first";
if(per.lastname == null || per.lastname =="")
per.last_name ="demo_last";
if (ModelState.IsValid) //fails even assignment is done above
{
}
}
so using if condition I make sure the model elements will contain atleast some value but even after that
ModelState.IsValid is failing and returning back to the view saying first_name and last_name is requried
how can we achieve this logic??
It is quite unclear why are the first_name and last_name properties on your model decorated with the Required attribute if they clearly are not required.
This being said, if you want to update the value of some model property in your controller you might need to ensure that you also update it in the ModelState:
if (per.first_name == null || per.first_name == "")
{
per.first_name ="demo_first";
ModelState.Remove("first_name");
ModelState.SetModelValue("first_name", new ValueProviderResult(per.first_name, per.first_name, CultureInfo.InvariantCulture));
}
if (per.last_name == null || per.lastname == "")
{
per.last_name ="demo_last";
ModelState.Remove("last_name");
ModelState.SetModelValue("last_name", new ValueProviderResult(per.last_name, per.last_name, CultureInfo.InvariantCulture));
}
if (ModelState.IsValid)
{
...
}
I just want to add some reference to the actual documentation to clear things up for the OP on why changing properties in the method does not solve his problem.
From the docs:
Handling Model State Errors
Model validation occurs prior to each
controller action being invoked, and it is the action method’s
responsibility to inspect ModelState.IsValid and react appropriately.
In many cases, the appropriate reaction is to return some kind of
error response, ideally detailing the reason why model validation
failed.
That means, that validation has already happened when your ActionResult is invoked and the .IsValid property is set.
As per your comment: If the same model needs to be used in a different view, but with different validation requirements, it would be a good idea to create a new ViewModel with appropriate annotations:
public class AnotherPersonViewModel {
//no required attribute
public string last_name {get; set;}
//no required attribute
public string first_name {get; set;}
//... rest of attributes
}
After this you could always map your view model to your entity model or whatever you are currently doing. But you avoid cluttering your controller with unnecessary code to remedy mistakes in your architecture.
Alright...this may be a bit backwards but, I only need to do it in one spot.
I have a Model
public class LoginModel : xxx.Models.PageVars
{
public Item.LoginAttempt LoginAttempt { get; set; }
public LoginModel()
{
// does a bunch of stuff here...mainly to set the layout properties from PageVar
this.LoginAttempt = new Item.LoginAttempt();
}
}
Login Attempt is a simple obj (for now)
// login attempt
public class LoginAttempt
{
public string Email { get; set; }
public string Password { get; set; }
}
My controller
public ActionResult Login()
{
return View("Login", new Models.LoginModel());
}
[HttpPost]
public ActionResult LoginAttempt(LoginAttempt model)
{
return View("Login", model);
}
In my view
#model xxx.Models.LoginModel
Is there a way to use the property of the obj/model from LoginModel for the #model.
I can get the values from FormCollection or request but...that's not optimal.
thoughts???
tnx
The model for your GET should match the model for your POST. Otherwise, you're not playing on the same field. In order to allow the binding of data from a POST to a model, the HTML Helpers will generate a name that matches the access path of the property in the view's model. In other words, in your form, based on the model being LoginModel, your field names will be LoginAttempt.Email and LoginAttempt.Password. But, in the POST action, you're accepting just LoginAttempt, so the modelbinder is expecting to see data for Email and Password, which it won't find.
There's actually not even any need for this nested class. Just put your Email and Password fields directly on LoginModel and use that for both your view and your POST parameter. Then, you won't have any issues because everything will match up.
Why don't you have the form post controller action accept the parent model LoginModel instead of LoginAttempt? That way, the default MVC model binding should automatically parse the submitted values into the LoginModel and you'll have acces to LoginAttempt.
If it isn't then your form needs to use the prefix values in the names of the properties on the form. This is done automatically when you use TextboxFor, DropdownListFor etc.
In your example, the names of the form fields should start with LoginAttempt.Email etc
I've seen it work 2 ways. First way would be to rename your LoginAttempt model parameter to be
[HttpPost]
public ActionResult LoginAttempt(LoginAttempt loginModel)
{
return View("Login", model);
}
But i would use the Bind(Prefix) option
[HttpPost]
public ActionResult LoginAttempt([Bind(Prefix="LoginModel")] LoginAttempt model)
{
return View("Login", model);
}
you can't really return model of type LoginAttempt to the view though so you'd have to do even more work to get it to work if you're set on doing it this way. You should probably be redirecting to a different page instead of returning the Login view if it succeeds. Other wise return new LoginModel() {LoginAttempt = model}
I have an ASP.Net MVC application which contains Model and ViewModel, this app have UI and API interfaces which works through different controllers, UI works with ViewModel, API works with Model. ViewModel has validation with data annotations (C# attributes) and Model hasn't so now API allows to save to DB any unconsistent models.
What I have now:
// Model
public class Contact
{
public string Email { get; set; }
...
}
// ViewModel
public class CreateContactViewModel
{
[Required(ErrorMessage = "*")]
[EmailAddress(ErrorMessageResourceType = typeof(CreateContact), ErrorMessageResourceName = "Validation_invalid_email", ErrorMessage = null)]
public string Email { get; set; }
...
}
// View
...
<div style="padding-bottom:13px;">
#Html.TextBoxFor(x => x.Email, new { style = "width:405px;" })
#Html.ValidationMessage("Email", new { style = "color:red;" })
</div>
...
// UI controller
[HttpPost]
public ActionResult Create(CreateContactViewModel model, GetContactsViewModel contactsModel)
{
/* Now validation work only on client side, should be fixed? */
var newContact = new Contact()
{
Email = model.Email,
...
};
UnitOfWork.ContactRepository.Insert(newContact);
UnitOfWork.Save();
return GetContactsList(contactsModel);
}
// API Controller
public class ContactsController : BaseApiController
{
...
public IHttpActionResult Post(Contact contact)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
UnitOfWork.ContactRepository.Insert(contact);
try
{
UnitOfWork.Save();
}
catch (DbUpdateException)
{
if (ContactExists(contact.Id))
{
return Conflict();
}
else
{
throw;
}
}
return Created(contact);
}
...
}
I suppose that the better way is to move validation attributes to the Model (and also more complex busines logic validation will be added in the future) and check Model validity manualy after mapping from ViewModel I found the same idea here http://blogs.msdn.com/b/simonince/archive/2010/12/07/view-model-versus-domain-entity-validation-with-mvc.aspx. (Manual validation call works for me)
What should I do in this case to support validation both in API and UI?
Probably app architecture is not good enough and I am glad to get any advices but I am not fully authorized to change anything now.
As you already know (or figured out), validation can, will and should occur on many levels.
The first thing you need to do is differentiate what should be a UI validation from what should be a business rule validation.
Assuming I have a register <form>...</form>. Within that form, I have a simple email textbox.
Assuming my rules are the following:
The email textbox is mandatory
The email should not be a duplicate
I can quickly differentiate that the mandatory textbox should be a UI validation.
As for the “no duplicate email” rule, this requires a trip to the database to see if the given email address does not already exist. To me this is a business rule validation.
So basically, my RegisterViewModel would have a [required] data annotation attribute set on the email property. This would take care of the UI Validation.
Upon submit, I would validate my ViewModel with Model.IsValid() for server-side validation.
Once the ViewModel is ok, I would pass the ViewModel to the API (or some people prefer to transform the ViewModel into a POCO before sending it to the API).
Inside the API, I would invoke the database and check if the given email address does not already exist.
If it does exist, the method would return false (assuming your method returns true or false).
The Controller would check if the returned value is false and perhaps add some error to the UI.
If it doesn’t exist, then great! Convert your ViewModel (if it hasn’t been transformed before) into a POCO (or into what you call your Model object) in order to save it to your database.
In my example (and in most of my POCO’s) I rarely have data annotations I usually leave those to the ViewModel and whatever more complex validation I need, I do manually within the API layer.
I was looking for an implementation / example of loading and authorizing a resource at a controller level. I am looking for the same functionality as load_and_authorize_resource in the cancan gem in ruby on rails.
Has anyone come across one / have an example how to implement something similar using Mvc .Net attributes?
Thanks!
The load_and_authorize_resource behaviour
With rails, controller and model names are linked up by convention. The attribute load_and_authorize_resource takes that to its advantage. When an action is hit that requires an instance of a resource, the load_and_authorize_resource verifies whether the instance of the resource can be accessed. If it can, it will load it up in an instance variable, if it cant, it will return a 404 or any error behaviour you have configured the attribute to produce.
For example, if I have a resource picture, and only user that own a certain picture can edit the picture's name.
So we would have a Edit action, which obviously would have a pictureId of the picture you want to edit. load_and_authorize_resource would verify whether the current context/user has access to the resource.
Here is a small video introduction of the module.
I am not aware of the existence of such plugin for ASP.NET MVC. To mimic it's functionality you could write a custom Authorize attribute though:
public class LoadAndAuthorizeResourceAttribute : AuthorizeAttribute
{
private class ModelDescriptor
{
public string Name { get; set; }
public Type ModelType { get; set; }
}
private const string ModelTypeKey = "__ModelTypeKey__";
public override void OnAuthorization(AuthorizationContext filterContext)
{
var parameters = filterContext.ActionDescriptor.GetParameters();
if (parameters.Length > 0)
{
// store the type of the action parameter so that we could access it later
// in the AuthorizeCore method
filterContext.HttpContext.Items[ModelTypeKey] = new ModelDescriptor
{
Name = parameters[0].ParameterName,
ModelType = parameters[0].ParameterType,
};
}
base.OnAuthorization(filterContext);
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var authorized = base.AuthorizeCore(httpContext);
if (!authorized)
{
// the user is not authenticated or authorized => no need to continue
return false;
}
// get the currently authenticated username
string username = httpContext.User.Identity.Name;
// get the id of the resource that he is trying to manipulate
// the id should be sent either as part of the query string or the routes
string id = httpContext.Request.RequestContext.RouteData.Values["id"] as string;
// get the action param type
var modelDescriptor = httpContext.Items[ModelTypeKey] as ModelDescriptor;
if (modelDescriptor == null)
{
throw new InvalidOperationException("The controller action that was decorated with this attribute must take a model as argument");
}
// now load the corresponding entity from your database given the
// username, id and type
object model = LoadModel(id, username, modelDescriptor.ModelType);
if (model == null)
{
// the model that satisfies the given criteria was not found in the database
return false;
}
httpContext.Request.RequestContext.RouteData.Values[modelDescriptor.Name] = model;
return true;
}
private object LoadModel(string id, string username, Type modelType)
{
// TODO: depending on how you are querying your database
// you should load the corresponding model here or return null
// if not found
throw new NotImplementedException();
}
}
and now you could have a controller action that is decorated with this attribute:
[LoadAndAuthorizeResource]
public ActionResult Edit(Picture model)
{
... if we get that far the user is authorized to modify this model
}
I have facing the following problem after the update.
I have a Model with Class level Validation plus property level validation in it. After updating to MVC 2 RC 2. The model validation fails on Model binding. What i actually understand that new mechanism trying to validate the model when you first request it or say on GET and it get null object exception during tryvalidatemodel Model binding call.
My Model is like this below
[Serializable]
[MetadataType(typeof(InterestClaimMetaData))] //metadata with all properties level validation
//these validations fails when you request a page.
[DateComparison("DateA", "DateB", eDateComparitor.GreaterThan,
ErrorMessage = "Date A must be greater than B Date")]
[MutuallyExclusive("A", "B", ErrorMessage = "Please select either A or B field")]
public class IE {
public int ID { get; set; }
public byte[] Updated { get; set; }
}
DataComparison and MutuallyExclusive overrides the validate function isvalid and check the validation but it fails trying to validate as first requested.
dont know how to stop this happening as it should not validate model on get request; just attach the properties.
Only models without these class level validation works.
Please advise.
Thanks
Seperate your action method in your controller in to two action methods. Mark one as GET and the other as POST. Then only apply the validation in the POST method. So, for example, if you currently have an method called Create that looks something like this...
public ActionResult Create(YourModel yourModel)
{
// Some code in here to validate stuff
// Some code in here to do stuff
return RedirectToAction("Index");
}
Split this out in to two methods like this...
[HttpGet]
public ActionResult Create()
{
return View();
}
[HttpPost]
public ActionResult Create(YourModel yourModel)
{
try
{
// Some code in here to validate stuff
// Some code in here to do stuff
return RedirectToAction("Index");
}
catch
{
return View();
}
}