6

La esencia de mi pregunta es cómo componer estos objetos (ver a continuación) de manera sensata con MVC3 y Ninject (aunque no estoy seguro de que DI deba desempeñar un papel en la solución) No puedo revelar los detalles reales de mi proyecto, pero aquí hay una aproximación que ilustra el problema/pregunta. ¡Se agradecen las respuestas en VB o C#!Componiendo objetos polimórficos en el proyecto ASP.NET MVC3

Tengo varios productos diferentes con propiedades que varían ampliamente pero todos ellos necesitan estar representados en un catálogo. Cada clase de producto tiene una tabla correspondiente en mi base de datos. Una entrada de catálogo tiene un puñado de propiedades específicas para ser una entrada de catálogo y, en consecuencia, tienen su propia tabla. He definido una interfaz para las entradas de catálogo con la intención de que llamar a la propiedad DescriptionText me proporcione resultados muy diferentes en función del tipo concreto subyacente.

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 

Dado que el DescriptionText es una preocupación capa de presentación no quiero para implementar la interfaz ICatalogEntry en mis clases de productos. En cambio, quiero delegar eso en algún tipo de formateador.

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 

En un controlador de algún lugar habrá un código como éste:

Dim entries As List(Of ICatalogEntry) 
        = catalogService.CurrentCatalog(DateTime.Now) 

En una vista en algún lugar habrá un código como éste:

<ul> 
@For Each entry As ICatalogEntry In Model.Catalog 
    @<li>@entry.DescriptionText</li> 
Next 
</ul> 

Así que la pregunta es lo que hace el constructores parecen? Cómo configurarlo para que los objetos apropiados sean instanciados en los lugares correctos. Parece que los medicamentos genéricos o tal vez DI puede ayudar con esto, pero parece que estoy teniendo un bloqueo mental. La única idea que he llegado con es añadir una propiedad ProductType a ICatalogEntry y luego aplicar una fábrica de la siguiente manera:

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 

en mi humilde opinión, no estoy realmente recibiendo ningún olor fuera de este código aparte de tener que cambiar el fábrica para cada nueva clase de producto que se presente. Sin embargo, mi instinto me dice que esta es la forma antigua de hacer las cosas y que hoy DI y/o genéricos pueden hacerlo mejor. Sugerencias sobre cómo manejar esto son muy apreciadas (como son sugerencias sobre un mejor título ...)

Respuesta

1

Así que haciendo algunos pequeños cambios conseguí que esto funcione con la extensión Ninject Factory. El mayor cambio es que mis entidades tienen suficiente información para mostrar cualquier tipo (ropa o sujetadores en mi ejemplo artificial) si el artículo es realmente ropa, entonces las propiedades específicas del sujetador serán nulas y viceversa.

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 

Entonces, con esto en su lugar puedo definir mi servicio de catálogo de la siguiente manera:

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 proporcionará la fábrica suponiendo que la configuración de los enlaces de esta manera (que es impresionante!):

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

He omitido FastenerCatalogEntry para abreviar; la ClothingCatalogEntry es así:

Public Class ClothingCatalogEntry 
    Public Sub New(ByVal entity As CatalogEntryEntity) 
... 

Fue this post que más me ayudó a resolver esto. Usé UseFirstParameterAsNameInstanceProvider exactamente como se muestra allí.

1

Me gusta usar el constructor predeterminado en los modelos para la vista y completarlos a través de Automapper.

me gustaría tener un modelo de vista así:

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

Y tengo un modelo de la DAL así:

public class DALModelType 
{ 
    public string SomeText { get; set; } 
} 

Así que tienes algo como esto en su controlador:

var dalModel = someRepository.GetAll(); 
var viewModel = Mapper.Map<DALModelType, ViewModelType>(dalModel); 

Y tiene el código de configuración de Automapper en algún archivo. De esta forma, solo tendrá el código de conversión en un lugar en lugar de múltiples métodos/controladores. Tiene un custom resolver que usa inyección de dependencia (en lugar de() => nuevo CustomResolver()) y esto incluirá su lógica para obtener el texto de visualización.

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

No estoy seguro si esto funciona con el flujo de trabajo pero debe ser capaz de conseguir lo que quiere.

+0

¿La resolución personalizada maneja el hecho de que más de una clase se asigna a la misma interfaz? Eso es lo que me empuja a la clase de fábrica en mi pregunta y eso es lo que más me incomoda y no tengo muy claro cómo deshacerme de él. – schmidlop

+0

Bien, ahora que leí el enlace que proporcioné, he llegado a la conclusión de que debo esperar razonablemente que mi contenedor DI (Ninject) tenga alguna respuesta para este escenario. Además, parece que sí y necesito un enlace contextual https://github.com/ninject/ninject/wiki/Contextual-Binding y probablemente también necesite esto: https://github.com/ninject/ninject.extensions. fábrica/wiki – schmidlop

+0

Sí, eso realmente debería resolver su problema. En su inicialización de ninject, debería poder cargar desde un archivo de recursos. –