在 MVC3 项目中组合多态对象 ASP.NET



我问题的本质是如何用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 和/或泛型可以做得更好。非常感谢有关如何处理此问题的建议(以及关于更好标题的建议......

我喜欢在视图的模型上使用默认构造函数,并通过自动映射器填充它们。

我会有一个这样的视图模型:

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);

并且您在某个文件中具有自动映射器设置代码。 这样,您只能将转换代码放在一个位置,而不是在多个方法/控制器中。 您有一个使用依赖项注入的自定义解析程序(而不是 () => 新的 CustomResolver()),这将容纳用于获取显示文本的逻辑。

Mapper.CreateMap<IHasDescription, ViewModelType>()
    .ForMember(dest => dest.DescriptionText, 
               opt => opt.ResolveUsing<CustomResolver>().ConstructedBy(() => new CustomResolver()));

不确定这是否适用于您的工作流程,但它应该能够为您提供所需的内容。

因此,进行了一些小的更改,我使用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。