Resolved #1844 and Resolved #1777: Created contextualized IObjectMapper service.

pull/1716/head^2
Halil İbrahim Kalkan 6 years ago
parent e2a334f37b
commit 0aa89831d5

@ -248,6 +248,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.MailKit", "src\Vol
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.MailKit.Tests", "test\Volo.Abp.MailKit.Tests\Volo.Abp.MailKit.Tests.csproj", "{70DD6E17-B98B-4B00-8F38-C489E291BB53}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.ObjectMapping.Tests", "test\Volo.Abp.ObjectMapping.Tests\Volo.Abp.ObjectMapping.Tests.csproj", "{667F5544-C1EB-447C-96FD-9B757F04DE2B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -738,6 +740,10 @@ Global
{70DD6E17-B98B-4B00-8F38-C489E291BB53}.Debug|Any CPU.Build.0 = Debug|Any CPU
{70DD6E17-B98B-4B00-8F38-C489E291BB53}.Release|Any CPU.ActiveCfg = Release|Any CPU
{70DD6E17-B98B-4B00-8F38-C489E291BB53}.Release|Any CPU.Build.0 = Release|Any CPU
{667F5544-C1EB-447C-96FD-9B757F04DE2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{667F5544-C1EB-447C-96FD-9B757F04DE2B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{667F5544-C1EB-447C-96FD-9B757F04DE2B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{667F5544-C1EB-447C-96FD-9B757F04DE2B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -864,6 +870,7 @@ Global
{E026A085-D881-4AE0-9F08-422AC3903BD7} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{0CAED4CC-1CFD-4092-A326-AFE4DB3A9AB4} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{70DD6E17-B98B-4B00-8F38-C489E291BB53} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{667F5544-C1EB-447C-96FD-9B757F04DE2B} = {447C8A77-E5F0-4538-8687-7383196D04EA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5}

@ -24,7 +24,11 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.RazorPages
{
public IServiceProvider ServiceProvider { get; set; }
protected readonly object ServiceProviderLock = new object();
protected TService LazyGetRequiredService<TService>(ref TService reference)
=> LazyGetRequiredService(typeof(TService), ref reference);
protected TRef LazyGetRequiredService<TRef>(Type serviceType, ref TRef reference)
{
if (reference == null)
{
@ -32,7 +36,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.RazorPages
{
if (reference == null)
{
reference = ServiceProvider.GetRequiredService<TService>();
reference = (TRef)ServiceProvider.GetRequiredService(serviceType);
}
}
}
@ -48,7 +52,27 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.RazorPages
public IUnitOfWorkManager UnitOfWorkManager => LazyGetRequiredService(ref _unitOfWorkManager);
private IUnitOfWorkManager _unitOfWorkManager;
public IObjectMapper ObjectMapper => LazyGetRequiredService(ref _objectMapper);
protected Type ObjectMapperContext { get; set; }
public IObjectMapper ObjectMapper
{
get
{
if (_objectMapper != null)
{
return _objectMapper;
}
if (ObjectMapperContext == null)
{
return LazyGetRequiredService(ref _objectMapper);
}
return LazyGetRequiredService(
typeof(IObjectMapper<>).MakeGenericType(ObjectMapperContext),
ref _objectMapper
);
}
}
private IObjectMapper _objectMapper;
public IGuidGenerator GuidGenerator => LazyGetRequiredService(ref _guidGenerator);

@ -23,7 +23,11 @@ namespace Volo.Abp.AspNetCore.Mvc
{
public IServiceProvider ServiceProvider { get; set; }
protected readonly object ServiceProviderLock = new object();
protected TService LazyGetRequiredService<TService>(ref TService reference)
=> LazyGetRequiredService(typeof(TService), ref reference);
protected TRef LazyGetRequiredService<TRef>(Type serviceType, ref TRef reference)
{
if (reference == null)
{
@ -31,7 +35,7 @@ namespace Volo.Abp.AspNetCore.Mvc
{
if (reference == null)
{
reference = ServiceProvider.GetRequiredService<TService>();
reference = (TRef)ServiceProvider.GetRequiredService(serviceType);
}
}
}
@ -42,7 +46,27 @@ namespace Volo.Abp.AspNetCore.Mvc
public IUnitOfWorkManager UnitOfWorkManager => LazyGetRequiredService(ref _unitOfWorkManager);
private IUnitOfWorkManager _unitOfWorkManager;
public IObjectMapper ObjectMapper => LazyGetRequiredService(ref _objectMapper);
protected Type ObjectMapperContext { get; set; }
public IObjectMapper ObjectMapper
{
get
{
if (_objectMapper != null)
{
return _objectMapper;
}
if (ObjectMapperContext == null)
{
return LazyGetRequiredService(ref _objectMapper);
}
return LazyGetRequiredService(
typeof(IObjectMapper<>).MakeGenericType(ObjectMapperContext),
ref _objectMapper
);
}
}
private IObjectMapper _objectMapper;
public IGuidGenerator GuidGenerator => LazyGetRequiredService(ref _guidGenerator);

@ -1,15 +1,55 @@
using Microsoft.AspNetCore.Mvc;
using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.ObjectMapping;
namespace Volo.Abp.AspNetCore.Mvc
{
public abstract class AbpViewComponent : ViewComponent
{
public IObjectMapper ObjectMapper { get; set; }
public IServiceProvider ServiceProvider { get; set; }
protected readonly object ServiceProviderLock = new object();
protected AbpViewComponent()
protected TService LazyGetRequiredService<TService>(ref TService reference)
=> LazyGetRequiredService(typeof(TService), ref reference);
protected TRef LazyGetRequiredService<TRef>(Type serviceType, ref TRef reference)
{
if (reference == null)
{
lock (ServiceProviderLock)
{
if (reference == null)
{
reference = (TRef)ServiceProvider.GetRequiredService(serviceType);
}
}
}
return reference;
}
protected Type ObjectMapperContext { get; set; }
public IObjectMapper ObjectMapper
{
get
{
if (_objectMapper != null)
{
return _objectMapper;
}
if (ObjectMapperContext == null)
{
return LazyGetRequiredService(ref _objectMapper);
}
return LazyGetRequiredService(
typeof(IObjectMapper<>).MakeGenericType(ObjectMapperContext),
ref _objectMapper
);
}
}
private IObjectMapper _objectMapper;
}
}

@ -0,0 +1,23 @@
using Microsoft.Extensions.DependencyInjection.Extensions;
using Volo.Abp.AutoMapper;
using Volo.Abp.ObjectMapping;
namespace Microsoft.Extensions.DependencyInjection
{
public static class AbpAutoMapperServiceCollectionExtensions
{
public static IServiceCollection AddAutoMapperObjectMapper(this IServiceCollection services)
{
return services.Replace(
ServiceDescriptor.Transient<IAutoObjectMappingProvider, AutoMapperAutoObjectMappingProvider>()
);
}
public static IServiceCollection AddAutoMapperObjectMapper<TContext>(this IServiceCollection services)
{
return services.Replace(
ServiceDescriptor.Transient<IAutoObjectMappingProvider<TContext>, AutoMapperAutoObjectMappingProvider<TContext>>()
);
}
}
}

@ -12,6 +12,8 @@ namespace Volo.Abp.AutoMapper
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddAutoMapperObjectMapper();
var mapperAccessor = new MapperAccessor();
context.Services.AddSingleton<IMapperAccessor>(_ => mapperAccessor);
context.Services.AddSingleton<MapperAccessor>(_ => mapperAccessor);

@ -1,13 +1,18 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.DependencyInjection;
using Volo.Abp.ObjectMapping;
using Volo.Abp.ObjectMapping;
namespace Volo.Abp.AutoMapper
{
[Dependency(ServiceLifetime.Transient, ReplaceServices = true)]
public class AutoMapperAutoObjectMappingProvider<TContext> : AutoMapperAutoObjectMappingProvider, IAutoObjectMappingProvider<TContext>
{
public AutoMapperAutoObjectMappingProvider(IMapperAccessor mapperAccessor)
: base(mapperAccessor)
{
}
}
public class AutoMapperAutoObjectMappingProvider : IAutoObjectMappingProvider
{
protected IMapperAccessor MapperAccessor { get; }
public IMapperAccessor MapperAccessor { get; }
public AutoMapperAutoObjectMappingProvider(IMapperAccessor mapperAccessor)
{

@ -0,0 +1,23 @@
using AutoMapper;
using Volo.Abp.AutoMapper;
namespace Volo.Abp.ObjectMapping
{
public static class AbpAutoMapperObjectMapperExtensions
{
public static IMapper GetMapper(this IObjectMapper objectMapper)
{
return objectMapper.AutoObjectMappingProvider.GetMapper();
}
public static IMapper GetMapper(this IAutoObjectMappingProvider autoObjectMappingProvider)
{
if (autoObjectMappingProvider is AutoMapperAutoObjectMappingProvider autoMapperAutoObjectMappingProvider)
{
return autoMapperAutoObjectMappingProvider.MapperAccessor.Mapper;
}
throw new AbpException($"Given object is not an instance of {typeof(AutoMapperAutoObjectMappingProvider).AssemblyQualifiedName}. The type of the given object it {autoObjectMappingProvider.GetType().AssemblyQualifiedName}");
}
}
}

@ -34,7 +34,11 @@ namespace Volo.Abp.Application.Services
{
public IServiceProvider ServiceProvider { get; set; }
protected readonly object ServiceProviderLock = new object();
protected TService LazyGetRequiredService<TService>(ref TService reference)
=> LazyGetRequiredService(typeof(TService), ref reference);
protected TRef LazyGetRequiredService<TRef>(Type serviceType, ref TRef reference)
{
if (reference == null)
{
@ -42,7 +46,7 @@ namespace Volo.Abp.Application.Services
{
if (reference == null)
{
reference = ServiceProvider.GetRequiredService<TService>();
reference = (TRef)ServiceProvider.GetRequiredService(serviceType);
}
}
}
@ -57,7 +61,27 @@ namespace Volo.Abp.Application.Services
public IUnitOfWorkManager UnitOfWorkManager => LazyGetRequiredService(ref _unitOfWorkManager);
private IUnitOfWorkManager _unitOfWorkManager;
public IObjectMapper ObjectMapper => LazyGetRequiredService(ref _objectMapper);
protected Type ObjectMapperContext { get; set; }
public IObjectMapper ObjectMapper
{
get
{
if (_objectMapper != null)
{
return _objectMapper;
}
if (ObjectMapperContext == null)
{
return LazyGetRequiredService(ref _objectMapper);
}
return LazyGetRequiredService(
typeof(IObjectMapper<>).MakeGenericType(ObjectMapperContext),
ref _objectMapper
);
}
}
private IObjectMapper _objectMapper;
public IGuidGenerator GuidGenerator { get; set; }

@ -188,8 +188,7 @@ namespace Volo.Abp.Application.Services
protected virtual IQueryable<TEntity> ApplySorting(IQueryable<TEntity> query, TGetListInput input)
{
//Try to sort query if available
var sortInput = input as ISortedResultRequest;
if (sortInput != null)
if (input is ISortedResultRequest sortInput)
{
if (!sortInput.Sorting.IsNullOrWhiteSpace())
{
@ -215,15 +214,13 @@ namespace Volo.Abp.Application.Services
protected virtual IQueryable<TEntity> ApplyPaging(IQueryable<TEntity> query, TGetListInput input)
{
//Try to use paging if available
var pagedInput = input as IPagedResultRequest;
if (pagedInput != null)
if (input is IPagedResultRequest pagedInput)
{
return query.PageBy(pagedInput);
}
//Try to limit query result if available
var limitedInput = input as ILimitedResultRequest;
if (limitedInput != null)
if (input is ILimitedResultRequest limitedInput)
{
return query.Take(limitedInput.MaxResultCount);
}

@ -1,5 +1,6 @@
using Microsoft.Extensions.Options;
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.DependencyInjection;
using Volo.Abp.DynamicProxy;
using Volo.Abp.EventBus.Distributed;
@ -9,14 +10,14 @@ namespace Volo.Abp.Domain.Entities.Events.Distributed
{
public class EntityToEtoMapper : IEntityToEtoMapper, ITransientDependency
{
protected IObjectMapper ObjectMapper { get; }
protected IHybridServiceScopeFactory HybridServiceScopeFactory { get; }
protected DistributedEventBusOptions Options { get; }
public EntityToEtoMapper(
IOptions<DistributedEventBusOptions> options,
IObjectMapper objectMapper)
IHybridServiceScopeFactory hybridServiceScopeFactory)
{
ObjectMapper = objectMapper;
HybridServiceScopeFactory = hybridServiceScopeFactory;
Options = options.Value;
}
@ -31,15 +32,23 @@ namespace Volo.Abp.Domain.Entities.Events.Distributed
}
var entityType = ProxyHelper.UnProxy(entity).GetType();
var etoType = Options.EtoMappings.GetOrDefault(entityType);
if (etoType == null)
var etoMappingItem = Options.EtoMappings.GetOrDefault(entityType);
if (etoMappingItem == null)
{
var keys = entity.GetKeys().JoinAsString(",");
return new EntityEto(entityType.FullName, keys);
}
//TODO: Also add KeysAsString property to resulting json for compatibility with the EntityEto!
return ObjectMapper.Map(entityType, etoType, entityObj);
using (var scope = HybridServiceScopeFactory.CreateScope())
{
var objectMapperType = etoMappingItem.ObjectMappingContextType == null
? typeof(IObjectMapper)
: typeof(IObjectMapper<>).MakeGenericType(etoMappingItem.ObjectMappingContextType);
var objectMapper = (IObjectMapper) scope.ServiceProvider.GetRequiredService(objectMapperType);
return objectMapper.Map(entityType, etoMappingItem.EtoType, entityObj);
}
}
}
}

@ -3,11 +3,11 @@ using System.Collections.Generic;
namespace Volo.Abp.EventBus.Distributed
{
public class EtoMappingDictionary : Dictionary<Type, Type>
public class EtoMappingDictionary : Dictionary<Type, EtoMappingDictionaryItem>
{
public void Add<TEntity, TEntityEto>()
public void Add<TEntity, TEntityEto>(Type objectMappingContextType = null)
{
this[typeof(TEntity)] = typeof(TEntityEto);
this[typeof(TEntity)] = new EtoMappingDictionaryItem(typeof(TEntityEto), objectMappingContextType);
}
}
}

@ -0,0 +1,17 @@
using System;
namespace Volo.Abp.EventBus.Distributed
{
public class EtoMappingDictionaryItem
{
public Type EtoType { get; }
public Type ObjectMappingContextType { get; }
public EtoMappingDictionaryItem(Type etoType, Type objectMappingContextType = null)
{
EtoType = etoType;
ObjectMappingContextType = objectMappingContextType;
}
}
}

@ -19,5 +19,13 @@ namespace Volo.Abp.ObjectMapping
);
});
}
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddTransient(
typeof(IObjectMapper<>),
typeof(DefaultObjectMapper<>)
);
}
}
}

@ -4,11 +4,22 @@ using Volo.Abp.DependencyInjection;
namespace Volo.Abp.ObjectMapping
{
//TODO: It can be slow to always check if service is available. Test it and optimize if necessary.
public class DefaultObjectMapper<TContext> : DefaultObjectMapper, IObjectMapper<TContext>
{
public DefaultObjectMapper(
IServiceProvider serviceProvider,
IAutoObjectMappingProvider<TContext> autoObjectMappingProvider
) : base(
serviceProvider,
autoObjectMappingProvider)
{
}
}
public class DefaultObjectMapper : IObjectMapper, ITransientDependency
{
protected IAutoObjectMappingProvider AutoObjectMappingProvider { get; }
public IAutoObjectMappingProvider AutoObjectMappingProvider { get; }
protected IServiceProvider ServiceProvider { get; }
public DefaultObjectMapper(
@ -18,6 +29,8 @@ namespace Volo.Abp.ObjectMapping
AutoObjectMappingProvider = autoObjectMappingProvider;
ServiceProvider = serviceProvider;
}
//TODO: It can be slow to always check if service is available. Test it and optimize if necessary.
public virtual TDestination Map<TSource, TDestination>(TSource source)
{

@ -6,4 +6,9 @@ namespace Volo.Abp.ObjectMapping
TDestination Map<TSource, TDestination>(TSource source, TDestination destination);
}
public interface IAutoObjectMappingProvider<TContext> : IAutoObjectMappingProvider
{
}
}

@ -5,6 +5,11 @@
/// </summary>
public interface IObjectMapper
{
/// <summary>
/// Gets the underlying <see cref="IAutoObjectMappingProvider"/> object that is used for auto object mapping.
/// </summary>
IAutoObjectMappingProvider AutoObjectMappingProvider { get; }
/// <summary>
/// Converts an object to another. Creates a new object of <see cref="TDestination"/>.
/// </summary>
@ -24,6 +29,14 @@
TDestination Map<TSource, TDestination>(TSource source, TDestination destination);
}
/// <summary>
/// Defines a simple interface to automatically map objects for a specific context.
/// </summary>
public interface IObjectMapper<TContext> : IObjectMapper
{
}
/// <summary>
/// Maps an object to another.
/// Implement this interface to override object to object mapping for specific types.

@ -22,6 +22,13 @@ namespace Volo.Abp.AutoMapper
Assert.True(ServiceProvider.GetRequiredService<IAutoObjectMappingProvider>() is AutoMapperAutoObjectMappingProvider);
}
[Fact]
public void Should_Get_Internal_Mapper()
{
_objectMapper.GetMapper().ShouldNotBeNull();
_objectMapper.AutoObjectMappingProvider.GetMapper().ShouldNotBeNull();
}
[Fact]
public void Should_Map_Objects_With_AutoMap_Attributes()
{

@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\common.test.props" />
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Volo.Abp.ObjectMapping\Volo.Abp.ObjectMapping.csproj" />
<ProjectReference Include="..\AbpTestBase\AbpTestBase.csproj" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
</ItemGroup>
</Project>

@ -0,0 +1,7 @@
namespace Volo.Abp.ObjectMapping
{
public abstract class AbpObjectMappingTestBase : AbpIntegratedTest<AbpObjectMappingTestModule>
{
}
}

@ -0,0 +1,17 @@
using Volo.Abp.Modularity;
namespace Volo.Abp.ObjectMapping
{
[DependsOn(
typeof(AbpObjectMappingModule),
typeof(AbpTestBaseModule)
)]
public class AbpObjectMappingTestModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddTest1AutoObjectMappingProvider<MappingContext1>();
context.Services.AddTest2AutoObjectMappingProvider<MappingContext2>();
}
}
}

@ -0,0 +1,24 @@
using Shouldly;
using Xunit;
namespace Volo.Abp.ObjectMapping
{
public class ContextSpecificMapper_Tests : AbpObjectMappingTestBase
{
[Fact]
public void Should_Resolve_Correct_ObjectMappper_For_Specific_Context()
{
GetRequiredService<IObjectMapper<MappingContext1>>()
.ShouldBeOfType(typeof(DefaultObjectMapper<MappingContext1>));
GetRequiredService<IAutoObjectMappingProvider<MappingContext1>>()
.ShouldBeOfType(typeof(Test1AutoObjectMappingProvider<MappingContext1>));
GetRequiredService<IObjectMapper<MappingContext2>>()
.ShouldBeOfType(typeof(DefaultObjectMapper<MappingContext2>));
GetRequiredService<IAutoObjectMappingProvider<MappingContext2>>()
.ShouldBeOfType(typeof(Test2AutoObjectMappingProvider<MappingContext2>));
}
}
}

@ -0,0 +1,7 @@
namespace Volo.Abp.ObjectMapping
{
public class MappingContext1
{
}
}

@ -0,0 +1,7 @@
namespace Volo.Abp.ObjectMapping
{
public class MappingContext2
{
}
}

@ -0,0 +1,22 @@
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.ObjectMapping
{
public class Test1AutoObjectMappingProvider<TContext> : Test1AutoObjectMappingProvider, IAutoObjectMappingProvider<TContext>
{
}
public class Test1AutoObjectMappingProvider : IAutoObjectMappingProvider, ITransientDependency
{
public TDestination Map<TSource, TDestination>(object source)
{
return default;
}
public TDestination Map<TSource, TDestination>(TSource source, TDestination destination)
{
return default;
}
}
}

@ -0,0 +1,15 @@
namespace Volo.Abp.ObjectMapping
{
public class Test2AutoObjectMappingProvider<TContext> : IAutoObjectMappingProvider<TContext>
{
public TDestination Map<TSource, TDestination>(object source)
{
return default;
}
public TDestination Map<TSource, TDestination>(TSource source, TDestination destination)
{
return default;
}
}
}

@ -0,0 +1,25 @@
using Microsoft.Extensions.DependencyInjection;
namespace Volo.Abp.ObjectMapping
{
public static class TestContextRegistrar
{
public static IServiceCollection AddTest1AutoObjectMappingProvider<TContext>(this IServiceCollection services)
{
return services
.AddTransient<
IAutoObjectMappingProvider<TContext>,
Test1AutoObjectMappingProvider<TContext>
>();
}
public static IServiceCollection AddTest2AutoObjectMappingProvider<TContext>(this IServiceCollection services)
{
return services
.AddTransient<
IAutoObjectMappingProvider<TContext>,
Test2AutoObjectMappingProvider<TContext>
>();
}
}
}
Loading…
Cancel
Save