Table of Contents
If you've stumbled upon this post, it most likely means you're struggling with a nasty issue that often affects ASP.NET applications heavily based on partial views: the naming conflict between multiple HTML input elements that handle different ViewModels (or multiple instances of the same ViewModel class) having properties with the same name, when they are placed inside a Razor Page (or View) featuring multiple Razor Partial Views.
In this post, we'll see how to fix this issue using the use the TemplateInfo.HtmlFieldPrefix property.
The Problem
Before presenting the solution, let's clarify the problem. Suppose we have the following Razor View:
1 2 3 4 5 6 |
@model IEnumerable<UserViewModel> @foreach (var user in Model) { <partial name="UserFormPartial" model="user" /> } |
And the following Partial View:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
@model UserViewModel <form> <label asp-for="Id">Unique ID:</label> <input asp-for="Id" /> <label asp-for="Username">Username:</label> <input asp-for="Username" /> <label asp-for="Email">Email:</label> <input asp-for="Email" /> <!-- TODO: other form elements --> </form> |
As we can see, the first Razor Page will likely contain several Partial Views - one for each user: if we run the project and take a look at the generated HTML, we would see the following markup:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
<form> <label for="Id">Unique ID:</label> <input id="Id" name="Id" value="1" /> <label for="Username">Username:</label> <input id="Username" name="Username" value="Mark" /> <label for="Email">Email:</label> <!-- TODO: other form elements --> </form> <form> <label for="Id">Unique ID:</label> <input id="Id" name="Id" value="2" /> <label for="Username">Username:</label> <input id="Username" name="Username" value="John" /> <label for="Email">Email:</label> <input id="Email" name="Email" value="john@email.com /> <!-- TODO: other form elements --> </form> <form> <label for="Id">Unique ID:</label> <input id="Id" name="Id" value="3" /> <label for="Username">Username:</label> <input id="Username" name="Username" value="David" /> <label for="Email">Email:</label> <input id="Email" name="Email" value="david@email.com /> <!-- TODO: other form elements --> </form> |
Notice how the model values are correctly applied to each Partial View, but the id and name attributes of the HTML input fields are always the same. This creates an evident naming conflict that will easily create issues, especially if we need to perform some DOM manipulation using JavaScript.
The problem can be easily spotted even without JavaScript: after rendering the above page on our favorite browser, we just have to click (or tap) on the <label> element. As we probably know, doing that would be supposed to move the focus to the input element having the ID defined in the for attribute of the label: however, our page contains multiple input elements with that same id, hence the focus will be always moved to the first of them - even if the selected label pertains to a different form.
The Solution
Luckily, the ASP.NET Core framework provides an easy fix for these kinds of issues: we just have to use the TemplateInfo.HtmlFieldPrefix property, which - if present - will be used to prepend a customizable prefix to all the relevant HTML form fields.
The property can be applied in several ways. For example, we can set it at the controller level:
1 2 |
ViewData.TemplateInfo.HtmlFieldPrefix = $"MyPrefix_{model.Id}"; return PartialView("UserFormPartial", model); |
or when calling the partial view from the main Razor page/view:
1 2 3 4 5 6 7 8 9 10 |
@model IEnumerable<UserViewModel> @foreach (var user in Model) { ViewDataDictionary viewData = new ViewDataDictionary(Html.ViewData) { TemplateInfo = new TemplateInfo { HtmlFieldPrefix = $"MyPrefix_{user.Id}" } }; } <partial name="UserFormPartial" model="user" view-data="viewData" /> } |
or even within the view itself:
1 2 |
@model UserViewModel ViewData.TemplateInfo.HtmlFieldPrefix = $"MyPrefix_{Model.Id}"; |
As soon as we do that, all naming conflicts will be resolved.
Conclusion
That's it: we hope that this solution will help other ASP.NET Core developers looking for a way to fix this nasty naming conflict issue.