Каков наилучший способ отображения в представлении большего количества типов, полученных из абстрактной базы?
Например, у меня есть эти три класса:
public abstract class AbstractBase
{
public int A { set; get; }
}
public class Derived1 : AbstractBase
{
public int B { set; get; }
public int C { set; get; }
}
public class Derived2 : AbstractBase
{
public int D { set; get; }
}
И сервис, который возвращается со списком AbstractBase
public class AbstractBaseService
{
public IEnumerable<AbstractBase> GetInstances()
{
var results = new List<AbstractBase>();
// TODO: Get data
return results;
}
}
И я хочу отобразить эти классы в строго типизированном ASP.NET MVC View в таблице (или GridView в WF). Так какой же самый лучший способ? А что если я хочу добавить опцию редактирования?
1 ответ
Вы можете использовать шаблоны отображения в этом сценарии.
Идея состоит в том, чтобы создать шаблон отображения для каждого конкретного производного типа и оставить MVC задачу выяснения конкретного типа каждого элемента в списке во время выполнения, а затем подобрать соответствующий шаблон.
Представьте, что у нас есть следующий метод действия контроллера, который возвращает представление для отображения списка объектов AbstractBase:
public ActionResult Foo()
{
var list = new List<AbstractBase>()
{
new Derived1{ A = 1, B = 2, C = 3},
new Derived2{ A = 1, D = 4},
};
return View(list);
}
Вы можете создать следующие шаблоны:
~ / Views / Shared / DisplayTemplates / Derived1.cshtml
@model MvcApplication1.Models.Derived1
<td>
@Html.DisplayFor(m => m.A)
</td>
<td>
@Html.DisplayFor(m => m.B)
</td>
<td>
@Html.DisplayFor(m => m.C)
</td>
<td></td>
~ / Views / Shared / DisplayTemplates / Derived2.cshtml
@model MvcApplication1.Models.Derived2
<td>
@Html.DisplayFor(m => m.A)
</td>
<td></td>
<td></td>
<td>
@Html.DisplayFor(m => m.D)
</td>
К сожалению, поскольку вы хотите отобразить элементы в таблице, вам нужно будет предоставить пустые элементы tr для столбцов, которые не относятся к каждому конкретному типу.
И основной вид ~/Views/Home/Foo.cshtml будет выглядеть так:
@model IEnumerable<MvcApplication1.Models.AbstractBase>
<table>
<thead>
<tr>
<th>Col A</th>
<th>Col B</th>
<th>Col C</th>
<th>Col D</th>
</tr>
</thead>
<tbody>
@foreach(var item in Model)
{
<tr>
@Html.DisplayFor(m => item)
</tr>
}
</tbody>
</table>
Он берет список объектов AbstractBase и строит тело таблицы, перебирая список и вызывая DisplayFor для каждого элемента. MVC во время выполнения проверит конкретный тип каждого элемента, а затем выполнит поиск соответствующего представления в папке DisplayTemplates.
Это самый простой подход, когда поля в общем базовом классе отображаются каждым конкретным шаблоном производного класса. Однако имеет смысл иметь общий шаблон для полей в базовом классе.
Вы можете создать другой шаблон отображения для полей в базовом классе: ~/Views/Shared/DisplayTemplates/AbstractBase.cshtml
@model MvcApplication1.Models.AbstractBase
<td>
@Html.DisplayFor(m => m.A)
</td>
Как будет называться этот шаблон для полей в общем базовом классе, зависит от того, используете вы MVC 4 или нет. Если вы используете MVC 4, вы можете вызывать базовый шаблон из каждого из шаблонов для производных классов. Например, шаблон для Derived1 будет выглядеть так:
@model MvcApplication1.Models.Derived1
@Html.DisplayFor(m => m, "AbstractBase")
<td>
@Html.DisplayFor(m => m.B)
</td>
<td>
@Html.DisplayFor(m => m.C)
</td>
<td></td>
Если вы не используете MVC 4, печально вложенные редакторы экрана не работают, поэтому строка @Html.DisplayFor(m => m, "AbstractBase")
выше ничего не сделаю.
Ваш первый и лучший вариант за пределами MVC4 - вручную отрисовать шаблон для базового класса как частичное представление. Эта строка будет заменена @Html.Partial("~/Views/Shared/DisplayTemplates/AbstractBase.cshtml", Model)
Другой вариант, который я бы порекомендовал вам избегать, - это перенести вызов шаблона абстрактного класса в главное представление вместо вызова из каждого из шаблонов производных классов. Это может даже не быть допустимым вариантом, если шаблон для базового класса должен быть встроен в некоторый элемент, сгенерированный каждым из производных классов. Например, это не будет возможным, если столбцы базового класса были не все в начале (или конце) каждой строки
Итак, основной вид, который отображает вид, будет делать:
@foreach(var item in Model)
{
<tr>
@Html.DisplayFor(m => item, "AbstractBase")
@Html.DisplayFor(m => item)
</tr>
}
Я бы всегда придерживался вызова базового шаблона из каждого из производных шаблонов, и я бы сказал, что вы должны быть очень осторожны, если вы когда-нибудь обнаружите, что используете последний подход. В противном случае вам нужно помнить, что звонил @Html.DisplayFor(m => item, "AbstractBase")
каждый раз, когда вы хотите отобразить производный шаблон и некоторые html-проекты, возможно, даже не удастся достичь с помощью этого подхода.
Надеюсь, поможет!