Составление полиморфных объектов в проекте ASP.NET MVC3
Суть моего вопроса заключается в том, как правильно составить эти объекты (см. Ниже) с помощью MVC3 и Ninject (хотя я не уверен, что DI должен играть роль в решении). Я не могу раскрыть реальные детали моего проекта, но вот приблизительное, которое иллюстрирует проблему / вопрос. Ответы в VB или C# приветствуются!
У меня есть несколько различных продуктов с различными свойствами, но все они должны быть представлены в каталоге. Каждый класс продуктов имеет соответствующую таблицу в моей базе данных. Запись каталога имеет несколько свойств, характерных для того, чтобы быть записью каталога, и, следовательно, имеет свою собственную таблицу. Я определил интерфейс для записей каталога с намерением, что вызов свойства DescriptionText даст мне очень разные результаты, основанные на базовом конкретном типе.
Public Class Clothing
Property Identity as Int64
Property AvailableSizes As List(Of String)
Property AvailableColor As List(Of String)
End Class
Public Class Fasteners
Property Identity as Int64
Property AvailableSizes As List(Of String)
Property AvailableFinishes As List(Of String)
Property IsMetric As Boolean
End Class
Public Interface ICatalogEntry
Property ProductId as Int64
Property PublishedOn As DateTime
Property DescriptionText As String
End Interface
Учитывая, что DescriptionText является проблемой уровня представления, я не хочу реализовывать интерфейс ICatalogEntry в моих классах продуктов. Вместо этого я хочу передать это какому-то форматеру.
Public Interface ICatalogEntryFormatter
Property DescriptionText As String
End Interface
Public Class ClothingCatalogEntryFormatter
Implements ICatalogEntryFormatter
Property DescriptionText As String
End Class
Public Class FastenerCatalogEntryFormatter
Implements ICatalogEntryFormatter
Property DescriptionText As String
End Class
В контроллере где-то будет такой код:
Dim entries As List(Of ICatalogEntry)
= catalogService.CurrentCatalog(DateTime.Now)
В представлении где-то будет такой код:
<ul>
@For Each entry As ICatalogEntry In Model.Catalog
@<li>@entry.DescriptionText</li>
Next
</ul>
Итак, вопрос в том, как выглядят конструкторы? Как настроить его так, чтобы соответствующие объекты создавались в нужных местах. Похоже на дженерики или, возможно, DI может помочь с этим, но у меня, кажется, есть умственный блок. Единственная идея, которую я предложил, - это добавить свойство ProductType в ICatalogEntry, а затем реализовать фабрику следующим образом:
Public Class CatalogEntryFactory
Public Function Create(catEntry as ICatalogEntry) As ICatalogEntry
Select Case catEntry.ProductType
Case "Clothing"
Dim clothingProduct = clothingService.Get(catEntry.ProductId)
Dim clothingEntry = New ClothingCatalogEntry(clothingProduct)
Return result
Case "Fastener"
Dim fastenerProduct = fastenerService.Get(catEntry.ProductId)
Dim fastenerEntry = New FastenerCatalogEntry(fastenerProduct)
fastenerEntry.Formatter = New FastenerCatalogEntryFormatter
Return fastenerEntry
...
End Function
End Class
Public ClothingCatalogEntry
Public Sub New (product As ClothingProduct)
Me.Formatter = New ClothingCatalogEntryFormatter(product)
End Sub
Property DescriptionText As String
Get
Return Me.Formatter.DescriptionText
End Get
End Property
End Class
...FastenerCatalogEntry is omitted but you get the idea...
Public Class CatalogService
Public Function CurrentCatalog(currentDate as DateTime)
Dim theCatalog As List(Of ICatalogEntry)
= Me.repository.GetCatalog(currentDate)
Dim theResult As New List(Of ICatalogEntry)
For Each entry As ICataLogEntry In theCatalog
theResult.Add(factory.Create(entry))
Next
Return theResult
End Function
End Class
ИМХО, я не чувствую никакого запаха от этого кода, кроме необходимости менять фабрику для каждого нового класса продукта, который появляется. Тем не менее, моя интуиция говорит, что это старый способ делать вещи, и в настоящее время DI и / или генерики могут сделать это лучше. Высоко ценятся предложения о том, как с этим справиться (как и предложения по лучшему названию...)
2 ответа
Сделав несколько небольших изменений, я заставил это работать с использованием расширения Ninject Factory. Самое большое изменение заключается в том, что у моих сущностей достаточно информации для отображения любого типа (одежда или застежки в моем надуманном примере), если предмет на самом деле является одеждой, то свойства, характерные для застежки, будут нулевыми, и наоборот.
Public Interface IDescribable
ReadOnly Property DescriptionText As String
End Interface
Public Enum ProductType
CLOTHING
FASTENER
End Enum
Public Interface ICatalogEntry
Inherits IDescribable
ReadOnly Property ProductId As Int64
ReadOnly Property PublishedOn As DateTime
ReadOnly Property ProductType As ProductType
End Interface
Public Class CatalogEntryEntity
Public Property ProductId As Long
Public Property ProductType As ProductType
Public Property PublishedOn As Date
Public Property DescriptionText As String
Public Property Color As String
Public Property Finish As String
Public Property IsMetric As Boolean
End Class
После этого я могу определить свой сервис каталогов следующим образом:
Public Class CatalogService
Private ReadOnly _factory As ICatalogEntryFactory
Private ReadOnly _repository As CatalogRepository
Public Sub New(entryFactory As ICatalogEntryFactory, repository As CatalogRepository)
Me._factory = entryFactory
Me._repository = repository
End Sub
Public Function CurrentCatalog(currentDate As DateTime) As List(Of ICatalogEntry)
Dim items = Me._repository.GetCatalog()
Return (From item In items Select _factory.Create(item.ProductType.ToString(), item)).ToList()
End Function
End Class
Public Interface ICatalogEntryFactory
Function Create(bindingName As String, entity As CatalogEntryEntity) As ICatalogEntry
End Interface
Ninject предоставит заводские настройки (что здорово!), Если я настрою привязки следующим образом:
theKernel.Bind(Of ICatalogEntry)().To(Of ClothingCatalogEntry)().Named("CLOTHING")
theKernel.Bind(Of ICatalogEntry)().To(Of FastenerCatalogEntry)().Named("FASTENER")
theKernel.Bind(Of ICatalogEntryFactory)().ToFactory(Function() New UseFirstParameterAsNameInstanceProvider())
Для краткости я опустил FastenerCatalogEntry; ClothingCatalogEntry выглядит так:
Public Class ClothingCatalogEntry
Public Sub New(ByVal entity As CatalogEntryEntity)
...
Именно этот пост помог мне больше всего понять это. Я использовал UseFirstParameterAsNameInstanceProvider точно так, как показано там.
Мне нравится просто использовать конструктор по умолчанию для моделей для представления и заполнять их через Automapper.
Я хотел бы иметь модель вида, как это:
public interface IHasDescription
{
public string DescriptionText { get; set; }
}
public class ViewModelType : IHasDescription
{
[DisplayName("This will be rendered in the view")]
public string SomeText { get; set; }
public string DescriptionText { get; set; }
}
И у меня есть модель из DAL, как это:
public class DALModelType
{
public string SomeText { get; set; }
}
Итак, у вас есть что-то вроде этого в вашем контроллере:
var dalModel = someRepository.GetAll();
var viewModel = Mapper.Map<DALModelType, ViewModelType>(dalModel);
И у вас есть код установки Automapper в каком-то файле. Таким образом, у вас есть только код преобразования в одном месте, а не в нескольких методах / контроллерах. У вас есть собственный распознаватель, который использует внедрение зависимостей (вместо () => new CustomResolver()), и в нем будет храниться ваша логика для получения отображаемого текста.
Mapper.CreateMap<IHasDescription, ViewModelType>()
.ForMember(dest => dest.DescriptionText,
opt => opt.ResolveUsing<CustomResolver>().ConstructedBy(() => new CustomResolver()));
Не уверен, что это работает с вашим рабочим процессом, но он должен иметь возможность получить то, что вы хотите.