Composing polymorphic objects in an ASP.NET MVC3 project

The essence of my question is how to compose these objects (see below) with MVC3 and Ninject (although I'm not sure that DI should play a role in the solution). I cannot reveal the real details of my project, but here is an approximation that illustrates the problem / question. Responses to VB or C # are appreciated!

I have several different products with various properties, but all of them should be presented in the catalog. Each product class has a corresponding table in my database. There are several properties in a directory entry that are specific to entering a directory, and therefore their table. I defined an interface for directory entries with the intention that calling the DescriptionText property would give me very different results based on the underlying concrete type.

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 

Given that DescriptionText is a presentation-level issue, I don’t want to implement the ICatalogEntry interface in my product classes. Instead, I want to delegate this to the format.

 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 the controller somewhere there will be such code:

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

In the view, there will be a code somewhere:

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

So the question is, what do designers look like? How to configure it so that the corresponding objects are created in the right places. Sounds like generics, or maybe DI can help with this, but I seem to have a mental block. The only idea I came up with is to add the ProductType property to ICatalogEntry, and then implement the factory as follows:

 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, I really don't get any smells from this code, except for having to change the factory for every new product class that comes in. However, my gut says this is an old way of doing something, and these days DI and / or generics can do it better. Suggestions on how to handle this are greatly appreciated (as are suggestions for a better name ...)

+6
source share
2 answers

So, after making a few small changes, I got this to work using the Ninject Factory extension. The biggest change is that my entities have enough information to display any type (clothes or fasteners in my far-fetched example), if the item is actually clothes, then the specific properties of the fasteners will be zero and vice versa.

 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 

Then, using this place, I can define my directory service as follows:

 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 will provide the Factory (which is amazing!), Assuming I configure the bindings as follows:

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

I abbreviated FastenerCatalogEntry for short; ClothingCatalogEntry is as follows:

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

This is the post that helped me most understand this. I used UseFirstParameterAsNameInstanceProvider exactly as shown.

+1
source

I like to just use the default constructor for models to represent and populate them through Automapper .

I would have a model like this:

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

And I have a model from DAL, for example:

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

So, you have something like this in the controller:

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

And you have the Automapper installation code in some kind of file. This way, you only have conversion code in one place, and not in multiple methods / controllers. You have a custom converter that uses dependency injection (instead of () => new CustomResolver ()), and that will be your logic to get the text displayed.

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

Not sure if this works with your workflow, but it should be able to get what you want.

+1
source

Source: https://habr.com/ru/post/913466/


All Articles