2 votes

AutoMapper map long ? vers string

J'ai deux classes, un ViewModel et un Dto, qui sont fondamentalement identiques sauf que le Dto a un champ 'readonly long ? Phone;' alors que le ViewModel a une propriété 'string Phone { get ; set ; }'.

Le seul moyen que j'ai trouvé pour faire fonctionner AutoMapper est de changer la propriété ViewModel en une propriété d'appui :

    public long? Phone { get; set; }

    public string PhoneNumberString
    {
        get
        {
            var srv = DependencyResolver.Current.GetService<IPhoneNumberService>();
            return srv.GetFormattedPhoneNumber(Phone);
        }
        set
        {
            var srv = DependencyResolver.Current.GetService<IPhoneNumberService>();
            Phone = srv.GetLongPhoneNumber(value);
        }
    }

Ensuite, dans AutoMapper, il y a une ligne gigantesque pour appeler le constructeur :

Mapper.Initialize(cfg =>
{
    cfg.CreateMap<MyViewModel, MyDto>()
        .ConstructUsing(src => new MyDto(
            src.Phone
    /* ...Some ~30 other parameters here... */))
        .ReverseMap();
});

...Il doit y avoir une meilleure façon de procéder ? J'ai essayé ceci :

.ForSourceMember(x => x.PhoneNumberString, opt => opt.DoNotValidate())

et

.ForMember(x => x.PhoneNumberString, opt => opt.Ignore())

et

.ForMember(viewModel => viewModel.Phone, options => options.MapFrom<PhoneNumberResolver>());//PhoneNumberResolver implements IValueResolver<ProspectMaintenanceViewModel, ProspectMaintenanceDto, long?>

Qui donnent tous 'Core.DTO.MyDto needs to have a constructor with 0 args or only optional args' lorsqu'ils essaient de mapper, et :

.ForMember(dest => dest.Phone, opt => opt.MapFrom(src => 5))

Ce qui donne 'System.ArgumentException : 'Expression must be writeable' lors de la configuration d'AutoMapper.

Existe-t-il un moyen de faire comprendre à AutoMapper qu'il peut ignorer PhoneNumberString (ou, mieux encore, un moyen de le faire convertir long ? en string pour que je n'aie pas besoin de la propriété backing) sans avoir à utiliser le constructeur du dto ?

Existe-t-il une raison particulière pour laquelle votre DTO n'a pas de constructeur par défaut ?

Tous mes champs sont en lecture seule, ce qui me permet d'inclure un constructeur qui modifie (par exemple, "Description = description ?.Trim() ;") et valide (par exemple, "if (Phone.HasValue && Phone.ToString().Length != 10) throw ...") les paramètres. De cette façon, je peux m'assurer que le Dto, étant un objet valeur, est toujours dans un état valide.

2voto

Prolog Points 58

1) Correspondance avec un champ en lecture seule

Vous avez donc un Dto classe :

public class Dto
{
    public readonly long? PhoneNumber;
}

Et vous essayez de forcer AutoMapper à le faire :

var dto = new Dto();
dto.PhoneNumber = 123; // <== ERROR! A readonly field cannot be assigned to.

AutoMapper ne peut pas écrire dans des champs ou des propriétés en lecture seule. En fait, vous ne pouvez pas écrire dans ces champs ou propriétés. Soit vous transformez votre champ en propriété avec protected o private setter :

public class Dto
{
    public long? PhoneNumber { get; private set; }
}

ou en faire un champ ordinaire en supprimant le champ readonly mot-clé :

public class Dto
{
    public long? PhoneNumber;
}

2) Résolution des valeurs personnalisées

ASP.NET MVC

Utiliser un ValueResolver :

public class StringPhoneNumberResolver : IValueResolver<Dto, ViewModel, string>
{
    private readonly IPhoneNumberService _phoneNumberService;

    public StringPhoneNumberResolver()
    {
        _phoneNumberService = DependencyResolver.Current.GetService<IPhoneNumberService>();
    }

    public string Resolve(Dto source, ViewModel destination, string destMember, ResolutionContext context)
    {
        return _phoneNumberService.GetFormattedPhoneNumber(source.PhoneNumber);
    }
}

Vous devez savoir qu'en règle générale, l'injection de service dans un DTO ou un IValueResolver . AutoMapper devrait être muet et tous les types d'injections et autres devraient être gérés ailleurs. Ceci étant dit, voici la configuration d'AutoMapper :

Mapper.Initialize(cfg =>
{
    cfg.CreateMap<Dto, ViewModel>()
        .ForMember(viewModel => viewModel.PhoneNumber, options =>
            options.MapFrom<StringPhoneNumberResolver>());
});

Si vous souhaitez inverser le processus de long ==> string a string ==> long il suffit d'ajouter un autre résolveur de valeurs :

public class LongPhoneNumberResolver : IValueResolver<ViewModel, Dto, long?>
{
    private readonly IPhoneNumberService _phoneNumberService;

    public LongPhoneNumberResolver()
    {
        _phoneNumberService = DependencyResolver.Current.GetService<IPhoneNumberService>();
    }

    public long? Resolve(ViewModel source, Dto destination, long? destMember, ResolutionContext context)
    {
        return _phoneNumberService.GetLongPhoneNumber(source.PhoneNumber);
    }
}

NET Core

Si vous souhaitez travailler dans l'environnement .NET Core, qui prend entièrement en charge le IServiceCollection vous devez ajouter cette configuration AutoMapper :

serviceCollection.AddAutoMapper(config =>
{
    config.CreateMap<Dto, ViewModel>()
        .ForMember(viewModel => viewModel.PhoneNumber, options =>
            options.MapFrom<StringPhoneNumberResolver>());
}, typeof(Startup));

et ont ensuite IPhoneNumberServce automatiquement injecté dans le résolveur de valeurs :

public StringPhoneNumberResolver(IPhoneNumberService phoneNumberService)
{
    _phoneNumberService = phoneNumberService;
}

Pour l'injection de dépendances, j'ai utilisé automapper.extensions.microsoft.dependencyinjection paquete.

0voto

Sarov Points 321

J'ai trouvé le problème. Il n'a absolument rien à voir avec ce que je pensais qu'il faisait. Mapping map long ? to string fonctionne dès sa sortie de la boîte .

Le problème que j'ai rencontré concernait une propriété totalement différente.

J'avais la structure suivante :

public class MyDto
{
    public readonly AddressDto BillingAddress;
    public readonly AddressDto ShippingAddress;
    public readonly long? Phone;
    ...
}
public class AddressDto
{
    public readonly string Country;
    public readonly string SubnationalEntity;
    ...
}
public class MyViewModel
{
    public string BillingAddressCountry { get; set; }
    public string BillingAddressSubnationalEntity { get; set; }
    public string ShippingAddressCountry { get; set; }
    public string ShippingAddressSubnationalEntity { get; set; }
    public string Phone { get; set; }
    ...
}

Il a fonctionné une fois que je l'ai remplacé par ce qui suit :

public class MyDto
{
    public readonly AddressDto BillingAddress;
    public readonly AddressDto ShippingAddress;
    public readonly long? Phone;
    ...
}
public class AddressDto
{
    public readonly string Country;
    public readonly string SubnationalEntity;
    ...
}
public class MyViewModel
{
    public string AddressViewModel BillingAddress { get; set; }
    public string AddressViewModel ShippingAddress { get; set; }
    public string Phone { get; set; }
    ...
}
public class AddressViewModel
{
    public string Country { get; set; }
    public string SubnationalEntity { get; set; }
    ...
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X