ASP.NET MVC 5アプリケーションでAutoMapper 6.2.0を使用しています。
コントローラを介してビューを呼び出すと、すべてのものが正しく表示されます。しかし、そのビューを更新すると、Visual Studioにエラーが表示されます。
System.InvalidOperationException: 'マッパーは既に初期化されています。初期化は、アプリケーションドメイン/プロセスごとに1回呼び出す必要があります。
AutoMapperは1つのコントローラーでのみ使用しています。まだどこでも設定を行っておらず、他のサービスやコントローラーでAutoMapperを使用していません。
私のコントローラー:
public class StudentsController : Controller
{
private DataContext db = new DataContext();
// GET: Students
public ActionResult Index([Form] QueryOptions queryOptions)
{
var students = db.Students.Include(s => s.Father);
AutoMapper.Mapper.Initialize(cfg =>
{
cfg.CreateMap<Student, StudentViewModel>();
});
return View(new ResulList<StudentViewModel> {
QueryOptions = queryOptions,
Model = AutoMapper.Mapper.Map<List<Student>,List<StudentViewModel>>(students.ToList())
});
}
// Other Methods are deleted for ease...
コントローラー内のエラー:
私のモデルクラス:
public class Student
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string CNIC { get; set; }
public string FormNo { get; set; }
public string PreviousEducaton { get; set; }
public string DOB { get; set; }
public int AdmissionYear { get; set; }
public virtual Father Father { get; set; }
public virtual Sarparast Sarparast { get; set; }
public virtual Zamin Zamin { get; set; }
public virtual ICollection<MulaqatiMehram> MulaqatiMehram { get; set; }
public virtual ICollection<Result> Results { get; set; }
}
私のViewModelクラス:
public class StudentViewModel
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string CNIC { get; set; }
public string FormNo { get; set; }
public string PreviousEducaton { get; set; }
public string DOB { get; set; }
public int AdmissionYear { get; set; }
public virtual FatherViewModel Father { get; set; }
public virtual SarparastViewModel Sarparast { get; set; }
public virtual ZaminViewModel Zamin { get; set; }
}
ビューを更新すると、StudentsController
の新しいインスタンスが作成されるため、マッパーが再初期化され、エラーメッセージ "マッパーは既に初期化されています"が表示されます。
スタートガイド から
AutoMapperはどこで設定しますか?
静的なマッパーメソッドを使用している場合、構成はAppDomainごとに1回だけ行う必要があります。つまり、構成コードを配置する最適な場所は、ASP.NETアプリケーションのGlobal.asaxファイルなどのアプリケーションの起動時です。
これを設定する1つの方法は、すべてのマッピング構成を静的メソッドに配置することです。
App_Start/AutoMapperConfig.cs:
public class AutoMapperConfig
{
public static void Initialize()
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Student, StudentViewModel>();
...
});
}
}
次に、Global.asax.csでこのメソッドを呼び出します
protected void Application_Start()
{
App_Start.AutoMapperConfig.Initialize();
}
これで、コントローラーアクションで(再)使用できます。
public class StudentsController : Controller
{
public ActionResult Index(int id)
{
var query = db.Students.Where(...);
var students = AutoMapper.Mapper.Map<List<StudentViewModel>>(query.ToList());
return View(students);
}
}
単体テストのシナリオで静的実装に固執したい/必要な場合は、初期化を呼び出す前にAutoMapper.Mapper.Reset()
を呼び出すことができることに注意してください。ドキュメントに記載されているように、これは本番コードでは使用しないでください。
ソース: AutoMapperドキュメント 。
以前にこの方法を使用したことがあり、バージョン6.1.1まで機能しました。
Mapper.Initialize(cfg => cfg.CreateMap<ContactModel, ContactModel>()
.ConstructUsing(x => new ContactModel(LoggingDelegate))
.ForMember(x => x.EntityReference, opt => opt.Ignore())
);
バージョン6.2以降、これは機能しなくなりました。 Automapperを正しく使用するには、新しいマッパーを作成します。このマッパーは次のようになります。
var mapper = new MapperConfiguration(cfg => cfg.CreateMap<ContactModel, ContactModel>()
.ConstructUsing(x => new ContactModel(LoggingDelegate))
.ForMember(x => x.EntityReference, opt => opt.Ignore())).CreateMapper();
var model = mapper.Map<ContactModel>(this);
AutoMapper
を本当に「再初期化」する必要がある場合は、 インスタンスベースのAPIに切り替える を使用して、System.InvalidOperationException
を回避する必要があります:Mapper already initialized. You must call Initialize once per application domain/process.
たとえば、TestServer
テスト用にxUnit
を作成する場合、fixure
クラスコンストラクター内でServiceCollectionExtensions.UseStaticRegistration
をfalse
に設定するだけで、トリックを作成できます。
public TestServerFixture()
{
ServiceCollectionExtensions.UseStaticRegistration = false; // <-- HERE
var hostBuilder = new WebHostBuilder()
.UseEnvironment("Testing")
.UseStartup<Startup>();
Server = new TestServer(hostBuilder);
Client = Server.CreateClient();
}
単体テストの場合、Mapper.Reset()を単体テストクラスに追加できます。
[TearDown]
public void TearDown()
{
Mapper.Reset();
}
Automapper 8.0.0バージョン
AutoMapper.Mapper.Reset();
Mapper.Initialize(
cfg => {
cfg.CreateMap<sourceModel,targetModel>();
}
);
静的APIおよびインスタンスAPIとしてオートマッパーを使用できます。マッパーは既に初期化されていますは、静的APIでは、マッパーを初期化した場所でmapper.Reset()を使用できますが、これはまったく答えではありません。
インスタンスAPIで試す
var students = db.Students.Include(s => s.Father);
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<Student, StudentViewModel>();
});
IMapper iMapper = config.CreateMapper();
return iMapper.Map<List<Student>, List<StudentViewModel>>(students);
単にMapper.Reset()
を使用できます。
例:
public static TDestination MapToObject<TSource, TDestination>(TSource Obj)
{
Mapper.Initialize(cfg => cfg.CreateMap<TSource, TDestination>());
TDestination tDestination = Mapper.Map<TDestination>(Obj);
Mapper.Reset();
return tDestination;
}
UnitTestでMapperを使用していて、複数のテストを実行している場合は、Mapper.Reset()
を使用できます
//Your mapping.
public static void Initialize()
{
Mapper.Reset();
Mapper.Initialize(cfg =>
{
cfg.CreateMap<***>
}
//Your test classes.
[TestInitialize()]
public void Initialize()
{
AutoMapping.Initialize();
}
private static bool _mapperIsInitialized = false;
public InventoryController()
{
if (!_mapperIsInitialized)
{
_mapperIsInitialized = true;
Mapper.Initialize(
cfg =>
{
cfg.CreateMap<Inventory, Inventory>()
.ForMember(x => x.Orders, opt => opt.Ignore());
}
);
}
}
MsTestを使用している場合、AssemblyInitialize属性を使用して、マッピングがそのアセンブリ(ここではテストアセンブリ)に対して1回だけ構成されるようにすることができます。これは通常、コントローラーユニットテストの基本クラスに追加されます。
[TestClass]
public class BaseUnitTest
{
[AssemblyInitialize]
public static void AssemblyInit(TestContext context)
{
AutoMapper.Mapper.Initialize(cfg =>
{
cfg.CreateMap<Source, Destination>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.EmailAddress));
});
}
}
この答えがお役に立てば幸いです