Как применить пользовательскую логику к AutoMapper при отображении объекта? - PullRequest
0 голосов
/ 31 октября 2018

У меня есть проект, который написан на C# поверх платформы ASP.NET MVC 5. Я использую AutoMapper , чтобы динамически отобразить модель представления в модель объекта и наоборот.

Все свойства DateTime в модели объекта находятся в часовом поясе UTC, где свойства DateTime в моих моделях представления находятся в локальном часовом поясе пользователя, вошедшего в систему.

Когда я сопоставляю модель объекта с моделью представления, мне нужно преобразовать DateTime из часового пояса UTC в зарегистрированный локальный часовой пояс пользователя. Для стандартизации процесса преобразования времени у меня есть класс обслуживания, который обрабатывает преобразование времени, который называется DateTimeConverter. Класс преобразователя имеет зависимости, и его методы не являются статически вызываемыми.

Обычно у меня есть класс mapper / factory, который позволяет мне группировать процесс, который создает объект, в одно место.

Вот пример моего класса картостроителя / фабрики

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

Как вы можете видеть выше, я сначала вызываю Mapper.Map<List<DisplayCategoryViewModel>>, чтобы сообщить AutoMapper для сопоставления объекта с использованием сопоставленного профиля. Но сопоставленный профиль не знает, что свойства DateTime должны быть преобразованы с использованием реализации IDateTimeConverter. Таким образом, все значения DateTime отображаются как есть, что означает, что они все еще находятся в UTC TimeZone. Чтобы преобразовать значения DateTime, я вызываю метод ForEach, который повторяет все записи, чтобы исправить свойства DateTime.

Кроме того, все мои модели ViewModel являются простыми DTO, поэтому я не хочу добавлять туда никакой логики. Я не хочу вставлять что-либо в конструктор любого из моих кодов неисправности.

Я надеюсь, что мне удастся добавить какое-то правило в конфигурацию Mapping, чтобы оно могло преобразовать любое свойство DateTime в UTC или в местный часовой пояс. Или передайте обратный вызов Mapper.Map<>(), чтобы я мог передать пользовательскую логику, которая применяется во время процесса сопоставления, чтобы избежать необходимости создавать еще один цикл для сопоставленных записей.

1 Ответ

0 голосов
/ 31 октября 2018

Вы можете использовать распознаватель пользовательских значений .

Сначала создайте пользовательское разрешение, которое зависит от вашей 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 ());

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...