6

L'essenza della mia domanda è come comporre questi oggetti (vedi sotto) in modo sensato con MVC3 e Ninject (anche se non sono sicuro che DI dovrebbe svolgere un ruolo nella soluzione). Non posso rivelare i dettagli reali del mio progetto ma qui è un'approssimazione che illustra il problema/domanda. Le risposte in VB o C# sono apprezzate!Composizione di oggetti polimorfici nel progetto ASP.NET MVC3

Ho diversi prodotti con proprietà molto diverse, ma tutti devono essere rappresentati in un catalogo. Ogni classe di prodotto ha una tabella corrispondente nel mio database. Una voce di catalogo ha una manciata di proprietà specifiche per essere una voce di catalogo e di conseguenza dispone di una propria tabella. Ho definito un'interfaccia per le voci del catalogo con l'intento che chiamare la proprietà DescriptionText mi darà risultati molto diversi in base al tipo concreto sottostante.

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 

Dato che la DescriptionText è una preoccupazione presentazione strato Non voglio per implementare l'interfaccia ICatalogEntry nelle mie classi di prodotto. Invece voglio delegarlo a qualche tipo di formattatore.

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 

In un controller da qualche parte ci sarà il codice come questo:

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

In una vista da qualche parte ci sarà il codice come questo:

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

Quindi la domanda è che cosa fare il assomigliano costruttori? Come impostarlo in modo che gli oggetti appropriati vengano istanziati nei posti giusti. Sembra che i farmaci generici o forse DI possano aiutare in questo, ma mi sembra di avere un blocco mentale. L'unica idea che è venuta in mente è quello di aggiungere una proprietà ProductType per ICatalogEntry e quindi implementare una fabbrica come questa:

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 

IMHO, non sto veramente ottenere qualsiasi odori fuori questo codice diverso da dover cambiare la fabbrica per ogni nuova classe di prodotti che arriva. Eppure, il mio istinto dice che questo è il vecchio modo di fare le cose e oggi DI e/o generici possono farlo meglio. Suggerimenti su come gestirlo sono molto apprezzati (come i suggerimenti su un titolo migliore ...)

risposta

1

Così facendo alcune piccole modifiche ho ottenuto questo lavoro utilizzando l'estensione di fabbrica Ninject. Il più grande cambiamento è che le mie entità hanno abbastanza informazioni per visualizzare entrambi i tipi (vestiti o elementi di fissaggio nel mio esempio forzato) se l'oggetto è effettivamente vestiti, quindi le proprietà specifiche del dispositivo di fissaggio saranno nulle e 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 

Poi, con questo posto che posso definire il mio servizio di catalogo come segue:

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 fornirà la fabbrica supponendo che l'installazione degli attacchi come questo (che è impressionante!):

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

Ho omesso FastenerCatalogEntry per brevità; la ClothingCatalogEntry è come questo:

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

Era this post che mi ha aiutato di più a questo numero. Ho usato UseFirstParameterAsNameInstanceProvider esattamente come mostrato qui.

1

Mi piace usare il costruttore predefinito sui modelli per la vista e popolarli tramite Automapper.

avrei un modello di vista come questo:

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

E ho un modello dalla DAL così:

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

in modo da avere qualcosa di simile nel vostro controller:

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

E il codice di installazione di Automapper è presente in alcuni file. In questo modo hai solo il codice di conversione in un posto anziché in più metodi/controller. Si dispone di un custom resolver che utilizza l'iniezione delle dipendenze (invece di() => new CustomResolver()) e questo ospiterà la logica per ottenere il testo visualizzato.

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

Non sono sicuro se questo funziona con il vostro flusso di lavoro, ma dovrebbe essere in grado di ottenere quello che vuoi.

+0

Quindi il resolver personalizzato gestisce il fatto che più di una classe esegue il mapping sulla stessa interfaccia? Questo è ciò che mi spinge alla classe di fabbrica nella mia domanda ed è quello che mi imbarazza di più e del tutto oscuro come liberarmi di. – schmidlop

+0

Ok, ora che ho letto il link che hai fornito, ho concluso che dovrei ragionevolmente aspettarmi che il mio contenitore DI (Ninject) abbia una risposta per questo scenario. Inoltre sembra che lo faccia e ho bisogno di un binding contestuale https://github.com/ninject/ninject/wiki/Contextual-Binding e probabilmente ho bisogno anche di questo: https://github.com/ninject/ninject.extensions. factory/wiki – schmidlop

+0

Sì, questo dovrebbe effettivamente risolvere il tuo problema. Nella tua inizializzazione ninject dovresti essere in grado di caricare da un file di risorse. –

Problemi correlati