映射对象时如何将自定义逻辑应用于AutoMapper?

时间:2018-10-31 16:29:11

标签: c# asp.net-mvc asp.net-mvc-5 automapper asp.net-mvc-5.2

我在ASP.NET MVC 5框架的顶部有一个用sleep编写的项目。我正在使用AutoMapper,将视图模型动态映射到实体模型,反之亦然。

实体模型上的所有DateTime属性都位于UTC时区,而我的视图模型上的DateTime属性位于已登录用户的本地TimeZone中。

当我从实体模型映射到视图模型时,我需要将DateTime从UTC时区转换为登录用户的本地时区。为了使我的时间转换过程标准化,我有一个处理时间转换的服务类,称为C#。转换器类具有依赖关系,并且其方法不是静态调用。

通常,我有一个mapper / factory类,该类使我可以将创建对象的过程分组到一个位置。

这是我的mapper / factory类的一个例子

DateTimeConverter

这是我的public CategoryMapper { private IMapper Mapper; private IDateTimeConverter TimeConverter; private ICategoryService CategoryService; // There services are auto injected from the IoC public MapperService(IMapper mapper, IDateTimeConverter timeConverter, ICategoryService categoryService) { Mapper = mapper; TimeConverter = timeConverter; CategoryService = categoryService; } public ListCategoriesViewModel GetListCategoriesViewModel() { var viewModel = new ListCategoriesViewModel(); // Use AutoMapper to create the viewmodels var categories = Mapper.Map<List<DisplayCategoryViewModel>>(CategoryService.GetAll()); // I am trying to avoid having to do this loop each time... // Hoping that somehome this can be adding in tho the previous call // or add some kind of converter to AutoMapper to call TimeConverter.UtcToLocal on ALL DateTime properties categories.ForEach(category => { category.CreatedAt = TimeConverter.UtcToLocal(category.CreatedAt); category.UpdatedAt = TimeConverter.UtcToLocal(category.UpdatedAt); }); ViewModel.Categories = categories; return viewModel; } }

DisplayVategoryViewModel

如上所述,我首先调用public class DisplayCategoryViewModel : IMapFrom { public int Id { get; set; } public string Name { get; set; } public DateTime CreatedAt { get; set; } public DateTime? UpdatedAt { get; set; } ... ... public void Map(IMapperConfigurationExpression expression) { // This method is called from the mapping profile when the app starts using reflection // This way I can keep the mapping rules close to my viewmodel instead of having to move // these rules into the mapper class directly. // It is much easier to have the rules and the object in the same place when adding or renaming properties expression.CreateMap<Category, DisplayCategoryViewModel>() .ForMember(viewModel => viewModel.Name, opts => opts.MapFrom(model => model.Title)); } } 来告诉AutoMapper使用映射的配置文件来映射对象。但是映射的配置文件不知道必须使用Mapper.Map<List<DisplayCategoryViewModel>>实现来转换DateTime属性。因此,所有DateTime值均按原样映射,这意味着它们仍处于UTC TimeZone中。为了转换DateTime值,我在调用IDateTimeConverter方法,该方法再次遍历每条记录以修复DateTime属性。

此外,我所有的ViewModel都是简单的DTO,所以我不想在其中添加任何逻辑。我不想在我的任何DTC的构造函数中注入任何东西。

我希望能够向Mapping配置添加某种规则,以告诉它将任何DateTime属性转换为UTC或本地时区。或将回调传递给ForEach,以便我可以传递在映射过程中应用的自定义逻辑,以避免不必在映射的记录上创建另一个循环。

1 个答案:

答案 0 :(得分:0)

您可以使用custom value resolver

首先创建一个取决于您的IDateTimeConverter服务的自定义解决方案

public class LocalToUtcResolver : IMemberValueResolver<object, object, DateTime, DateTime>, IMemberValueResolver<object, object, DateTime?, DateTime?>
{
    private IDateTimeConverter TimeConverter;

    public LocalToUtcResolver(IDateTimeConverter timeConverter)
    {
        TimeConverter = timeConverter;
    }

    public DateTime Resolve(object source, object destination, DateTime sourceMember, DateTime destMember, ResolutionContext context)
    {
        return TimeConverter.LocalToUtc(sourceMember);
    }

    public DateTime? Resolve(object source, object destination, DateTime? sourceMember, DateTime? destMember, ResolutionContext context)
    {
        return TimeConverter.LocalToUtc(sourceMember);
    }
}

然后按照以下说明更新地图配置

public void Map(IMapperConfigurationExpression expression)
{
    expression.CreateMap<Category, DisplayCategoryViewModel>()
        .ForMember(viewModel => viewModel.Name, opts => opts.MapFrom(model => model.Title))                
        .ForMember(viewModel => viewModel.CreatedAt, opts => opts.ResolveUsing<UtcToLocalResolver, DateTime>(model => model.CreatedAt))
        .ForMember(viewModel => viewModel.UpdatedAt, opts => opts.ResolveUsing<UtcToLocalResolver, DateTime?>(model => model.UpdatedAt));
}

要让IDateTimeConverter在自定义解析器中解析,请确保为AutoMapper配置的服务未解析为默认服务,否则将无法正确解析依赖项。

这是我如何使用Unity-Container在我的一个项目中设置解析器的示例

var mapperConfig = new MapperConfiguration(expression =>
{
    expression.AddProfile(...);
    expression.ConstructServicesUsing(type => container.Resolve(type));
    //...
    //...
});

// Register singleton instance of the mapper class
container.RegisterInstance(mapperConfig.CreateMapper(), new 

ContainerControlledLifetimeManager());