2016-06-28 2 views
2

Обзорамногоразовый MVC кода: обработка типы значений в LINQ выражение

Я пишу набор MVC, который предназначен для обеспечения основного поиска/добавлять/редактировать/удалять функции для объекта Entity Framework. Он также выполняет некоторые причудливые вещи, такие как поддержка выпадающих списков, заполненных из других таблиц с использованием атрибутов.

Основной задачей в игре является наследуемым общий контроллер (AdminController<T>, где T является объектом EF), содержащий массив AdminField<T>, каждый элемент которого представляет собой поле/свойство в T. AdminController<T> может сгенерировать объект AdminViewModel<T>. Ниже представлена ​​диаграмма Visio отношений между этими объектами.

enter image description here

Проблема

Моя проблема заключается в соображениях. Чтобы отображать значения, я использую помощники Html.EditorFor/HiddenFor/TextboxFor. Гипер-упрощенное представление просто пересекает поле и отображает их значения, позволяя редактировать, если они являются редактируемыми полями. Это выглядит следующим образом:

@model AdminViewModel<ExampleEFObject> 
@using (Html.BeginForm()) 
{ 
    foreach (var f in Model.Fields) 
    { 
     var expression = Model.ExpressionFromField(f); 

     <label class="control-label col-md-3">@f.DisplayName</label> 

     @if (!f.Editable) 
     { 
      @Html.TextBoxFor(expression, new { @class = "form-control", @disabled = "disabled" }) 
     } 
     else 
     { 
      @Html.EditorFor(expression, new { htmlAttributes = new { @class = "form-control" } }) 
     } 
    } 
    <input type="submit" value="Save" class="btn btn-primary" /> 
} 

Ну, вот что я хочу это выглядеть. Он отлично работает с полями/свойствами, которые являются ссылочными типами. С типами значений, однако, я получаю System.InvalidOperationException ошибку на EditorFor линии: «Шаблоны могут быть использованы только с доступом поля, доступ к собственности, одномерный индекс массива или одного параметра выражений пользовательских индексатор

Это связано с тем, что метод ExpressionFromField, который возвращает выражение, обращающееся к экземпляру T в ViewModel, имеет тип возврата Expression<Func<AdminViewModel<T>, object>>. Поскольку он возвращает тип объекта, бокс принудительно генерирует выражение, так что вместо (например) t => t.Count мой метод фактически возвращает t => Convert(t.Count).

Текущий Обход

Мой текущий обходной путь, чтобы иметь второй метод, определенный в качестве Expression<Func<AdminViewModel<T>, int>> IntExpressionFromField(AdminField<T> field). Поскольку он возвращает int, он не форсирует бокс в выражении. Тем не менее, это делает мои взгляды крайне некрасиво:

@model AdminViewModel<ExampleEFObject> 
@using (Html.BeginForm()) 
{ 
    foreach (var f in Model.Fields) 
    { 
     var expression = Model.ExpressionFromField(f); 
     var intExpression = Model.IntExpressionFromField(f); 

     <label class="control-label col-md-3">@f.DisplayName</label> 

     @if (!f.Editable) 
     { 
      if (intExpression == null) 
      { 
       @Html.TextBoxFor(expression, new { @class = "form-control", @disabled = "disabled" }) 
      } 
      else 
      { 
       @Html.TextBoxFor(intExpression, new { @class = "form-control", @disabled = "disabled" }) 
      } 
     } 
     else 
     { 

      if (intExpression == null) 
      { 
       @Html.EditorFor(expression, new { htmlAttributes = new { @class = "form-control" } }) 
      } 
      else 
      { 
       @Html.EditorFor(intExpression, new { htmlAttributes = new { @class = "form-control" } }) 
      } 
     } 
    } 
    <input type="submit" value="Save" class="btn btn-primary" /> 
} 

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

Помощь!

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

Любая помощь была бы высоко оценена!

+0

В общем, плохая практика передачи вашего объекта EF для просмотра. Вам, вероятно, придется создать абстрактную форму FormViewModel, которая содержит реализации некоторых абстрактных FieldViewModels (с шаблонами редакторов, такими как DatePickerViewModel, TextBoxViewModel и т. Д.), А в вашем контроллере вам необходимо преобразовать эту модель представления для обновления операций для объекта Entity Framework или в DTO, если у вас есть сервисный уровень. –

+0

В целом это не стоит того, что не позволяет выполнять сложные макеты или причудливое взаимодействие с пользователем, поэтому переход на ViewModels и ручной код для обновления объектов жизнеспособен, если у вас не так много объектов –

+0

@raderick: Спасибо за отвечать на запросы. Я не передал свой объект EF в представление, а только общий ViewModel. Однако, если это плохая практика, мне бы хотелось понять, почему я могу выбрать лучший подход в будущем. Причина, по которой я делаю это, состоит в том, что существуют десятки типов объектов, для которых требуются только некоторые основные функции обслуживания. Я хочу, чтобы не было десятков одинаковых наборов MVC. Подход, который вы описали, кажется невероятно тяжелым и ограниченным, как вы сказали, но я буду помнить его в качестве резервного варианта. – Daniel

ответ

0

Невозможно исправить эту проблему, используя мой существующий подход, я пробовал подход, о котором я упомянул в конце: отказ от методов HtmlHelper. Это отлично поработало, и я бы хотел, чтобы я это сделал давно.

MVC ViewModel связывание осуществляется с помощью свойства «имя», так, чтобы вручную привязать к моей модели мне нужно, чтобы получить имя безоговорочной собственности (т.е. поле выражается как cust => cust.Records.Address.State, мне нужна строка SelectedItem.Records.Address.State (где SelectedItem является ViewModel объект, который содержит отображаемое значение). Мой AdminField<T> объект уже имел MemberExpression свойство, так что я просто использовал метод ToString(), удалил параметр, и предваряется имя объекта ViewModel. Некрасиво, я знаю, но надежно и легко.

Раньше мне нужно было выражение для подачи объекта HtmlHelper, но теперь мне просто нужно значение, поэтому я заменил свой метод ExpressionFromField() с более простым методом GetValue(), который возвращает значение string. Важно, чтобы он возвращал string; это преобразование позволяет мне читать и записывать все типы значений (поскольку привязка ViewModel обрабатывает преобразование редактируемых значений обратно в исходный тип свойства).

Мои новые взгляды выглядеть следующим образом:

@model AdminViewModel<ExampleEFObject> 
@using (Html.BeginForm()) 
{ 
    foreach (var f in Model.Fields) 
    { 
     var propertyName = f.PropertyName(f); //unqualified, can contain multiple parts 
     var value = Model.GetValue(f);  //uses the field expression to retrieve the value 
               //of a "T SelectedItem" object in the ViewModel 
     <label class="control-label col-md-3">@f.DisplayName</label> 

     @if (!f.Editable) 
     { 
      <input class="form-control" disabled="disabled" name="@propertyName" type="text" value="@value" /> 
     } 
     else 
     { 
      <input class="form-control" name="@propertyName" type="text" value="@value" /> 
     } 
    } 
    <input type="submit" value="Save" class="btn btn-primary" /> 
} 

намного чище!

Одна вещь, которую я теряю при таком подходе, является валидацией. Отсюда я могу добавить проверку javascript или создать шаблоны для дублирования проверки данных, которую автоматически добавляет HtmlHelper. В любом случае, ничего страшного.

Надеюсь, это поможет кому-то.

Примечание: @Ivan Stoeve рекомендуется использовать Html.Editor/Html.Textbox и т. Д., Которые принимают привязку с помощью имени свойства, а не выражения. Это все еще оставляет меня без строго типизированных выражений в представлении, но оно возвращает мне подтверждение, поэтому я думаю, что это стоит подправить. Он также очистит некоторые уродливые манипуляции с строками, которые я должен был сделать, добавляя варианты выпадающего списка. :)

+2

Не можете ли вы использовать методы 'HtmlHelper', которые принимают' string' вместо 'Expression' (те, которые не имеют суффикса' For', например 'TextBox', а не' TextBoxFor' и т. Д.)? –

+0

Спасибо за предложение, я мог бы это использовать.В конечном итоге обучение для меня было обязательной моделью и вытащить имя свойства в представление; вся идея использования «HtmlHelper» заключалась в том, чтобы использовать строго типизированное выражение, сохраняя многоразовый макет. Тем не менее, это, по крайней мере, даст мне бесплатную проверку. Я возьму это. – Daniel

Смежные вопросы