6

Die Essenz meiner Frage ist, wie diese Objekte (siehe unten) in sinnvoller Weise mit MVC3 und Ninject zusammengesetzt werden (obwohl ich nicht sicher bin, sollte DI eine Rolle in der Lösung spielen). Ich kann die wahren Details meines Projekts nicht offen legen, aber hier ist eine Annäherung, die das Thema/die Frage veranschaulicht. Antworten in VB oder C# sind willkommen!Komponieren von polymorphen Objekten in ASP.NET MVC3 Projekt

Ich habe mehrere verschiedene Produkte mit sehr unterschiedlichen Eigenschaften, aber alle müssen in einem Katalog vertreten sein. Jede Produktklasse hat eine entsprechende Tabelle in meiner Datenbank. Ein Katalogeintrag hat eine Handvoll Eigenschaften, die spezifisch für einen Katalogeintrag sind und folglich eine eigene Tabelle haben. Ich habe eine Schnittstelle für die Katalogeinträge mit der Absicht definiert, dass das Aufrufen der DescriptionText -Eigenschaft sehr unterschiedliche Ergebnisse basierend auf dem zugrundeliegenden konkreten Typ gibt.

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 

Da die Description ein Anliegen Präsentationsschicht ist, ich will nicht die ICatalogEntry Schnittstelle in meinen Produktklassen implementieren. Stattdessen möchte ich das an eine Art Formatierer delegieren.

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 einem Controller irgendwo wird es Code wie folgt sein:

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

In einer Ansicht irgendwo wird es Code wie folgt sein:

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

Die Frage ist also, was die tun Konstruktoren sehen aus? Wie es eingerichtet wird, damit die entsprechenden Objekte an den richtigen Stellen instanziiert werden. Scheint wie Generika oder vielleicht DI kann dabei helfen, aber ich habe eine mentale Blockade. Die einzige Idee, die ich habe kommen mit ist eine Product Eigenschaft ICatalogEntry hinzufügen und dann eine Fabrik wie folgt implementieren:

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, ich bin nicht wirklich immer riecht aus diesen Code anders als mit der sich ändern Fabrik für jede neue Produktklasse, die mitkommt. Aber mein Bauchgefühl sagt, dass dies die alte Art ist, Dinge zu tun, und heutzutage können DI und/oder Generika das besser machen. Vorschläge, wie man damit umgeht, werden sehr geschätzt (ebenso wie Vorschläge zu einem besseren Titel ...)

Antwort

1

Also ein paar kleine Änderungen, die ich mit der Ninject Factory-Erweiterung funktioniert haben. Größte Änderung ist, dass meine Entitäten genug Informationen haben, um einen der beiden Typen anzuzeigen (Kleidung oder Verschlüsse in meinem erfundenen Beispiel), wenn der Gegenstand tatsächlich Kleidung ist, dann sind die Verschlusseigenschaften null und umgekehrt.

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 

dann mit dieser an Ort und Stelle kann ich meinen Katalog Dienst wie folgt definieren:

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 wird die Fabrik liefern vorausgesetzt, ich Setup die Bindungen so (was super ist!):

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

Ich habe den FastenerCatalogEntry der Kürze wegen weggelassen; die ClothingCatalogEntry ist wie folgt:

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

Es war this post die mich am meisten dazu beigetragen, um dies herauszufinden. Ich habe UseFirstParameterAsNameInstanceProvider genau wie dort gezeigt verwendet.

1

Ich mag es einfach, den Standardkonstruktor für Modelle für die Ansicht zu verwenden und sie über Automapper zu füllen.

würde ich einen Blick Modell wie dieses:

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

Und ich habe ein Modell aus der DAL wie folgt aus:

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

So haben Sie so etwas wie dies in Ihrem Controller:

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

Und Sie haben den Automapper Setup-Code in einer Datei. Auf diese Weise haben Sie den Conversion-Code nur an einer Stelle statt in mehreren Methoden/Controllern. Sie haben eine custom resolver, die Dependency Injection (anstelle von() => neue CustomResolver()) verwendet, und dies wird Ihre Logik enthalten, um den Anzeigetext zu erhalten.

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

Nicht sicher, ob dies mit Ihrem Workflow funktioniert, aber es sollte der Lage sein, Sie zu bekommen, was Sie wollen.

+0

So behandelt der benutzerdefinierte Resolver die Tatsache, dass mehr als eine Klasse der gleichen Schnittstelle zugeordnet ist? Das ist es, was mich in meiner Frage in die Fabrikklasse treibt, und das ist es, was mir am unbehaglichsten und völlig unklar ist, wie ich mich loswerden kann. – schmidlop

+0

Ok, jetzt, da ich den von Ihnen bereitgestellten Link gelesen habe, bin ich zu dem Schluss gekommen, dass ich vernünftigerweise erwarten sollte, dass mein DI-Container (Ninject) eine Antwort für dieses Szenario hat. Außerdem sieht es so aus und ich brauche eine kontextuelle Bindung https://github.com/ninject/ninject/wiki/Contextual-Binding und ich brauche das wahrscheinlich auch: https://github.com/ninject/ninject.extensions. factory/wiki – schmidlop

+0

Ja, das sollte eigentlich dein Problem lösen. In Ihrer Ninject-Initialisierung sollten Sie in der Lage sein, aus einer Ressourcendatei zu laden. –

Verwandte Themen