如何在 ASP.NET MVC Web API 控制器中使用 Automapper 10.1.1?

How do I use Automapper 10.1.1 in ASP.NET MVC Web API controller?

我正在尝试重新制作我不久前使用 ASP.NET MVC 制作的应用程序,但我 运行 遇到的问题是由使用最新版本的 Automapper。我第一次做应用的时候跟着一个教程,在这个教程中,我使用的Automapper版本是4.1.0.

然而,这个版本是在 2016 年左右发布的——教程很老了——从那时起 Automapper 发生了很多变化,即很多东西现在已经过时了。

在以前的版本中,您可以使用 Mapper class 中的静态 CreateMap 方法,但现在已过时。

这是我用老方法做的。首先,我创建了一个派生自 Profile 的 class 来存储我的配置。

public class MappingProfile : Profile
{
    public MappingProfile()
    {
       // Domain to DTO
       Mapper.CreateMap<Customer, CustomerDto>();
       Mapper.CreateMap<Movie, MovieDto>();
       Mapper.CreateMap<MembershipType, MembershipTypeDto>();
       Mapper.CreateMap<MembershipTypeDto, MembershipType>();
       Mapper.CreateMap<Genre, GenreDto>();
       Mapper.CreateMap<GenreDto, Genre>();

       // Dto to Domain 
       Mapper.CreateMap<CustomerDto, Customer>()
          .ForMember(c => c.Id, opt => opt.Ignore());

       Mapper.CreateMap<MovieDto, Movie>()
          .ForMember(c => c.Id, opt => opt.Ignore());
     }
}

接下来,我初始化了 Mapper class 并在 Global.asax.cs 中添加了配置文件。

public class MvcApplication : System.Web.HttpApplication
{
     protected void Application_Start()
     {
       // Here's the line of code I added
       Mapper.Initialize(c => c.AddProfile<MappingProfile>());

       GlobalConfiguration.Configure(WebApiConfig.Register);
       AreaRegistration.RegisterAllAreas();
       FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
       RouteConfig.RegisterRoutes(RouteTable.Routes);
       BundleConfig.RegisterBundles(BundleTable.Bundles);
     }
}

最后,我使用 Mapper 中的静态 Map 方法将我的域对象映射到我的 API 控制器中的 DTO 对象,反之亦然。顺便说一下,这一切都很好。

public IHttpActionResult CreateCustomer(CustomerDto customerDto)
{
     if (!ModelState.IsValid)
        {
           BadRequest();
        }

     var customer = Mapper.Map<CustomerDto, Customer>(customerDto);

     _context.Customers.Add(customer);
     _context.SaveChanges();

     customerDto.Id = customer.Id;

     return Created(new Uri(Request.RequestUri + "/" + customer.Id ), customerDto);
}

latest Automapper documentation 建议您使用 MapperConfigurationCreateMap 为域和 DTO 对象创建映射,并且每个实例只需要一个 MapperConfiguration应在启动期间实例化的 AppDomain。

我继续创建了一个 MappingProfile 再次存储我的配置 (as recommended by the documentation) 并将此配置包含在 Global.asax.csApplication_Start() 中,这是 MVC 应用程序的启动。

public class MappingProfile : Profile
    {
        public MappingProfile()
        {
            CreateMap<Customer, CustomerDto>();
            CreateMap<CustomerDto, Customer>();
        }
    }
public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            // This is the new way of creating a MapperConfiguration 
            var configuration = new MapperConfiguration(cfg =>
            {
                cfg.AddProfile<MappingProfile>();
            });

            IMapper mapper = configuration.CreateMapper();

            GlobalConfiguration.Configure(WebApiConfig.Register);
            AreaRegistration.RegisterAllAreas();
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
            UnityConfig.RegisterComponents();
        }
    }

此时,文档说您可以使用依赖注入来注入创建的 IMapper 实例。 这是我卡住并 运行 出错的地方。

文档涵盖 ASP.NET Core 很好地解释了如何使用依赖注入,所以我尝试将这种方法与 UnityConfig 一起使用,我在其中创建了 MapperConfiguration 并注册了实例然后 IMapper 尝试将其注入我的网络 API 控制器,如下所示:

public static class UnityConfig
    {
        public static void RegisterComponents()
        {
            var container = new UnityContainer();

            var configuration = new MapperConfiguration(cfg =>
            {
                cfg.AddProfile<MappingProfile>();
            });

            IMapper mapper = configuration.CreateMapper();

            container.RegisterInstance(mapper);
            // register all your components with the container here
            // it is NOT necessary to register your controllers
            
            // e.g. container.RegisterType<ITestService, TestService>();
            
            DependencyResolver.SetResolver(new UnityDependencyResolver(container));
        }
    }
 public class MvcApplication : System.Web.HttpApplication
    {
        
        protected void Application_Start()
        {            
            GlobalConfiguration.Configure(WebApiConfig.Register);
            AreaRegistration.RegisterAllAreas();
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);

            // I registered the components here inside Global.asax.cs
            UnityConfig.RegisterComponents();
        }
    }

这是我尝试通过依赖注入使用实例的地方。

public class CustomersController : ApiController
    {
        private ApplicationDbContext _context;

        private readonly IMapper _mapper;

        public CustomersController()
        {
            _context = new ApplicationDbContext();
        }

        public CustomersController(IMapper mapper)
        {
            _mapper = mapper;
        }

        // GET /api/customers
        public IHttpActionResult GetCustomers()
        {
            var customersDto = _context.Customers
                .Include(c => c.MembershipType)
                .ToList()
                .Select(_mapper.Map<Customer, CustomerDto>);

            return Ok(customersDto);
        }
        // Remaining code omitted because it's unnecessary

我在调试时遇到的错误是 NullReferenceException。在 Watch window 中,我注意到 _mapper 为空,但是,对象确实从数据库中加载。问题是即使在使用 DI 后 _mapper 仍然为 null。

有人可以解释为什么在 ASP.NET MVC Web API 控制器中以这种方式使用此版本的 Automapper 是无效的并且可能 fixes/tips 吗?

经过大量的阅读和研究,我终于弄清楚了哪里出了问题。我相信 Unity.Mvc5 适用于常规控制器而不是 API 控制器。

出于这个原因,我添加了一个单独的包 Unity.AspNet.WebApi,它带有不同的 Unity.Config.cs。添加包时出现提示,问我是否要覆盖之前的Unity.Config.cs(来自Unity.Mvc5),我选择了

从这里开始,我将配置添加到此文件,注册了实例,一切顺利。

这段视频很好地解释了整个事情: https://youtu.be/c38krTX0jeo