ASP.NET MVC Binding with Remote Validation - c#

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)

Related

Dynamic type model binding in ASP.NET Core

I'm currently working on a content editor that can be used for multiple types of content, where a developer could specify their own model. For example, a model might look like this:
public class ImageWithCopyWidgetModel : WidgetModel, IWidgetModel
{
public string ImageUrl { get; set; }
public string ImageAltText { get; set; }
public string HeaderText { get; set; }
public string BodyContent { get; set; }
}
On the editor side, I have a view model that looks like:
public class EditContentViewModel<TModel> where TModel : IWidgetModel
{
public int Id { get; set; }
public string Name { get; set; }
public TModel WidgetModel { get; set; }
}
I have the binding on the GET/form display side working fine. My issue comes with getting the model binder to accept the data on the POST? I've tried the following, but each returns null for model.WidgetModel:
// Option 1
EditContent(int pageId, int id, EditContentViewModel<dynamic> model)
// Option 2
EditContent(int pageId, int id, EditContentViewModel<object> model)
// Option 3
EditContent(int pageId, int id, EditContentViewModel<IWidgetModel> model)
Note, for testing purposes, I tried explicitly setting the type of WidgetModel to a concrete class (the ImageWithCopyWidgetModel noted above) and that works.
I'm really trying to avoid having to use Request.Form here as its going to limit future plans for this implementation.
What you're wanting is not possible, at least out of the box. On post, all the modelbinder has is a bunch of key-value pair string. What informs its decision about how to bind those values to something useful is the action param(s). Specifically, it has no way of knowing that it should actually create an instance of ImageWithCopyWidgetModel when you're binding to EditContentViewModel.
Also, the modelbinder is designed to discard values it doesn't know what to do with. That means that it's unfortunately not even possible to cast to ImageWithCopyWidgetModel after the fact, because all properties not present on EditCopyViewModel would have been discarded by that point.
Your best bet is a custom model binder, but the implementation of that is too broad for the scope of Stack Overflow. I suggest you refer to the documentation.

MVC Range Attribute fails when a hidden field is added to the model

I have a Parent Model that contains a property used by a Sub Model for pre-filling a field on the View.
I would like to move the property into the Sub Model where it belongs, but another property's range attribute fails when i do this.
Why is the range attribute failing validation when i have a property used only to be hidden on the EditorTemplate?
The Model looks like
public class ParentModel
{
public SubModel subModel { get; set; }
}
public class SubModel
{
public uint? DefaultValue { get; set; }
[Required]
[Range(1,100)]
public uint RangedId { get; set;}
public bool EnableRange { get; set; }
}
The View (EditorTemplate) Looks like
#model SubModel
#Html.HiddenFor(model => model.DefaultValue)
#Html.TextBoxFor(model => model.RangeId)
<script>
$('#EnableRange').change(function() {
if($('#EnableRange').val()){
// remove the disabled attribute from the RangeId Field
} else {
// add the disabled attribute from the RangeId Field
}
}
</script>
The Controller Looks Like
public ActionResult Create(TViewModel model)
{
try
{
if (ModelState.IsValid)
{
//Do Something Meaningful
}
//Redisplay the view
}
}
With the DefaultValue property in the SubModel the RangeId's Range Attribute fires even when the RangeId is disabled on the form. This causes the ModelState.IsValid to be false.
When I move the DefaultValue property up to the ParentModel the Range attribute for RangeId no longer fires (because the field is disable). Which causes the ModelState.IsValid to be true, because the RangeId is never evaluated for validation.
Whatever you think is happening is NOT happening. The server side Model.IsValid does not care anything about, nor is it directly affected by disabling the control on the client side (though it can be indirectly affected as we will see below). The validation will always occur if nested form fields are posted and nested objects have required properties.
More likely, the real issue here is that when you have DefaultValue in the child model, then when you submit the model to the parent, the model binder creates an instance of SubModel because it contains a value for DefaultValue. When you move it to the parent, and you disable the RangeId, there is no value to post and therefore no SubModel gets created, and thus no validation occurs.
Ie, my guess is that when you move DefaultValue to the parent, SubModel is null on postback, thus because there is no instance to validate, there is nothing to fail validation, particularly since you are not persisting the EnableRange value.
So you really have several issues. First, disabling a control on the client will not disable validation on the server. Second, if there are no nested form fields posted to the server, then no nested objects will be created and no validation will occur (so in that respect, validation can be disabled as a side-effect if you are very careful). Third, If you DO post some nested form fields but not others, validation then nested objects WILL get created and validation will occur on fields that are not posted because they were disabled.

Model validation with respect to action being taken over entity being validated

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; }
}

How do I prevent Entity Framework forcing validation on child object properties?

I have an ASP.NET MVC 3 project that uses .Net 4.0 Framework and Entity Framework 4.4. I am using the code first w/ migrations approach for the Entity Framework.
I have an object A that has a property of object B. object A really just needs to know the id for object B, but MVC is enforcing all annotation validations on object B as I've marked it required in object A (which it is). ModelState.IsValid is always returning false because some validations are failing on object B when the form is submitted.
Example:
public class FormField
{
public int Id { get; set; }
[MaxLength(50)]
[Required]
[DisplayName("Field Name")]
public string Name { get; set; }
[Required]
public Form Form { get; set; }
}
public class Form
{
public Form()
{
Fields = new List<FormField>();
}
public int Id { get; set; }
[Required]
public string Name { get; set; }
public List<FormField> Fields { get; set; }
}
This isn't a problem when editing an existing FormField as I can just put a hidden field with the Form.Name property on the page (this still strikes me as something that should be unnecessary). The issue arises when creating a new FormField. I display a drop down list of forms (this is populated from my view model), and make that field point to the FormField.Form.Id property. ASP .NET MVC is still expecting formField.Form.Name (as this was marked as required on the Form object).
If I remove the "[Required]" annotation from the Form field of the FormField object, the validations wouldn't fire, but this would make the foreign key to Form.Id nullable in the database, which it shouldn't be.
Any thoughts? I'm probably doing something wrong here but I'm not entirely sure what.
I removed the [Required] annotation on FormField.Form. That didn't fix the issue. I tried adding "[Bind(Exclude = "vm.FormField.Form")]" to the parameter being posted. This stops the binding, but ensures I'm still left with the validation errors in ModelState.
Ultimately, I had to do:
ModelState.Remove("FormField.Form.Id");
ModelState.Remove("FormField.Form.Name");
And this prevented the ModelState errors I was getting. As I really only need the Form.Id, I had a property in my ViewModel "SelectedForm" and use this property for that value.
So...this works, but...this seems a pretty tedious solution as if I had more required fields on the FormField.Form object, I would have to list each one as well as hard code the property name in a string form.
Can anyone think of a way to refine this approach?
Maybe you could remove the [Required] attribute and instead use the EF fluent API to configure this:
modelBuilder.Entity<FormField>().Property(ff => ff.Form).IsRequired();

asp.NET MVC 2 DataAnnotations UpdateModel<T> validation

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.

Categories

Resources