7

I'm trying to get name of input correct so a collection of objects on my view model can get bound.

   @{ViewData.TemplateInfo.HtmlFieldPrefix = listName;}
            @Html.EditorFor(m => m, "DoubleTemplate", new { 
                Name = listName,
                Index = i,
                Switcher = (YearOfProgram >= i +1)
            })

As you can see here, I pass in the "listName" as the prefix for my template, the value of listName = "MyItems".

And here is my template:

@model Web.Models.ListElement

@if (ViewData["Switcher"] != null)
{
    
    var IsVisible = (bool)ViewData["Switcher"];
    var index = (int)ViewData["Index"];
    var thisName = (string)ViewData["Name"] + "[" + index + "].Value";
    var thisId = (string)ViewData["Name"] + "_" + index + "__Value";
    if (IsVisible)
    {
        @*<input type="text" value="@Model.Value" name="@thisName" id ="@thisId" class="cell@(index + 1)"/>*@
        @Html.TextBoxFor(m => m.Value, new { @class ="cell" + (index + 1)})
        @Html.ValidationMessageFor(m => m.Value)
    }
}

but I found that the generated name becomes this: MyItems.[0].Value

It has one extra dot. How can I get rid of it?

Incidentally, I tried to manually specify the name inside the template and found the name gets overridden by the Html helper.

Update

The reason why I have to manually set the HtmlFieldPrefix is the property name (MyItems which is a list of objects) will get lost when MyItems is passed from main view to the partial view. By the time, the partial view called my template and passed in one object in MyItems, the template itself has no way to figure out the name of MyItems as it has been lost since the last "pass-in".

So that's why I have to manually set the html field prefix name. And I even tried to use something similar to reflection(but not reelection, I forgot the name) to check the name of passed in object and found it returned "Model".


Update 2

I tried Stephen's approach, and cannot find the html helper PartialFor().

enter image description here

I even tried to use this in my main view:

Html.Partial(Model, "_MyPartialView");

In Partial View:

@model MvcApplication1.Models.MyModel
<h2>My Partial View</h2>


@Html.EditorFor(m => m.MyProperty)

Here is my templat:

@model  MvcApplication1.Models.ListElement

@Html.TextBoxFor(m => m.Value)

Here is my Model:

 public class MyModel
    {
        private List<ListElement> myProperty;
        public List<ListElement> MyProperty
        {
            get
            {
                if (myProperty == null)
                {
                    this.myProperty = new List<ListElement>() { new ListElement() { Value = 12 }, new ListElement() { Value = 13 }, new ListElement() { Value = 14 }, new ListElement() { Value = 15 }, };
                }
                return this.myProperty;
            }
            set
            {
                this.myProperty = value;
            }
        }
    }

    public class ListElement
    {
        [Range(0, 999)]
        public double Value { get; set; }
    }

And here is my controller:

public ActionResult MyAction()
{
    return View(new MyModel());
}

It only generates raw text("12131415") for me, instead of the wanted text box filled in with 12 13 14 15.

But if I specified the template name, it then throws an exception saying:

The template view expecting ListElement, and cannot convert
List<ListElement> into ListElement.
halfer
  • 18,701
  • 13
  • 79
  • 158
Franva
  • 5,410
  • 16
  • 62
  • 122
  • It a little hard to understand what your trying to achieve by overriding the default behavior. Does you mode have property `public ListElement MyItems { get; set; }`? If not, how would you bind this on postback? Do you have another `EditorTemplate` for typeof `ListElement`? –  Sep 08 '14 at 08:58
  • hi @StephenMuecke yes, my model has MyItems(MyViewModel has MyItems). I bind the MyViewModel to MyView.cshtml page which uses a PartialView _MyPartialView, this partial view uses a template. And I am talking the lose of the property name in this template. I have found a solution, it may not be the best one though. – Franva Sep 09 '14 at 00:09
  • But you don't need to set value of `HtmlFieldPrefix`. If your using you `EditorTemplate` corectly, MVC will do this for you (you should not use the overload that accepts the template name). –  Sep 09 '14 at 00:18
  • hi @StephenMuecke I have updated my post according to your question, please see my update part. cheers – Franva Sep 09 '14 at 00:57
  • I'll post an answer shortly showing how you can do this but I'm a little unsure of what `Switcher` but we can sort that out after –  Sep 09 '14 at 01:06
  • hi @StephenMuecke The Switcher is used for determining whether to show this text box or not. And thank you, looking forward to the proper way to do it :) – Franva Sep 09 '14 at 01:16
  • Answer posted. Where does the value of `Switcher` come from. Is it a property of `ListElement` or is it something based on a property in the main model? - perhaps respond in the answer and i'll update it based on your response –  Sep 09 '14 at 01:24

4 Answers4

6

There is no need set the HtmlFieldPrefix value. MVC will correctly name the elements if you use an EditorTemplate based on the property type (and without the template name).

Assumed models

public class ListElement
{
  public string Value { get; set; }
  ....
}

public class MyViewModel
{
  public IEnumerable<ListElement> MyItems { get; set; }
  ....
}

Editor template (ListElement.cshtml)

@model YourAssembly.ListElement
@Html.TextBoxFor(m => m.Value)

Main view

@model YourAssembly.MyViewModel
...
@Html.EditorFor(m => m.MyItems) // note do not specify the template name

This will render

<input type="text" name="MyItems[0].Value" ...>
<input type="text" name="MyItems[1].Value" ...>
....

If you want to do this using a partial, you just can pass the whole model to the partial

MyPartial.cshtml

@model @model YourAssembly.MyViewModel
@Html.EditorFor(m => m.MyItems)

and in the main view

@Html.Partial("MyPartial")

or create an extension method

public static MvcHtmlString PartialFor<TModel, TProperty>(this HtmlHelper<TModel> helper,
  Expression<Func<TModel, TProperty>> expression, string partialViewName)
  {
    string name = ExpressionHelper.GetExpressionText(expression);
    object model = ModelMetadata.FromLambdaExpression(expression, helper.ViewData).Model;
    var viewData = new ViewDataDictionary(helper.ViewData)
    {
      TemplateInfo = new System.Web.Mvc.TemplateInfo { HtmlFieldPrefix = name }
    };
    return helper.Partial(partialViewName, model, viewData);
  }
}

and use as

@Html.PartialFor(m => m.MyItems, "MyPartial")

and in the partial

@model IEnumerable<YourAssembly.ListElement>
@Html.EditorFor(m => m)
  • hi Stephen, thank you for your help. But the situation is different. Main View ---> Partial View(passed in the MyItems) ---> MyTemplate(passed in one object in MyItems, e.g. MyItems[i]) I tried to use them in a normal way, but found the name of MyItems was lost and it became [0].Value. Oh btw, why not specify the template name??? – Franva Sep 09 '14 at 04:06
  • You can still do this using a partial (I'll update answer). The reason for not using the overload with the template name is that you lose the default indexing for the name - refer [this question](http://stackoverflow.com/questions/25333332/correct-idiomatic-way-to-use-custom-editor-templates-with-ienumerable-models-in#comment40210985_25333332) for more detailed explanation –  Sep 09 '14 at 04:11
  • ha looks good. I'll give it a try and let you know how it goes. thx :) – Franva Sep 09 '14 at 06:20
  • Hi Stephen, I tried your approach. I cannot even find @Html.PartialFor() helper in my MVC. I'm using MVC4, what is your version? – Franva Sep 15 '14 at 06:53
  • @Franva, `PartialFor()` is the extension method I created for you - see the code in my answer - the code block that starts with `public static MvcHtmlString PartialFor –  Sep 15 '14 at 07:09
  • Hi Stephen, so if I don't use the extension method you created for me, the approach would not work? – Franva Sep 15 '14 at 07:18
  • That right, Note I gave 2 options above (you can always pass the whole model to the partial) but the extension method is better IMHO because its a reusable piece of code –  Sep 15 '14 at 07:23
  • I had a similar issue with an asynchronously called partial view and I found the examples within this answer very helpful and informative. For my particular situation I still needed to set the HtmlFieldPrefix (See: https://stackoverflow.com/questions/6617768/asp-net-mvc3-add-a-htmlfieldprefix-when-calling-controller-partialview) but again this answer bettered my understanding and helped me debug my issue. Kudos from ~3.5 years out – LoJo Feb 21 '18 at 21:50
  • @stephen-muecke Thank you for that extension method! That's so much cleaner than where I originally headed to solve a similar problem. – Ken Palmer Apr 11 '18 at 19:30
3
  1. Call your partial this way:
@Html.Partial("_SeatTypePrices", Model.SeatTypePrices, new ViewDataDictionary
{
    TemplateInfo = new TemplateInfo() {HtmlFieldPrefix = nameof(Model.SeatTypePrices)}
})
  1. Partial view:
@model List
@Html.EditorForModel()
  1. Editor template implementation:

    @using Cinema.Web.Helpers
    @model Cinema.DataAccess.SectorTypePrice
    @Html.TextBoxFor(x => Model.Price)
    

This way your partial view will contain list of items with prefixes. And then call EditorForModel() from your EditorTemplates folder.

0

I found that I can change the value of HtmlFeildPrefix in my template.

So what I did to solve my problem was just to assign the correct value to HtmlFeildPrefix in the template directly rather than in the page which calls the template.

I hope it helps.

Franva
  • 5,410
  • 16
  • 62
  • 122
0

If I want to pass the HtmlFieldPrefix I use the following construct:

<div id="_indexmeetpunttoewijzingen">
    @Html.EditorFor(model => model.MyItems, new ViewDataDictionary()
    {
        TemplateInfo = new TemplateInfo()
        {
            HtmlFieldPrefix = "MyItems"
        }
    })
</div>