1
votes

I have a page which is comprised of other pages which are both partials and editor templates. I have the templates showing properly in both cases with the exception of validation messages. When I render the razor page template shown below as a partial using @Html.Partial(...) I get the expected validation messages next to each of the fields (but form data is not included from fields within the template rendered as a Partial) when I use the @Html.EditorFor(...) helper the page is rendered as expected and the field values are included as part of the form data but for some reason the validation messages aren't showing. It is the exact same template just using a different html helper.

Razor page template:

@ModelType PreferencesModel

@If Model.Preferences.Count > 0 Then
    @For Each kvp As KeyValuePair(Of String, Pref) In Model.Preferences
        @<div class="row">
            <div class="col-md-2">
                @Html.Label(Model.Preferences(kvp.Key).Name)
            </div>
            <div class="col-md-2">
                @If kvp.Value.IsReadOnly Then
                    @kvp.Value.CurrentValue
                Else
                    @Html.TextBoxFor(Function(m) m.Preferences(kvp.Key).CurrentValue)
                End If
            </div>
            <div class="col-md-2">
                @Html.ValidationMessage(kvp.Key)
            </div>
        </div>
    Next
End If

Razor parent page:

@ModelType EditPreferencesModel

@Using Html.BeginForm("EditPrefs", "PrefsController", FormMethod.Post, New With {.id = "updatePrefs", .class = "form-horizontal", .role = "form"})
    @Html.AntiForgeryToken

    @If Not Model.PreferencesModel Is Nothing Then
        @Html.EditorFor(Function(m) m.PreferencesModel) 
        @*@Html.Partial("~/Views/Prefs/EditorTemplates/Pref.vbhtml", Model.PreferencesModel)*@
    End If

End Using

Models:

public class Pref
{
    public string Name { get; set; }
    public string CurrentValue { get; set; }
}

public class PreferencesModel
{
    public Dictionary<string, Pref> Preferences { get; set; }
}

public class EditPreferencesModel 
{
    public PreferencesModel PreferencesModel { get; set; }
}

PrefsController Action:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult EditPrefs(EditPreferencesModel model, string id)
{
    try
    {
        bool preferenceChangesAreValid = false;
        try {
            ValidatePreferenceChanges(model.PreferencesModel);
            preferenceChangesAreValid  = true;
        }
        catch(Exception e)
        {
            Logger.Error("Invalid preference information entered. Error: " + e.ToString());
        }

        if (preferenceChangesAreValid)
        {
            UpdatePreferencesFromViewModel(model.PreferencesModel);
            ... redirect to another view ...
        }

        model = PrepareEditPreferencesViewModel(model);
    }
    catch (Exception e)
    {
        Logger.Error("An unexpected error occurred. Error: " + e.ToString());
        ModelState.AddModelError("", "An error occurred with your request, please try again later.");
    }

    return View(model);
}


private void ValidatePreferenceChanges(PreferencesModel model)
{
    ... iterate preferences and validate ...

    if (preferenceIsInvalid)
        ModelState.AddModelError(kvp.Key, "Numeric value expected.");

    ...
}

As I mentioned above, when rendering this as a partial, the Model errors are displayed as expected but when I render this as an EditorFor template the errors aren't displayed.

I've set a breakpoint on the template when using EditorFor and upon inspection of the ViewState I can see that the errors I expect to see on the validation message fields are indeed present in the ModelState.

Am I misusing EditorFor?
Is there an extra step needed to get the template when using EditorFor to recognize the errors for each of the fields? Is this expected behavior for EditorFor?

2
Maybe I'm missing something, but your EditPreferencesModel class has no property PreferencesModel, so when your view's model is EditPreferencesModel and you reference Model.PreferencesModel you should be getting a runtime error.Chris Pratt
Nope, I was missing that model. Sorry, I had to strip a bunch of stuff out of this example and rename a number of items so I could post it and forgot to include that model. Question updated.jollyRoger

2 Answers

1
votes

After many hours looking through various resources for the answer to my problem the only answer I could come up with was that the Html.ValidationMessage() helper functionality simply doesn't work for Editor Templates. This, to me, seems like an oversight as I'm yet to see or hear a good reason why it would behave differently than the Partial in this particular respect especially since the Editor Templates can be used on whole models and not just one field. So, I appear to be using the Editor Template concept properly and its the only way I've found thus far to have a template's fields included as part of the form being posted.

Ultimately I had to work around this by manually checking for the existence of and displaying the error messages that are available.

So instead of the usual (works with a Partial but not an Editor):

@Html.ValidationMessage(kvp.Key)

I had to go with (works with both Partial and Editor):

@If ViewData.ModelState.ContainsKey(kvp.Key) AndAlso ViewData.ModelState(kvp.Key).Errors.Count >= 1 Then
    @ViewData.ModelState(kvp.Key).Errors(0).ErrorMessage
End If
0
votes

Not sure why it would have worked before, but your validation message should use the same property lambda as you're using for TextBoxFor or whatever. In other words:

@Html.ValidationMessageFor(Function(m) m.Preferences(kvp.Key).CurrentValue)