Remote Validation for LIST of MODELs
Asked Answered
C

2

3

I used the following tutorial: http://msdn.microsoft.com/en-us/library/gg508808%28VS.98%29.aspx

And everything seemed fine, but in my case, string Username always comes back null. After tonnes of research, I found everyone discovered BIND Prefixes. That would be great in many circumstances, but not this one. I should note all properties and names line up, however in my for loop, the EditorFor creates a [i].Username field and this doesn't map to any model property.

QUESTION: I think I want to map [i].Username to Username where i is any number from 0-infinity, so when it GETS, the value is passed to the Action properly. How do I do this? If this is wrong, what do I do validate this for a specific row in a table?

@for (var i = 0; i < Model.Count; i++)
{
  BLAH BLAH BLAH CODE FOR BUILDING TABLE ROWS
  <td>
     @Html.EditorFor(modelItem => Model[i].Username)
  </td>                               
}

Since I could technically have HUNDREDS if not THOUSANDS of records, I would rather not had a binding PREFIX for all 1000. Am I fundamentally missing something here? I am new to ASP.NET MVC and am used to WebForms so I feel like sometimes I am mixing concepts and mashing up something that is entirely wrong.

EDIT: I fixed it by doing the following, but not sure if this is the best idea. I set the parameter equal to the FieldName without [i] prefix, but still retrieve the element with the [i] prefix. Javascript isn't my Forte so please let me know if it is horrible.

adapters.add("remote", ["url", "type", "additionalfields"], function (options) {
    var value = {
        url: options.params.url,
        type: options.params.type || "GET",
        data: {}
    },
        prefix = getModelPrefix(options.element.name);

    $.each(splitAndTrim(options.params.additionalfields || options.element.name), function (i, fieldName) {

        var paramName = fieldName.substr(fieldName.lastIndexOf(".") + 1);

        var actualFieldName = appendModelPrefix(fieldName, prefix)
        value.data[paramName] = function () {
            return $(options.form).find(":input").filter("[name='" + escapeAttributeValue(actualFieldName) + "']").val();
        };
    });

    setValidationValues(options, "remote", value);
});
Castillo answered 16/12, 2014 at 20:35 Comment(1)
You're definitely missing something here. There's only one Prefix for the whole collection. You won't need one Prefix for each item in it. But, I don't even understand what your problem is. You need to provide more code and try to explain your problem more clearly.Regret
G
8

You have not posted your code for the model or controller, but assuming you have a RemoteAttribute applied to property Username, for example

public class MyModel
{
  [Remote("IsValidUserName", "Person")]
  public string Username { get; set; }
}

with a method in PersonController

public JsonResult IsValidUserName(string Username)
{
  ....
}

and the view

@model List<Person>
...
@for (var i = 0; i < Model.Count; i++)
{
  @Html.EditorFor(m => m[i].Username)                           
}

This will generate html such as

<input name="[0].UserName" ... />
<input name="[1].UserName" ... />

Unfortunately the remote method in jquery-validate posts back the name and value of the element so that the ajax call looks like

$.ajax({
  url: '/Person/IsValidUserName',
  data: { [0].UserName: '[email protected]' },
  ...

which will not bind.

I have reported this as an issue at Codeplex with a possible solution. In the meantime you can modify the remote method in jquery-validate.js file as follows

remote: function(value, element, param) {
  ....
  var data = {};
  // data[element.name] = value;
  data[element.name.substr(element.name.lastIndexOf(".") + 1)] = value; // add this

This will strip the prefix so that the posted data is

 data: { UserName: '[email protected]' },

and will correctly bind to the method.

Gunter answered 17/12, 2014 at 2:26 Comment(6)
What about the additional fields? Do I need to change something else for them?Castillo
Not sure - I have not tested that yet (and don't have time at the moment). If you try it and there is a problem, then suggest you add a comment to the issue (see link in answer) so that it gets addressed. Interesting I reported another bug recently regarding additional fields for [Remote] as a result of this questionGunter
I edited my post to include my fix. The substring isn't my preferred approach. You seem smart, recommendations? Otherwise I would be happy to comment on the link you submitted from your original Answer.Castillo
I think its best to add comments to the CodePlex issue and have the experts address it (its been assigned so hopefully they will come up with a fix soon)Gunter
@StephenMuecke look into this questionTrifle
Also, for what it's worth, it may not be obvious that the parameter name in the controller method must match the property name being validated. For example, if the model property is UserName, then controller method couldn't be public JsonResult IsValidUserName(string name) but would have to be public JsonResult IsValidUserName(string Username)Offutt
V
0

Assuming the code is formatted in the following way:
View:

@for(var i = 0; i<Model.Count; i++) {
    <div class="row">
        @Html.EditorFor(modelItem => Model[i].Username)
    </div>
}

<style>
    .valid{
        background: lime;
    }
</style>
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}

Model:

public class MyModel {
    [Remote("IsValidUserName", "Validation", HttpMethod = "POST")]
    public string Username { get; set; }
}

It is possible to use the automatic modelbinding to bind to the remote validation. If you were to use a list or array for this, the binding would fail while a Dictionary can catch this error.
Be aware however that the Key in the dictionary will be consistent with the id in the view (e.g. [5].Username will map to {Key: 5, Value: MyModel{Username:...}}) and won't be a default 0, hence the use of a Linq query.
Controller:

[HttpPost]
public JsonResult IsValidUserName(Dictionary<int,MyModel> Users) {
    return Json(Users.First().Value.Username.Contains("User"));
}
Vinificator answered 23/4, 2021 at 10:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.