How to create an EditorTemplate using UIHint with a property of type IEnumerable <T>

In MVC, you can create an editor template for T , and then when you want to render the editor for a property of type IEnumerable<T> , you can just do it.

 Html.EditorFor(m => m.MyListOfT) 

The beauty is that the names are automatically created by the framework for the inputs, and then when publishing the model's binding back, everything works beautifully.

My question is: how do you do this when you have several editor templates?

I tried using UIHint (), however, it seems that this allows you to specify UIHint for the list, rather than every item in the list. This means that you need to create a template editor for a list with a foreach () loop, and then skip the excellent auto-name and model binding.

What am I missing here?

The model is, for example,

 public class MyViewModel { public IEnumerable<SomeType> SomeProperty { get; set; } } 

Ideally, I want to do something like:

 public class MyViewModel { [UIHint("SomeTypeTemplate")] public IEnumerable<SomeType> SomeProperty { get; set; } } 

and they are automatically applied to all elements in the list, so I can do it with:

 Html.EditorFor(m => m.SomeProperty) 
+7
source share
3 answers

What am I missing here?

Nothing. Sorry like that. If you specify a template name when calling Html.EditorFor or using UIHint, the template will be called for a list, not for each element.

Having said that, you can of course write your own extension method that will achieve this functionality:

 public static class HtmlExtensions { private class ViewDataContainer: IViewDataContainer { public ViewDataContainer(ViewDataDictionary viewData) { ViewData = viewData; } public ViewDataDictionary ViewData { get; set; } } public static IHtmlString EditorForCollection<TModel, TProperty>( this HtmlHelper<TModel> html, Expression<Func<TModel, IList<TProperty>>> expression ) { var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData); if (string.IsNullOrEmpty(metadata.TemplateHint)) { return html.EditorFor(expression); } var collection = metadata.Model as IList<TProperty>; var sb = new StringBuilder(); for (int i = 0; i < collection.Count; i++) { var indexExpression = Expression.Constant(i, typeof(int)); var itemGetter = expression.Body.Type.GetProperty("Item", new[] { typeof(int) }).GetGetMethod(); var methodCallExpression = Expression.Call(expression.Body, itemGetter, indexExpression); var itemExpression = Expression.Lambda<Func<TModel, TProperty>>(methodCallExpression, expression.Parameters[0]); var result = html.EditorFor(itemExpression, metadata.TemplateHint).ToHtmlString(); sb.AppendLine(result); } return new HtmlString(sb.ToString()); } } 

which could work with the model properties of the collection type view, which are decorated with the UIHint attribute:

 public class MyViewModel { [UIHint("SomeTypeTemplate")] public IList<ItemViewModel> Items { get; set; } } 

and in your opinion:

 @model MyViewModel @Html.EditorForCollection(x => x.Items) 

and your ~/Views/Shared/EditorTemplates/SomeTypeTemplate.cshtml can now be entered into one ItemViewModel :

 @model ItemViewModel ... 

You no longer need an intermediate display template in which you just loop around and call the actual template - it will be a real waste.

+10
source

You create 2 EditorTemplates. One handles IEnumerable, and this template loop creates

 Html.EditorFor(m => m.SomeProperty, "SomeIndividualTemplate") 

for each item in the listing.

+2
source

Although @Darin's answer is perfect, for some reasons I haven't found yet, the TemplateHint from ModelMetaData has always been null. As a solution, I created another @Darin code overload to accept the template name parameter and render the view instead of checking for TemplateHint in ModelMetaData. Sorry to post it as an answer since I do not have privileges to post as a comment. Thanks Darin :)

+1
source

All Articles