简体   繁体   中英

ASP.NET MVC Binding with Remote Validation

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 , ie 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, ie 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)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM