Составление полиморфных объектов в проекте 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()));

Не уверен, что это работает с вашим рабочим процессом, но он должен иметь возможность получить то, что вы хотите.

Другие вопросы по тегам