Где разместить AutoMapper.CreateMaps?
Я использую AutoMapper
в ASP.NET MVC
приложение. Мне сказали, что я должен переместить AutoMapper.CreateMap
в другом месте, поскольку у них много накладных расходов. Я не слишком уверен, как спроектировать мое приложение, чтобы эти вызовы были размещены всего в одном месте.
У меня есть веб-слой, сервисный слой и слой данных. У каждого свой проект. я использую Ninject
чтобы я все. Я буду использовать AutoMapper
как на веб-уровне, так и на уровне сервиса.
Итак, каковы ваши настройки для AutoMapper
создать карту? Куда ты это положил? Как вы это называете?
10 ответов
Неважно, если это статический класс. Это все о конвенции.
Наше соглашение состоит в том, что каждый "слой" (веб, сервисы, данные) имеет один файл с именем AutoMapperXConfiguration.cs
с одним методом под названием Configure()
, где X
это слой.
Configure()
Затем метод вызывает private
методы для каждой области.
Вот пример нашей конфигурации веб-уровня:
public static class AutoMapperWebConfiguration
{
public static void Configure()
{
ConfigureUserMapping();
ConfigurePostMapping();
}
private static void ConfigureUserMapping()
{
Mapper.CreateMap<User,UserViewModel>();
}
// ... etc
}
Мы создаем метод для каждого "агрегата" (User, Post), чтобы все было хорошо разделено.
Тогда ваш Global.asax
:
AutoMapperWebConfiguration.Configure();
AutoMapperServicesConfiguration.Configure();
AutoMapperDomainConfiguration.Configure();
// etc
Это что-то вроде "интерфейса слов" - не может обеспечить его выполнение, но вы ожидаете этого, поэтому вы можете кодировать (и реорганизовывать) при необходимости.
РЕДАКТИРОВАТЬ:
Просто подумал, что упомяну, что теперь я использую профили AutoMapper, поэтому приведенный выше пример выглядит так:
public static class AutoMapperWebConfiguration
{
public static void Configure()
{
Mapper.Initialize(cfg =>
{
cfg.AddProfile(new UserProfile());
cfg.AddProfile(new PostProfile());
});
}
}
public class UserProfile : Profile
{
protected override void Configure()
{
Mapper.CreateMap<User,UserViewModel>();
}
}
Гораздо чище / надежнее.
Вы действительно можете поместить его в любое место, если ваш веб-проект ссылается на сборку, в которой он находится. В вашей ситуации я бы поместил его на сервисный уровень, так как он будет доступен для веб-слоя и сервисного уровня, а позже, если вы решите Создайте консольное приложение или проект модульного тестирования, конфигурация сопоставления будет также доступна из этих проектов.
В вашем Global.asax вы затем вызовете метод, который устанавливает все ваши карты. Увидеть ниже:
Файл AutoMapperBootStrapper.cs
public static class AutoMapperBootStrapper
{
public static void BootStrap()
{
AutoMapper.CreateMap<Object1, Object2>();
// So on...
}
}
Global.asax при запуске приложения
просто позвони
AutoMapperBootStrapper.BootStrap();
Теперь некоторые люди будут утверждать, что этот метод нарушает некоторые принципы SOLID, которые они имеют веские аргументы. Вот они для чтения.
Настройка Automapper в Bootstrapper нарушает принцип Open-Closed?
Обновление: подход, опубликованный здесь, больше не действителен как SelfProfiler
был удален с AutoMapper v2.
Я бы выбрал такой же подход, как и Тоаи. Но я бы использовал встроенный SelfProfiler<>
класс для обработки карт, затем используйте Mapper.SelfConfigure
функция для инициализации.
Используя этот объект в качестве источника:
public class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime BirthDate { get; set; }
public string GetFullName()
{
return string.Format("{0} {1}", FirstName, LastName);
}
}
И это как пункт назначения:
public class UserViewModel
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class UserWithAgeViewModel
{
public int Id { get; set; }
public string FullName { get; set; }
public int Age { get; set; }
}
Вы можете создать эти профили:
public class UserViewModelProfile : SelfProfiler<User,UserViewModel>
{
protected override void DescribeConfiguration(IMappingExpression<User, UserViewModel> map)
{
//This maps by convention, so no configuration needed
}
}
public class UserWithAgeViewModelProfile : SelfProfiler<User, UserWithAgeViewModel>
{
protected override void DescribeConfiguration(IMappingExpression<User, UserWithAgeViewModel> map)
{
//This map needs a little configuration
map.ForMember(d => d.Age, o => o.MapFrom(s => DateTime.Now.Year - s.BirthDate.Year));
}
}
Для инициализации в вашем приложении создайте этот класс
public class AutoMapperConfiguration
{
public static void Initialize()
{
Mapper.Initialize(x=>
{
x.SelfConfigure(typeof (UserViewModel).Assembly);
// add assemblies as necessary
});
}
}
Добавьте эту строку в ваш файл global.asax.cs: AutoMapperConfiguration.Initialize()
Теперь вы можете размещать свои классы отображения там, где они имеют смысл, и не беспокоиться об одном монолитном классе отображения.
Для тех из вас, кто придерживается следующего:
- используя контейнер ioc
- не люблю открываться для этого
- не нравится монолитный конфигурационный файл
Я сделал комбо между профилями и используя мой контейнер ioc:
Конфигурация IoC:
public class Automapper : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(Classes.FromThisAssembly().BasedOn<Profile>().WithServiceBase());
container.Register(Component.For<IMappingEngine>().UsingFactoryMethod(k =>
{
Profile[] profiles = k.ResolveAll<Profile>();
Mapper.Initialize(cfg =>
{
foreach (var profile in profiles)
{
cfg.AddProfile(profile);
}
});
profiles.ForEach(k.ReleaseComponent);
return Mapper.Engine;
}));
}
}
Пример конфигурации:
public class TagStatusViewModelMappings : Profile
{
protected override void Configure()
{
Mapper.CreateMap<Service.Contracts.TagStatusViewModel, TagStatusViewModel>();
}
}
Пример использования:
public class TagStatusController : ApiController
{
private readonly IFooService _service;
private readonly IMappingEngine _mapper;
public TagStatusController(IFooService service, IMappingEngine mapper)
{
_service = service;
_mapper = mapper;
}
[Route("")]
public HttpResponseMessage Get()
{
var response = _service.GetTagStatus();
return Request.CreateResponse(HttpStatusCode.Accepted, _mapper.Map<List<ViewModels.TagStatusViewModel>>(response));
}
}
Компромисс в том, что вы должны ссылаться на Mapper с помощью интерфейса IMappingEngine вместо статического Mapper, но с этим соглашением я могу согласиться.
Все вышеперечисленные решения предоставляют статический метод для вызова (из app_start или любого другого места), который он должен вызывать другими методами для настройки частей mapping-конфигурации. Но если у вас есть модульное приложение, эти модули могут подключаться и отключаться от приложения в любое время, эти решения не работают. Я предлагаю использовать WebActivator
библиотека, которая может зарегистрировать некоторые методы для запуска app_pre_start
а также app_post_start
в любом месте:
// in MyModule1.dll
public class InitMapInModule1 {
static void Init() {
Mapper.CreateMap<User, UserViewModel>();
// other stuffs
}
}
[assembly: PreApplicationStartMethod(typeof(InitMapInModule1), "Init")]
// in MyModule2.dll
public class InitMapInModule2 {
static void Init() {
Mapper.CreateMap<Blog, BlogViewModel>();
// other stuffs
}
}
[assembly: PreApplicationStartMethod(typeof(InitMapInModule2), "Init")]
// in MyModule3.dll
public class InitMapInModule3 {
static void Init() {
Mapper.CreateMap<Comment, CommentViewModel>();
// other stuffs
}
}
[assembly: PreApplicationStartMethod(typeof(InitMapInModule2), "Init")]
// and in other libraries...
Вы можете установить WebActivator
через NuGet.
В дополнение к лучшему ответу, хорошим способом является использование библиотеки Autofac IoC для добавления некоторой автоматизации. При этом вы просто определяете свои профили независимо от инициаций.
public static class MapperConfig
{
internal static void Configure()
{
var myAssembly = Assembly.GetExecutingAssembly();
var builder = new ContainerBuilder();
builder.RegisterAssemblyTypes(myAssembly)
.Where(t => t.IsSubclassOf(typeof(Profile))).As<Profile>();
var container = builder.Build();
using (var scope = container.BeginLifetimeScope())
{
var profiles = container.Resolve<IEnumerable<Profile>>();
foreach (var profile in profiles)
{
Mapper.Initialize(cfg =>
{
cfg.AddProfile(profile);
});
}
}
}
}
и вызывая эту строку в Application_Start
метод:
MapperConfig.Configure();
Приведенный выше код находит все подклассы профиля и запускает их автоматически.
Размещение всей логики сопоставления в одном месте не является хорошей практикой для меня. Потому что класс отображения будет очень большим и очень сложным в обслуживании.
Я рекомендую поместить сопоставление вместе с классом ViewModel в тот же файл cs. Вы можете легко перейти к определению отображения, которое вы хотите, следуя этому соглашению. Более того, при создании класса отображения вы можете быстрее ссылаться на свойства ViewModel, поскольку они находятся в одном файле.
Таким образом, ваш класс модели представления будет выглядеть так:
public class UserViewModel
{
public ObjectId Id { get; set; }
public string Firstname { get; set; }
public string Lastname { get; set; }
public string Email { get; set; }
public string Password { get; set; }
}
public class UserViewModelMapping : IBootStrapper // Whatever
{
public void Start()
{
Mapper.CreateMap<User, UserViewModel>();
}
}
В новой версии AutoMapper использование статического метода Mapper.Map() устарело. Таким образом, вы можете добавить MapperConfiguration в качестве статического свойства в MvcApplication (Global.asax.cs) и использовать его для создания экземпляра Mapper.
App_Start
public class MapperConfig
{
public static MapperConfiguration MapperConfiguration()
{
return new MapperConfiguration(_ =>
{
_.AddProfile(new FileProfile());
_.AddProfile(new ChartProfile());
});
}
}
Global.asax.cs
public class MvcApplication : System.Web.HttpApplication
{
internal static MapperConfiguration MapperConfiguration { get; private set; }
protected void Application_Start()
{
MapperConfiguration = MapperConfig.MapperConfiguration();
...
}
}
BaseController.cs
public class BaseController : Controller
{
//
// GET: /Base/
private IMapper _mapper = null;
protected IMapper Mapper
{
get
{
if (_mapper == null) _mapper = MvcApplication.MapperConfiguration.CreateMapper();
return _mapper;
}
}
}
https://github.com/AutoMapper/AutoMapper/wiki/Migrating-from-static-API
Для тех, кто (потерян) использует:
- WebAPI 2
- SimpleInjector 3.1
- AutoMapper 4.2.1 (с профилями)
Вот как мне удалось интегрировать AutoMapper "по- новому". Также огромное спасибо за этот ответ (и вопрос)
1 - Создана папка в проекте WebAPI под названием "ProfileMappers". В эту папку я помещаю все классы своих профилей, которые создают мои отображения:
public class EntityToViewModelProfile : Profile
{
protected override void Configure()
{
CreateMap<User, UserViewModel>();
}
public override string ProfileName
{
get
{
return this.GetType().Name;
}
}
}
2 - В моем App_Start у меня есть SimpleInjectorApiInitializer, который настраивает мой контейнер SimpleInjector:
public static Container Initialize(HttpConfiguration httpConfig)
{
var container = new Container();
container.Options.DefaultScopedLifestyle = new WebApiRequestLifestyle();
//Register Installers
Register(container);
container.RegisterWebApiControllers(GlobalConfiguration.Configuration);
//Verify container
container.Verify();
//Set SimpleInjector as the Dependency Resolver for the API
GlobalConfiguration.Configuration.DependencyResolver =
new SimpleInjectorWebApiDependencyResolver(container);
httpConfig.DependencyResolver = new SimpleInjectorWebApiDependencyResolver(container);
return container;
}
private static void Register(Container container)
{
container.Register<ISingleton, Singleton>(Lifestyle.Singleton);
//Get all my Profiles from the assembly (in my case was the webapi)
var profiles = from t in typeof(SimpleInjectorApiInitializer).Assembly.GetTypes()
where typeof(Profile).IsAssignableFrom(t)
select (Profile)Activator.CreateInstance(t);
//add all profiles found to the MapperConfiguration
var config = new MapperConfiguration(cfg =>
{
foreach (var profile in profiles)
{
cfg.AddProfile(profile);
}
});
//Register IMapper instance in the container.
container.Register<IMapper>(() => config.CreateMapper(container.GetInstance));
//If you need the config for LinqProjections, inject also the config
//container.RegisterSingleton<MapperConfiguration>(config);
}
3 - Startup.cs
//Just call the Initialize method on the SimpleInjector class above
var container = SimpleInjectorApiInitializer.Initialize(configuration);
4 - Затем в вашем контроллере просто введите, как обычно, интерфейс IMapper:
private readonly IMapper mapper;
public AccountController( IMapper mapper)
{
this.mapper = mapper;
}
//Using..
var userEntity = mapper.Map<UserViewModel, User>(entity);
Для программистов vb.net, использующих новую версию (5.x) AutoMapper.
Global.asax.vb:
Public Class MvcApplication
Inherits System.Web.HttpApplication
Protected Sub Application_Start()
AutoMapperConfiguration.Configure()
End Sub
End Class
AutoMapperConfiguration:
Imports AutoMapper
Module AutoMapperConfiguration
Public MapperConfiguration As IMapper
Public Sub Configure()
Dim config = New MapperConfiguration(
Sub(cfg)
cfg.AddProfile(New UserProfile())
cfg.AddProfile(New PostProfile())
End Sub)
MapperConfiguration = config.CreateMapper()
End Sub
End Module
Профили:
Public Class UserProfile
Inherits AutoMapper.Profile
Protected Overrides Sub Configure()
Me.CreateMap(Of User, UserViewModel)()
End Sub
End Class
Отображение:
Dim ViewUser = MapperConfiguration.Map(Of UserViewModel)(User)