【问题标题】:How to inject dependencies into models in asp.net core?如何将依赖项注入到 asp.net core 中的模型中?
【发布时间】:2016-09-01 16:50:07
【问题描述】:

假设我有一个如下所示的控制器操作:

[HttpPost]
public async Task<IActionResult> Add([FromBody] MyModel model){
    await model.Save();
    return CreatedAtRoute("GetModel", new {id = model.Id}, model);
}

为了让model.Save 工作,它需要一些依赖项:

public class MyModel{
    private readonly ApplicationDbContext _context;
    public MyModel(ApplicationDbContext context){
        _context = context;
    }
    public async Task Save(){
        // Do something with _context;
    }
}

截至目前,上下文是MyModel 的构造函数中的null。我怎样才能注入它?我知道我可以将服务注入控制器并以这种方式对我的模型执行操作,但是如果我宁愿使用面向对象的方法而不是贫血的域模型呢?这根本不可能吗?

【问题讨论】:

标签: c# asp.net asp.net-core asp.net-core-mvc


【解决方案1】:

您应该考虑使用 DTO(数据传输对象)模式重构代码。如果只是

  • MyModel 只能由数据容器组成 -> 包含属性/计算属性。
  • Save() 方法中的逻辑应该被提取到像 ModelRepository 这样的单独类中,它应该知道像 ApplicationDbContext 这样的依赖关系:

     public class ModelRepository
     {
        private readonly ApplicationDbContext _context;
    
        public ModelRepository(ApplicationDbContext context)
        {
            _context = context;
        }
    
        public async Task Save(MyModel model)
        {
            // Do something with _context;
        }
    }
    
  • 最后,您的控制器应该使用 ModelRepository 的实例(使用内置 DI 解决它)来保存您的数据:

    [HttpPost]
    public async Task<IActionResult> Add([FromBody] MyModel model)
    {
        await _modelRepository.Save(model);
        return CreatedAtRoute("GetModel", new {id = model.Id}, model);
    }
    

【讨论】:

    【解决方案2】:

    嗯,除了 DTO,您还可以使用丰富的模型。您将需要一个自定义的模型绑定器,它将负责ctor 注入模型

    内置模型绑定器抱怨找不到默认 ctor。因此,您需要一个自定义的。

    您可能会找到类似问题here 的解决方案,它会检查已注册的服务以创建模型。

    请务必注意,下面的 sn-ps 提供的功能略有不同,希望能满足您的特定需求。下面的代码需要带有 ctor 注入的模型。当然,这些模型具有您可能已经定义的常用属性。这些属性的填充完全符合预期,因此使用 ctor 注入绑定模型时的正确行为

        public class DiModelBinder : ComplexTypeModelBinder
        {
            public DiModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders) : base(propertyBinders)
            {
            }
    
            /// <summary>
            /// Creates the model with one (or more) injected service(s).
            /// </summary>
            /// <param name="bindingContext"></param>
            /// <returns></returns>
            protected override object CreateModel(ModelBindingContext bindingContext)
            {
                var services = bindingContext.HttpContext.RequestServices;
                var modelType = bindingContext.ModelType;
                var ctors = modelType.GetConstructors();
                foreach (var ctor in ctors)
                {
                    var paramTypes = ctor.GetParameters().Select(p => p.ParameterType).ToList();
                    var parameters = paramTypes.Select(p => services.GetService(p)).ToArray();
                    if (parameters.All(p => p != null))
                    {
                        var model = ctor.Invoke(parameters);
                        return model;
                    }
                }
    
                return null;
            }
        }
    

    此活页夹将由以下人员提供:

    public class DiModelBinderProvider : IModelBinderProvider
    {
        public IModelBinder GetBinder(ModelBinderProviderContext context)
        {
            if (context == null) { throw new ArgumentNullException(nameof(context)); }
    
            if (context.Metadata.IsComplexType && !context.Metadata.IsCollectionType)
            {
                var propertyBinders = context.Metadata.Properties.ToDictionary(property => property, context.CreateBinder);
                return new DiModelBinder(propertyBinders);
            }
    
            return null;
        }
    }
    

    以下是活页夹的注册方式:

    services.AddMvc().AddMvcOptions(options =>
    {
        // replace ComplexTypeModelBinderProvider with its descendent - IoCModelBinderProvider
        var provider = options.ModelBinderProviders.FirstOrDefault(x => x.GetType() == typeof(ComplexTypeModelBinderProvider));
        var binderIndex = options.ModelBinderProviders.IndexOf(provider);
        options.ModelBinderProviders.Remove(provider);
        options.ModelBinderProviders.Insert(binderIndex, new DiModelBinderProvider());
    });
    

    我不太确定新的活页夹是否必须在同一索引处完全注册,您可以尝试一下。

    最后,你可以这样使用它:

    public class MyModel 
    {
        private readonly IMyRepository repo;
    
        public MyModel(IMyRepository repo) 
        {
            this.repo = repo;
        }
    
        ... do whatever you want with your repo
    
        public string AProperty { get; set; }
    
        ... other properties here
    }
    

    模型类由提供(已注册的)服务的绑定器创建,其余模型绑定器提供来自其常用来源的属性值。

    HTH

    【讨论】:

    • 我尝试了类似的代码,直到我在控制器的操作中添加 [FromBody] 之前它一直有效。我似乎在这一行'var propertyBinders = context.Metadata.Properties.ToDictionary(property => property, context.CreateBinder);'我们丢失了有关 BindingSource 的信息。
    • 为什么需要显式的 [FromBody] 装饰?
    【解决方案3】:

    您是否尝试在配置服务方法中添加服务以提供服务

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-09-13
      • 1970-01-01
      • 2018-01-16
      • 2019-02-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-12-19
      相关资源
      最近更新 更多