Steven
Steven

Reputation: 47

MVC3 Remote Validation of a field within a List

For some reason, whenever I try to remotely validate a primitive property inside an array within my model, the value of the property is not passed as a parameter.

For example, when my remote validation method (UniqueItemNo) is called, the string parameter "id" is always null. If I were to perform validation on CartNumber instead of ItemNumber, the parameter is passed correctly.

Controller

public class HomeController : Controller
{
    public ActionResult Index()
    {
        CartModel cart = new CartModel();
        cart.Items.Add(new ItemModel() { ItemNumber = "12345" });
        return View(cart);
    }

    [HttpPost]
    public JsonResult UniqueItemNo(string id)
    {
        /** Do Work **/
        return null;
    }
}

Models

public class ItemModel
{
    [Remote("UniqueItemNo", "Home", HttpMethod="POST")]
    public string ItemNumber { get; set; }
}

public class CartModel
{
    public CartModel()
    {
        Items = new List<ItemModel>();
    }

    public List<ItemModel> Items { get; set; }
    public string CartNumber { get; set; }
}

View

@{
    ViewBag.Title = "Home Page";
}

<h2>@ViewBag.Message</h2>
@using(Html.BeginForm()) {
    <p>
        @for(int i = 0; i < Model.Items.Count; i++)
        {
            @Html.TextBoxFor(m => m.Items[i].ItemNumber);
            @Html.ValidationMessageFor(m => m.Items[i].ItemNumber);                                    
        }
    </p>
}

Upvotes: 2

Views: 2998

Answers (4)

Guilherme Melo
Guilherme Melo

Reputation: 494

I hope you found an answer already, but in case you didn't, try this:

[HttpPost]
public JsonResult UniqueItemNo()
{
    var itemNumber = Request.Form[Request.Form.AllKeys.First(p => p.Contains("ItemNumber"))];

    /** Do Work **/
    return Json(true);
}

Note that @Mamoon ur Rasheed answer was almost correct, but since you're using POST your parameters will be inside the Form property, not QueryString.

Let me know if you still have any issues regarding this.

Btw, you should use a try/catch. I didn't wrote it just to keep it simple.

Upvotes: 1

alexb
alexb

Reputation: 971

Unfortunately you don't have support for this.

In client side the remote validation is handled by jquery.validate.unobtrusive.js which says

...
$.each(splitAndTrim(options.params.additionalfields || options.element.name), function (i, fieldName) {
    var paramName = appendModelPrefix(fieldName, prefix);
    value.data[paramName] = function () {
        return $(options.form).find(":input[name='" + escapeAttributeValue(paramName) + "']").val();
    };
});
...

so you should notice that for each additional field it constructs just one parameter in query string (or in form if you do post). because of that you server side remote attribute should look like

[Remote("UniqueItemNo", AdditionalFields = "ItemNumber[0],ItemNumber[1],...")]

fact that you don't know at design time.

you could use a js hack and keep a hidden field named ItemNumber and with value a concatenated list of ids. on server side you should change the type of ItemNumber array into string and split the values.

I hope this helps although it's a long time since you asked.

Upvotes: 0

Mamoon ur Rasheed
Mamoon ur Rasheed

Reputation: 317

just solved the same problem. so for my type of people who are going to have this problem in future, here is the solution

here is the root cause.

when you do @Html.TextBoxFor(x=>x.m.Items[i].ItemNumber) it outputs something like this

<input type=text value='value' name='items[index].ItemNumber' ...

and then it adds the other validations like data-* attributes for validation. e.g. remote validation url data-remot-url and etc

now when form got validated it requested the remote url with data like key=xxx[1].propertyname

and this is not assignable to a single variable in remote action method that will receive this property.

if you will change the action arguments to something like this, then it will work

 public JsonResult UniqueItemNo(list<string> propertyname)

and even then you are not guranteed as it requires to have the parameters index start from 0 and without any gap. so xxx[1].ItemNumber will never get maped.

the only remaining solution is to get the property name from querystring and map it to your required variable. like this

    if(Request.QueryString.AllKeys.FirstOrDefault(p => p.ToLower().Contains("propertyname"))!=null)
    {
                     propertyname = Request.QueryString[Request.QueryString.AllKeys.First(p => p.ToLower().Contains("propertyname"))];
}

it will search and map the required variables.

hope this will help

Upvotes: 7

Tom Chantler
Tom Chantler

Reputation: 14941

Try changing the parameter to match the model. Like this:

    [HttpPost]
    public JsonResult UniqueItemNo(ItemModel itemModel)
    {
        // look at itemModel.ItemNumber...
        /** Do Work **/
        return null;
    }

You can still start the name of the ActionMethod parameter with lowercase (itemNumber instead of ItemNumber) as per the normal coding conventions.

EDIT: Have you tried coding the View like this?

@{
    ViewBag.Title = "Home Page";
}

<h2>@ViewBag.Message</h2>
        @foreach (var item in Items)
        {
           @using(Html.BeginForm()) {
              <p>
                 @Html.HiddenFor(m=>m.CartNumber)
                 @Html.TextBoxFor(m => item.ItemNumber);
                 @Html.ValidationMessageFor(m => item.ItemNumber);                                    
              </p>
           }
        }

Upvotes: 0

Related Questions