Использование MVC View LabelFor/TextFor с выражением с неизвестным возвращаемым типом

Что я пытаюсь сделать

У меня есть веб-приложение, в котором у пользователя есть административный портал, который можно использовать для редактирования определенных настроек. Существует множество редактируемых настроек, поэтому я хотел бы создать единый набор наследуемых объектов MVC, чтобы я мог просто указать объект настроек (который представляет собой просто строку в таблице), и он будет отображать настройки, предлагать функции поиска , возможность добавлять/удалять записи и т.д.

Что у меня есть сейчас

Создан наследуемый объект контроллера. Он работает, беря тип объекта (например, объект User) и создавая массив объектов AdminField. Каждый представляет строку в базе данных. Производный класс контроллера также передает метаданные для каждого поля в виде флагов, поэтому код производного контроллера будет выглядеть следующим образом:

public class UserController : AdminBaseController<UserViewModel, User>
{
    public UserController(Context context) : 
        base(context, new AdminField<User>[] {
            new AdminField<User>(u => u.UserId, AdminFieldFlags.Hidden | AdminFieldFlags.IsID),
            new AdminField<User>(u => u.UserName, AdminFieldFlags.Searchable | AdminFieldFlags.Sortable | AdminFieldFlags.Editable | AdminFieldFlags.EditLink, "User Name"),
            new AdminField<User>(u => u.Password, AdminFieldFlags.Editable),
            new AdminField<User>(u => u.AccessLevel, AdminFieldFlags.Editable)
        }, "User")
    {
    }
}

Первый параметр — это лямбда, которая показывает, как получить указанное поле из объекта.

Вот сигнатура метода для конструктора AdminField:

public AdminField(Expression<Func<TMaintainedType, object>> field, AdminFieldFlags flags, string displayName = null)

Данные поля также доступны для ViewModel, поэтому в представлении у нас есть такой код:

@foreach (var f in Model.Fields.Where(f => !f.Hidden))
{
    //Gets original expression for this field
    var expression = Model.ExpressionFromField<UserViewModel>(f);
    <div class="form-group">
        @Html.LabelFor(expression, new { @class = "control-label col-3" })
        @Html.EditorFor(expression, new { htmlAttributes = new { @class = "form-control" } })
    </div>
}

Виола! Метка поля и поле редактирования отображаются!

Проблема

Сигнатура метода для AdminField принимает объект Expression<Func<TMaintainedType, object>>. Это прекрасно работает для strings, но (исходя из моего ограниченного понимания) вызывает упаковку/распаковку для int полей, и, таким образом, сохраненное выражение изменяется с (например) u => u.UserId на u => Convert(u.UserId). Поскольку я сохраняю массив полей, возвращаемый тип должен быть object (поскольку он может отличаться), поэтому я не могу удалить Convert.

В результате, когда поле захватывает выражение и поле равно int32, я получаю ошибку System.InvalidOperationException в строке LabelFor: Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions.

Вопрос

Как я могу динамически преобразовать Expression<Func<T, object>> в Expression<Func<T, int>>?

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

  1. Мне нужно каждый раз проверять целое число (что делает МНОГО операторов If в представлениях), и
  2. Если мне нужен какой-либо другой тип значения, мне нужно будет создать еще один специальный метод и использовать операторы switch/if в представлениях, чтобы выбрать правильный метод.

Помощь! Дайте мне знать, если вам нужна дополнительная информация; всегда трудно понять, что включить.


person Daniel    schedule 07.06.2016    source источник
comment
из интереса, почему вы не использовали параметр типа вместо объекта? например Expression<Func<TMaintained, TReturn>>   -  person The Bearded Llama    schedule 29.06.2016
comment
Тип возвращаемого значения связан с AdminField<T>. Использование параметра типа возвращаемого значения потребует изменения его на AdminField<T, TReturn>. Мне нужно иметь массивы объектов AdminField с разными типами возврата, так что это не работает. Единственные альтернативы, которые я мог придумать (например, ковариация), будут работать только со ссылочными типами, а большинство моих типов являются типами значений.   -  person Daniel    schedule 29.06.2016
comment
Я ломал голову над этим, но пока не решился; интересная задача ;)   -  person The Bearded Llama    schedule 30.06.2016
comment
@TheBeardedLlama, я ценю это. :) Мое решение плана B ниже немного улучшилось за счет использования методов HtmlHelper без выражений (т.е. Html.Editor вместо Html.EditorFor, что в основном просто дает мне бесплатную проверку MVC. Это не решает основную проблему, но обходной путь постепенно становится все меньше болезненный.   -  person Daniel    schedule 30.06.2016


Ответы (1)


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

Я отказался от использования объектов HtmlHelper и вместо этого использовал свойства name элементов html для привязки значений к модели данных. Поскольку name — это string, я мог преобразовать все в string, решив мою проблему с типом данных. А привязка MVC обрабатывает преобразование отредактированных значений обратно в их исходные типы. Эта комбинация дает мне двустороннюю поддержку для всех необходимых мне типов данных (strings, типов значений и nullable типов значений).

person Daniel    schedule 29.06.2016