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);
});
MooseManCA
  • 83
  • 1
  • 4
  • 2
    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. – ataravati Dec 16 '14 at 21:02

2 Answers2

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: 'someone@somewhere.com' },
  ...

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: 'someone@somewhere.com' },

and will correctly bind to the method.

  • What about the additional fields? Do I need to change something else for them? – MooseManCA Dec 18 '14 at 19:55
  • 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 question](http://stackoverflow.com/questions/26308852/mvc-remote-validation-with-additional-bool-fields/26309626#26309626) –  Dec 18 '14 at 22:16
  • 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. – MooseManCA Jan 20 '15 at 22:01
  • 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) –  Jan 20 '15 at 22:42
  • @StephenMuecke look into [this question](http://stackoverflow.com/questions/33414745/generic-remote-validations-in-mvc-5) – Awais Mahmood Oct 30 '15 at 05:46
  • 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)` – JakeMc Jul 06 '21 at 01:50
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"));
}