Skip to content

Commit

Permalink
CompositeMapper
Browse files Browse the repository at this point in the history
  • Loading branch information
muratcakir committed May 12, 2023
1 parent 5b7584a commit 6166e60
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 13 deletions.
6 changes: 5 additions & 1 deletion src/Smartstore.Web/Controllers/CatalogHelper.Product.cs
@@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Mvc.Rendering;
using Smartstore.Collections;
using Smartstore.ComponentModel;
using Smartstore.Core.Catalog.Attributes;
using Smartstore.Core.Catalog.Pricing;
using Smartstore.Core.Catalog.Products;
Expand Down Expand Up @@ -259,7 +260,7 @@ protected internal virtual async Task<ProductDetailsModel> MapProductDetailsPage
#endregion

// ----> Core mapper <------
await PrepareProductDetailModelAsync(model, modelContext);
await PrepareProductDetailModelAsync(model, modelContext, 1);

#region Action items

Expand Down Expand Up @@ -389,6 +390,9 @@ public async Task PrepareProductDetailModelAsync(ProductDetailsModel model, Prod
// GiftCards
PrepareProductGiftCardsModel(model, modelContext);

// Custom mapping
await MapperFactory.MapWithRegisteredMapperAsync(product, model, new { Context = modelContext, Quantity = selectedQuantity });

_services.DisplayControl.Announce(product);
}

Expand Down
@@ -1,4 +1,5 @@
using Smartstore.Collections;
using Smartstore.ComponentModel;
using Smartstore.Core.Catalog.Attributes;
using Smartstore.Core.Catalog.Discounts;
using Smartstore.Core.Catalog.Pricing;
Expand Down Expand Up @@ -328,6 +329,7 @@ public virtual async Task<ProductSummaryModel> MapProductSummaryModelAsync(IPage
AllowShoppingCart = allowShoppingCart,
AllowWishlist = allowWishlist,
ShippingChargeTaxFormat = _taxService.GetTaxFormat(priceIncludesTax: calculationOptions.TaxInclusive, target: PricingTarget.ShippingCharge, language: language),
CustomMapper = MapperFactory.GetRegisteredMapper<Product, ProductSummaryItemModel>()
};

if (settings.MapPictures)
Expand Down Expand Up @@ -591,6 +593,12 @@ private async Task MapProductSummaryItem(Product product, ProductSummaryItemCont
});
}

// Custom mapping
if (ctx.CustomMapper != null)
{
await ctx.CustomMapper.MapAsync(product, item, new { Context = ctx });
}

model.Items.Add(item);
}

Expand Down
3 changes: 3 additions & 0 deletions src/Smartstore.Web/Models/Catalog/ProductSummaryItemModel.cs
@@ -1,4 +1,5 @@
using Smartstore.Collections;
using Smartstore.ComponentModel;
using Smartstore.Core.Catalog.Pricing;
using Smartstore.Core.Catalog.Products;
using Smartstore.Core.Content.Media;
Expand Down Expand Up @@ -26,6 +27,8 @@ public class ProductSummaryItemContext
public bool AllowShoppingCart { get; set; }
public bool AllowWishlist { get; set; }
public string ShippingChargeTaxFormat { get; set; }

internal IMapper<Product, ProductSummaryItemModel> CustomMapper { get; set; }
}

public class ProductSummaryItemModel : EntityModelBase
Expand Down
88 changes: 76 additions & 12 deletions src/Smartstore/ComponentModel/MapperFactory.cs
@@ -1,4 +1,7 @@
using System.Runtime.CompilerServices;
#nullable enable

using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using Autofac;
using Microsoft.EntityFrameworkCore;
using Smartstore.Collections;
Expand All @@ -19,9 +22,11 @@ namespace Smartstore.ComponentModel
/// </remarks>
public static class MapperFactory
{
private static Multimap<TypePair, Type> _mapperTypes = null;
private static Multimap<TypePair, Type> _mapperTypes = default!;
private readonly static object _lock = new();

#region Init

private static void EnsureInitialized()
{
if (_mapperTypes == null)
Expand Down Expand Up @@ -64,14 +69,18 @@ internal static void RegisterMappers(params Type[] mapperTypes)
}
}

#endregion

#region Map

/// <summary>
/// Maps instance of <typeparamref name="TFrom"/> to instance of <typeparamref name="TTo"/>.
/// </summary>
/// <param name="from">Source instance</param>
/// <param name="parameters">Custom parameters for the underlying mapper.</param>
/// <returns>The mapped target instance.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public async static Task<TTo> MapAsync<TFrom, TTo>(TFrom from, dynamic parameters = null)
public async static Task<TTo> MapAsync<TFrom, TTo>(TFrom from, dynamic? parameters = null)
where TFrom : class
where TTo : class, new()
{
Expand All @@ -89,7 +98,7 @@ internal static void RegisterMappers(params Type[] mapperTypes)
/// <param name="to">Target instance</param>
/// <param name="parameters">Custom parameters for the underlying mapper.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Task MapAsync<TFrom, TTo>(TFrom from, TTo to, dynamic parameters = null)
public static Task MapAsync<TFrom, TTo>(TFrom from, TTo to, dynamic? parameters = null)
where TFrom : class
where TTo : class
{
Expand All @@ -99,8 +108,35 @@ internal static void RegisterMappers(params Type[] mapperTypes)
parameters);
}

/// <summary>
/// Tries to map instance of <typeparamref name="TFrom"/> to <typeparamref name="TTo"/>.
/// This method will do nothing if no mapper is registered for the given type pair.
/// </summary>
/// <param name="from">Source instance</param>
/// <param name="to">Target instance</param>
/// <param name="parameters">Custom parameters for the underlying mapper.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Task MapWithRegisteredMapperAsync<TFrom, TTo>(TFrom from, TTo to, dynamic? parameters = null)
where TFrom : class
where TTo : class
{
Guard.NotNull(from);
Guard.NotNull(to);

var mapper = GetRegisteredMapper<TFrom, TTo>();

if (mapper != null)
{
return mapper.MapAsync(from, to, parameters);
}
else
{
return Task.CompletedTask;
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static async Task<List<TTo>> MapListAsync<TFrom, TTo>(IQueryable<TFrom> from, dynamic parameters = null)
public static async Task<List<TTo>> MapListAsync<TFrom, TTo>(IQueryable<TFrom> from, dynamic? parameters = null)
where TFrom : class
where TTo : class, new()
{
Expand All @@ -109,20 +145,40 @@ internal static void RegisterMappers(params Type[] mapperTypes)
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Task<List<TTo>> MapListAsync<TFrom, TTo>(IEnumerable<TFrom> from, dynamic parameters = null)
public static Task<List<TTo>> MapListAsync<TFrom, TTo>(IEnumerable<TFrom> from, dynamic? parameters = null)
where TFrom : class
where TTo : class, new()
{
return IMapperExtensions.MapListAsync(GetMapper<TFrom, TTo>(), from, parameters);
}

#endregion

#region GetMapper

/// <summary>
/// Gets a mapper implementation for <typeparamref name="TFrom"/> as source and <typeparamref name="TTo"/> as target.
/// </summary>
/// <returns>The mapper implementation or a generic mapper if not found.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IMapper<TFrom, TTo> GetMapper<TFrom, TTo>()
where TFrom : class
where TTo : class
=> GetMapperInternal<TFrom, TTo>(false)!;

/// <summary>
/// Gets a mapper implementation for <typeparamref name="TFrom"/> as source and <typeparamref name="TTo"/> as target.
/// </summary>
/// <returns>The mapper implementation or <c>null</c> if not found.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IMapper<TFrom, TTo>? GetRegisteredMapper<TFrom, TTo>()
where TFrom : class
where TTo : class
=> GetMapperInternal<TFrom, TTo>(true);

private static IMapper<TFrom, TTo>? GetMapperInternal<TFrom, TTo>([NotNullWhen(false)] bool onlyRegisteredMapper)
where TFrom : class
where TTo : class
{
EnsureInitialized();

Expand All @@ -149,26 +205,32 @@ internal static void RegisterMappers(params Type[] mapperTypes)

if (instances.Length > 0)
{
return new CompositeMapper<TFrom, TTo>(instances);
return new CompositeMapper<TFrom, TTo>(instances!);
}
}
}

return new GenericMapper<TFrom, TTo>();
return onlyRegisteredMapper
? null
: new GenericMapper<TFrom, TTo>();

static IMapper<TFrom, TTo> ResolveMapper(Type mapperType, ScopedServiceContainer scope)
static IMapper<TFrom, TTo>? ResolveMapper(Type mapperType, ScopedServiceContainer? scope)
{
var instance = scope?.ResolveUnregistered(mapperType);
if (instance != null)
{
scope.InjectProperties(instance);
scope!.InjectProperties(instance);
return (IMapper<TFrom, TTo>)instance;
}

return null;
}
}

#endregion

#region Private nested classes

class TypePair : Tuple<Type, Type>
{
public TypePair(Type fromType, Type toType)
Expand All @@ -184,7 +246,7 @@ class GenericMapper<TFrom, TTo> : Mapper<TFrom, TTo>
where TFrom : class
where TTo : class
{
protected override void Map(TFrom from, TTo to, dynamic parameters = null)
protected override void Map(TFrom from, TTo to, dynamic? parameters = null)
=> MiniMapper.Map(from, to);
}

Expand All @@ -199,13 +261,15 @@ public CompositeMapper(IMapper<TFrom, TTo>[] mappers)

private IMapper<TFrom, TTo>[] Mappers { get; }

public async Task MapAsync(TFrom from, TTo to, dynamic parameters = null)
public async Task MapAsync(TFrom from, TTo to, dynamic? parameters = null)
{
foreach (var mapper in Mappers)
{
await mapper.MapAsync(from, to, parameters);
}
}
}

#endregion
}
}

0 comments on commit 6166e60

Please sign in to comment.