MVC, дочерние объекты, EditorFor и ненавязчивая проверка

Это, вероятно, будет громогласным...

Итак, я создаю веб-сайт MVC4, который имеет умеренно частую тему, позволяющую редактировать родительскую запись, а также добавлять / удалять / редактировать дочерние записи на той же странице. Используя магию MVC, я могу определить частичное представление для дочерних записей следующим образом:

@model NFBC.Models.SubMap

<tr id="@String.Concat("SubMap", ViewBag.Index)">
    <td class="mapname">
        <input type="hidden" name="submaps[@ViewBag.Index].Id" value="@Model.Id" />
        <input type="text" name="submaps[@ViewBag.Index].MapName" value="@Model.MapName" />
    </td>
    <td class="miles"><input type="text" name="submaps[@ViewBag.Index].Miles" value="@Model.Miles" /></td>
    <td class="difficulty">@Html.DropDownList("submaps[" + (string)ViewBag.Index.ToString() + "].DifficultyId", (SelectList)ViewBag.Difficulty(Model.DifficultyId))</td>
    <td class="elevation"><input type="text" name="submaps[@ViewBag.Index].Elevation" value="@Model.Elevation" /></td>
    <td class="mapfile"><input type="text" name="submaps[@ViewBag.Index].MapFile" value="@Model.MapFile" /></td>
    <td class="delete"><img src="~/Images/Error_red_16x16.png" /></td>
</tr>

Затем в родительском представлении я просто вызываю частичное представление, чтобы отобразить все его дочерние элементы:

<table id="ChoicesTable">
    <thead>
        <tr>
            <th>Map Name</th>
            <th>Miles</th>
            <th>Difficulty</th>
            <th>Elevation</th>
            <th>Map File</th>
            <th></th>
        </tr>
    </thead>
    for (int i = 0; i < Model.SubMaps.Count; i++)
    {
        var map = Model.SubMaps.ElementAt(i);
        ViewBag.Index = i;
        Html.RenderPartial("_MapChoiceEditRow", map);
    }
</table>

Я не могу найти какую-либо документацию по синтаксису имени "sub entity" (то есть: name="subMaps[@ViewBag.Index].Id"), но он работает при привязке к модели; все дочерние элементы заполняются до тех пор, пока значения индекса начинаются с 0 и не пропускают никаких значений (т. е. 0, 1, 2, 4, 5 приведут к привязке только 0, 1 и 2). Используя магию вызова jQuery Ajax, я могу динамически вставлять и удалять строки на стороне клиента.

Проблема в том, что я просто не могу найти способ надежно использовать @Html.EditorFor() с элементами управления дочерней сущности. Это была бы действительно хорошая функциональность, поскольку EditorFor вставляет все ненавязчивые атрибуты проверки jquery в html. Прямо сейчас я вынужден эмулировать это поведение, добавляя свои собственные теги data-val='true' (не показанные в примере, я еще этого не делал) везде, что мне кажется крайне грязным,

Так что у меня появилась блестящая идея взять встроенные шаблоны и создать свои собственные шаблоны для внедрения этого материала (а также некоторые другие мои собственные вещи, такие как атрибут местозаполнителя Bootstraps для текста "помощник" и, возможно, всплывающие подсказки, так далее). Я скачал исходный код MVC и открыл шаблоны редактора по умолчанию, но вместо того, чтобы увидеть разметку, которая отображает ненавязчивые значения, я просто получаю целую кучу вспомогательных функций, которые в какой-то момент "магическим образом" отображают ненавязчивые атрибуты. Я не могу понять, как это делается, поскольку все средства проверки упакованы во внутренние классы, которые мне недоступны.

Я что-то здесь упускаю или это просто слабость MVC, которую мне придется обойти. Я бы очень хотел, чтобы мне не нужно было эмулировать ненавязчивый код генерации атрибутов проверки самостоятельно, но если это единственное решение, я думаю, я мог бы это сделать...

Спасибо!

1 ответ

Решение

Я провел день, играя с исходным кодом еще немного, и обнаружил несколько ключевых вспомогательных методов, которые позволяли мне делать то, что мне нужно было делать.

Это не самая красивая вещь в мире, но она очень сильно автоматизирует то, что я делал раньше. Мое решение состояло в том, чтобы создать новый вспомогательный метод TextBoxFor с именем TextBoxForChild:

    public static MvcHtmlString TextBoxForChild<TModel, TProperty>(
            this HtmlHelper<TModel> htmlHelper,
            Expression<Func<TModel, TProperty>> expression,
            string parentName,
            int index,
            IDictionary<string, object> htmlAttributes)
    {
        var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
        var rules = ModelValidatorProviders.Providers.GetValidators(metadata, htmlHelper.ViewContext).SelectMany(v => v.GetClientValidationRules());

        if (htmlAttributes == null)
            htmlAttributes = new Dictionary<String, object>();
        if (!String.IsNullOrWhiteSpace(metadata.Watermark))
            htmlAttributes["placeholder"] = metadata.Watermark;
        UnobtrusiveValidationAttributesGenerator.GetValidationAttributes(rules, htmlAttributes);

        return htmlHelper.TextBox(String.Format("{0}[{1}].{2}", parentName, index, metadata.PropertyName), metadata.Model, htmlAttributes);
    }

Я получаю объект "ModelMetadata", а затем генерирую "правила проверки модели" для модели. Затем я заполняю свой собственный атрибут html-заполнителя значением метаданных водяного знака и, наконец, вызываю "UnobtrusiveValidationAttributesGenerator.GetValidationAttributes", который заполняет мой словарь атрибутов html всеми атрибутами проверки.

Затем я делаю произвольное форматирование строки, чтобы убедиться, что имя входа соответствует формату дочернего объекта MVC, и вуаля, это работает!

К вашему сведению, если кому-то интересно, откуда берется это значение "Водяной знак", это значение из "DisplayAttribute", называемое "Prompt":

public class FamilyMember
{
    public int ClubNumber { get; set; }

    [Display(Name="Name", Prompt="Name")]
    [Required]
    public string Name { get; set; }
    public string Cell { get; set; }
    public string Email1 { get; set; }
    public string Email2 { get; set; }
    public DateTime? BirthDate { get; set; }
}

Прекрасный!

Другие вопросы по тегам