3

I've got a view model that contains 5 instances of a class as sub-properties. These sub-properties are rendered using a partial view, as follows:

<%Html.RenderPartial("_EntryItemForm", Model.EntryItem1, new ViewDataDictionary { TemplateInfo = new System.Web.Mvc.TemplateInfo { HtmlFieldPrefix = "EntryItem1" } }); %>        
<%Html.RenderPartial("_EntryItemForm", Model.EntryItem2, new ViewDataDictionary { TemplateInfo = new System.Web.Mvc.TemplateInfo { HtmlFieldPrefix = "EntryItem2" } }); %>
<%Html.RenderPartial("_EntryItemForm", Model.EntryItem3, new ViewDataDictionary { TemplateInfo = new System.Web.Mvc.TemplateInfo { HtmlFieldPrefix = "EntryItem3" } }); %>
<%Html.RenderPartial("_EntryItemForm", Model.EntryItem4, new ViewDataDictionary { TemplateInfo = new System.Web.Mvc.TemplateInfo { HtmlFieldPrefix = "EntryItem4" } }); %>
<%Html.RenderPartial("_EntryItemForm", Model.EntryItem5, new ViewDataDictionary { TemplateInfo = new System.Web.Mvc.TemplateInfo { HtmlFieldPrefix = "EntryItem5" } }); %>

Within the partial view, I have the following (showing one field only):

<%: Html.LabelFor<EntryItemForm, string>(x => x.ItemName)%>
<%: Html.TextBoxFor<EntryItemForm, string>(x => x.ItemName)%>
<%: Html.ValidationMessageFor(x => x.ItemName)%>

The label and textboxes both render with the correct ids, names and so on, and the default model binder handles everything perfectly.

Unfortunately, even when the ModelState contains an error for the ItemName field, the ValidationMessage never appears. If I add a ValidationSummary to the parent view, the error is displayed. Normally I'd just use a ValidationSummary and move on, but the design I'm working to requires inline validation messages.

Does anyone have any idea why this might be?

4 Answers 4

9

For someone who are googling for this trouble:

Problem caused in a reason that ModelState doesn't passes to partial view in a right way when HtmlFieldPrefix used. This html helper solved the problem(for me) and validation errors displays correct:

public static void RenderPartialWithPrefix(this HtmlHelper helper, string partialViewName, object model, string prefix)
    {
        ViewDataDictionary WDD = new ViewDataDictionary {TemplateInfo = new System.Web.Mvc.TemplateInfo { HtmlFieldPrefix = prefix } };

        foreach(string key in helper.ViewData.ModelState.Keys)
        {
            if(key.StartsWith(prefix+"."))
            {
                foreach (ModelError err in helper.ViewData.ModelState[key].Errors)
                {
                  if(!string.IsNullOrEmpty(err.ErrorMessage))
                    WDD.ModelState.AddModelError(key, err.ErrorMessage);
                  if (err.Exception != null)
                      WDD.ModelState.AddModelError(key, err.Exception);
                }
                WDD.ModelState.SetModelValue(key, helper.ViewData.ModelState[key].Value);
            }
        }

        helper.RenderPartial(partialViewName,model,WDD);
    }

just use it for render partial views with prefix

@{Html.RenderPartialWithPrefix("_StructureEditSharePartView", Model.sharePart,"sharePart");}
Sign up to request clarification or add additional context in comments.

3 Comments

This was a huge help, thanks! I also ended up creating a version for Partial called PartialWithPrefix, which calls helper.Partial (instead of helper.RenderPartial) at the end, and returns MvcHtmlString.
i had to add another conditional for cases where my partial is a list and has [0] indexes in the html. if (key.StartsWith(prefix + "[")) here is my key in the modelstate Command.PSoftAccessRequirements.PSoftAccountsPayable.VoucherApprovalMatrix[0].LEVELValidation prefix = Command.PSoftAccessRequirements.PSoftAccountsPayable.VoucherApprovalMatrix
This was a lifesaver, thank you! This should be a accepted answser. Make sure the casing of 'prefix' matches ModelState.Keys. I used ToLowerInvariant() to check if the key starts with the prefix.
3

Maybe the name of the field containing validation error in the metadata doesn't match the name of the field generated by the TextBoxFor helper. How about using editor templates? This way you don't need to bother with prefixes, setting template infos, problems with validation, ...

So you could define (~/Views/Shared/EditorTemplates/TypeOfEntryItem.ascx):

<%@ Control 
    Language="C#" 
    Inherits="System.Web.Mvc.ViewUserControl<AppName.Models.TypeOfEntryItem>"
%>
<%= Html.LabelFor(x => x.ItemName) %>
<%= Html.TextBoxFor(x => x.ItemName) %>
<%= Html.ValidationMessageFor(x => x.ItemName) %>

and in the main view simply:

<% using (Html.BeginForm()) { %>
    <%= Html.EditorFor(x => x.EntryItem1) %>
    <%= Html.EditorFor(x => x.EntryItem2) %>
    <%= Html.EditorFor(x => x.EntryItem3) %>
    <%= Html.EditorFor(x => x.EntryItem4) %>
    <%= Html.EditorFor(x => x.EntryItem5) %>

    <input type="submit" value="OK" />
<% } %>

This even works with collections if you don't want to bother creating 5 properties on your model. You could have a simple property:

public IEnumerable<TypeOfEntryItem> EntryItems { get; set; }

and then:

<% using (Html.BeginForm()) { %>
    <%= Html.EditorFor(x => x.EntryItems) %>
    <input type="submit" value="OK" />
<% } %>

which will render the editor template for each item in the collection and of course take care of generating proper ids, names, ...

4 Comments

Thanks for the advice. I tried using EditorFor as you specifed, and I got precisely nothing: no errors and no output! Any ideas about what might cause that?
@Paul Suart, the name and location of the editor template is important. It must be located in the ~/Views/Shared/EditorTemplates folder and named XXX.ascx where XXX is the name of the type it is strongly typed to. This must be the type of the property EntryItem1.
Hmm, I got EditorFor<> going (was my own fault, as I hadn't realised it output an MvcHtmlString instead of writing to the response), but still no joy. Have updated the question.
Correction - sorry EditorFor<> does work properly. It doesn't work, however, if I try to render an IEnumerable<EntryFormItem> - throwing an Argument Exception "Illegal characters in path". I assume I don't need to rename my view "IEnumerable<EntryItemForm>"! I'm also slightly bewildered as to why my first (less elegant) solution didn't work properly.
3

Adding the Html.ViewData object to the constructor of the ViewDataDictionary also works.

<%Html.RenderPartial("_EntryItemForm", Model.EntryItem1, new ViewDataDictionary(Html.ViewData) { TemplateInfo = new System.Web.Mvc.TemplateInfo { HtmlFieldPrefix = "EntryItem1" } }); %>

What this does is, it gives the current context (including the ModelState) to the newly created ViewDataDictionary, instead of giving it a separate context. This way, there is no need for an extension method.

Comments

1

http://www.dalsoft.co.uk/blog/index.php/2010/04/26/mvc-2-templates/#Complex_Types

Regarding your last question about IEnumerable, check out the article above for a decent workaround. Also, I wish someone would answer your question about making things work with the htmlFieldPrefix ... "elegant" or not, there's no point in supporting the display/rendering/processing of the view if you can't support the validation messages. I'm trying to get EditorFor to work for me as well (now) but if I ever get the time and return to making it work with RenderPartial, I'll come back here and post the solution.

For those who may find their way here by Googling the problem, the cause is that the ModelState contains keys like "ComplexObjectProperty.ModelProperty" but the ValidationMessageFor tries to look up just "ModelProperty". Major oversight.

1 Comment

So I have to use a UIHint? Looks like Pixie Magic to me! I too had reached the conclusion that ValidationMessageFor fails to look-up the right key in ModelState.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.