Передайте подкласс общей модели в режим бритвы

Большое изображение:

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

Игроки:

Скажем, у меня есть такая модель:

public abstract class BaseFooModel<T>
    where T : BaseBarType
{
    public abstract string Title { get; } // ACCESSED BY VIEW
    public abstract Table<T> BuildTable();

    protected Table<T> _Table;
    public Table<T> Table // ACCESSED BY VIEW
    {
        get
        {
            if (_Table == null)
            {
                _Table = BuildTable();
            }
            return _Table;
        }
    }
}

И подкласс вроде этого:

public class MyFooModel : BaseFooModel<MyBarType>
{
    // ...
}

public class MyBarType : BaseBarType
{
    // ...
}

Я хочу, чтобы передать MyFooModel в вид бритвы, который определяется следующим образом:

// FooView.cshtml
@model BaseFooModel<BaseBarType>

Но это не работает. Я получаю ошибку времени выполнения, говоря, что FooView ожидает BaseFooModel<BaseBarType>, но получает MyFooModel. Напомним, что MyFooModel в атрибутах от BaseFooModel<MyBarType> и MyBarType наследуется от BaseBarType.

Что я пробовал:

Я попробовал это в небезонной стране, чтобы узнать, правда ли это, что это такое. Мне пришлось использовать параметр шаблона в представлении, чтобы заставить его работать. Вот что выглядит не-бритва:

public class FooView<T>
    where T : BaseBarType
{
    BaseFooModel<T> Model;
    public FooView(BaseFooModel<T> model)
    {
        Model = model;
    }
}

С этой структурой следующий работает:

new FooView<MyBarType>(new MyFooModel());

Мой вопрос:

Как я могу сделать это с помощью Razor? Как передать тип, например, я делаю с FooView?
Я не могу, но есть ли способ обойти это? Могу ли я как-то достичь той же архитектуры?

Сообщите мне, могу ли я предоставить дополнительную информацию. Я использую .NET 4 и MVC 3.


EDIT:
На данный момент я просто добавляю вид бритвы для каждого подкласса BaseFooModel<BaseBarType>. Я не волнуюсь об этом, потому что я не хочу создавать новое представление каждый раз, когда добавляю новую модель.

Другой вариант - просто воспользоваться тем фактом, что я могу заставить это работать в обычных классах С# без бритвы. Я мог бы просто просмотреть вид бритвы @inherits С# view, а затем вызвать метод рендеринга. Мне не нравится этот параметр, потому что мне не нравится иметь два способа рендеринга html.

Любые другие идеи? Я знаю, что трудно понять контекст проблемы, когда я даю имена классов Foo и Bar, но я не могу предоставить слишком много информации, так как это немного чувствительно. Мои извинения об этом.


Что я до сих пор использовал, используя Бенджамин:

public interface IFooModel<out T> 
    where T : BaseBarModel
{
    string Title { get; }
    Table<T> Table { get; } // this causes an error:
                            // Invalid variance: The type parameter 'T' must be 
                            // invariantly valid on IFooModel<T>.Table. 
                            // 'T' is covariant.
}

public abstract class BaseFooModel<T> : IFooModel<T>
    where T : BaseBarModel
{
    // ...
}

Что закончилось:

public interface IFooModel<out T> 
    where T : BaseBarModel
{
    string Title { get; }
    BaseModule Table { get; } // Table<T> inherits from BaseModule
                              // And I only need methods from BaseModule
                              // in my view. 
}

public abstract class BaseFooModel<T> : IFooModel<T>
    where T : BaseBarModel
{
    // ...
}

Ответ 1

В иерархию классов вам необходимо ввести интерфейс с ковариантным параметром типа:

public interface IFooModel<out T> where T : BaseBarType
{
}

И выведите BaseFooModel из указанного выше интерфейса.

public abstract class BaseFooModel<T> : IFooModel<T> where T : BaseBarType
{
}

В вашем контроллере:

[HttpGet]
public ActionResult Index()
{
    return View(new MyFooModel());
}

Наконец, обновите параметр модели просмотра:

@model IFooModel<BaseBarType>

Ответ 2

Использование моделей на основе интерфейсов было преднамеренно изменено между ASP.NET MVC 2 и MVC 3.

Вы можете увидеть здесь

Команда MVC:

Наличие интерфейсных моделей не является чем-то, что мы поощряем (и, учитывая ограничения, налагаемые исправлением ошибок, может реально поддерживать). Переключение на абстрактные базовые классы устранит проблему.

"Скотт Гензельман"

Ответ 3

Проблема, с которой вы столкнулись, - это не ошибка Razor, а ошибка С#. Попробуйте сделать это с помощью классов, и вы получите ту же ошибку. Это связано с тем, что модель не BaseFooModel<BaseBarType>, а BaseFooModel<MyFooModel>, и неявное преобразование не может произойти между ними. Обычно в программе вам нужно сделать преобразование, чтобы сделать это.

Однако с .NET 4 введена контравариантность и ковариация, которая звучит как способность того, что вы ищете. Это только .NET 4, и я честно не знаю, использует ли Razor в .NET 4 или нет.