ASP.NET MVC
アプリケーションでAutoMapper
を使用しています。 AutoMapper.CreateMap
はオーバーヘッドが大きいため、他の場所に移動する必要があると言われました。これらの呼び出しをたった1か所に配置するようにアプリケーションを設計する方法がよくわかりません。
Webレイヤー、サービスレイヤー、データレイヤーがあります。それぞれ独自のプロジェクト。 Ninject
を使用してすべてをDIします。ウェブ層とサービス層の両方でAutoMapper
を利用します。
それでは、AutoMapper
のCreateMapのセットアップは何ですか?どこに置きますか?どうやって呼ぶの?
静的クラスである限り、問題ではありません。それはすべて慣習に関するものです。
conventionは、各「レイヤー」(ウェブ、サービス、データ)がAutoMapperXConfiguration.cs
という単一のファイルを持ち、Configure()
という単一のメソッドを持ち、X
がレイヤーであることです。
次に、Configure()
メソッドは、エリアごとにprivate
メソッドを呼び出します。
Web層の構成の例を次に示します。
public static class AutoMapperWebConfiguration
{
public static void Configure()
{
ConfigureUserMapping();
ConfigurePostMapping();
}
private static void ConfigureUserMapping()
{
Mapper.CreateMap<User,UserViewModel>();
}
// ... etc
}
各「集計」(ユーザー、投稿)に対してメソッドを作成するため、物事はうまく分離されます。
次に、Global.asax
:
AutoMapperWebConfiguration.Configure();
AutoMapperServicesConfiguration.Configure();
AutoMapperDomainConfiguration.Configure();
// etc
それは一種の「言葉のインターフェース」のようなものです-それを強制することはできませんが、あなたはそれを期待しているので、必要に応じてコーディング(およびリファクタリング)できます。
編集:
私は今、AutoMapper profiles を使用していることに言及すると思ったので、上の例は次のようになります。
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>();
}
}
よりクリーン/より堅牢。
Webプロジェクトが含まれているアセンブリを参照している限り、どこにでも実際に置くことができます。コンソールアプリを実行するか、単体テストプロジェクトを実行すると、マッピング構成もそれらのプロジェクトから利用可能になります。
Global.asaxで、すべてのマップを設定するメソッドを呼び出します。下記参照:
AutoMapperBootStrapper.csファイル
public static class AutoMapperBootStrapper
{
public static void BootStrap()
{
AutoMapper.CreateMap<Object1, Object2>();
// So on...
}
}
アプリケーション起動時のGlobal.asax
ただ電話する
AutoMapperBootStrapper.BootStrap();
今、一部の人々は、このメソッドに反論するでしょう。いくつかのSOLID原則に違反しています。ここで彼らは読書のためです。
更新: AutoMapper v2でSelfProfiler
が削除されたため、ここに投稿されたアプローチはもはや有効ではありません。
トアイと同じようなアプローチを取ります。しかし、組み込みの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()
これで、1つのモノリシックマッピングクラスを心配することなく、自分にとって意味のある場所にマッピングクラスを配置できます。
以下を順守している方へ:
プロファイルと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));
}
}
トレードオフは、静的なマッパーの代わりにIMappingEngineインターフェイスによってマッパーを参照する必要があることですが、それは私が一緒に暮らすことができる慣習です。
上記のソリューションはすべて、マッピング設定の一部を設定するために他のメソッドを呼び出す必要がある静的メソッド(app_startまたは任意の場所から)を提供します。ただし、モジュラーアプリケーションを使用している場合、そのモジュールはいつでもアプリケーションにプラグインおよびプラグアウトできるため、これらのソリューションは機能しません。 app_pre_start
およびapp_post_start
anyで実行するメソッドを登録できるWebActivator
ライブラリを使用することをお勧めします。
// 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...
NuGetを使用してWebActivator
をインストールできます。
最良の答えに加えて、 Autofac IoCライブラリを使用して自動化を追加するのが良い方法です。これを使用すると、開始に関係なくプロファイルを定義できますjust。
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();
上記のコードは、すべてのProfileサブクラスを検出し、それらを自動的に開始します。
すべてのマッピングロジックを1つの場所に配置することは、私にとって良い習慣ではありません。マッピングクラスは非常に大きく、保守が非常に難しいためです。
同じcsファイルにViewModelクラスと一緒にマッピングすることをお勧めします。この規則に従って、必要なマッピング定義に簡単にナビゲートできます。さらに、マッピングクラスを作成するときに、同じファイル内にあるため、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>();
}
}
静的メソッドMapper.Map()を使用するAutoMapperの新しいバージョンからは非推奨です。したがって、MvcApplication(Global.asax.cs)に静的プロパティとしてMapperConfigurationを追加し、それを使用して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
AutoMapperの新しいバージョン(5.x)を使用するvb.netプログラマー向け。
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)
以下を使用している(紛失している)人向け:
「 新しい方法 」でAutoMapperを統合する方法を次に示します。また、Hugeこのおかげで answer(and question)
1-「ProfileMappers」というWebAPIプロジェクトにフォルダーを作成しました。このフォルダーに、マッピングを作成するすべてのプロファイルクラスを配置します。
public class EntityToViewModelProfile : Profile
{
protected override void Configure()
{
CreateMap<User, UserViewModel>();
}
public override string ProfileName
{
get
{
return this.GetType().Name;
}
}
}
2-App_Startに、SimpleInjectorコンテナーを構成するSimpleInjectorApiInitializerがあります。
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);