如何在asp.net core中向模型注入依赖项?

8

假设我有一个控制器操作看起来像这样:

[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。我该如何注入它?我知道可以将服务注入控制器并通过这种方式对我的模型执行操作,但如果我更喜欢使用面向对象的方法而不是贫血领域模型,该怎么办?这是不可能的吗?


2
请查看https://dev59.com/SJrga4cB1Zd3GeqPlVci#39244267。 - Red
3个回答

10

你应该考虑使用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);
    }
    

5
除了DTO之外,您还可以使用丰富的模型。您需要一个定制的模型绑定器,它将负责将构造函数注入模型中。
内置的模型绑定器抱怨找不到默认构造函数。因此,您需要自定义一个。
您可以在这里找到类似问题的解决方案,该方案检查注册的服务以创建模型。
值得注意的是,下面的代码片段提供略有不同的功能,希望能满足您的特定需求。以下代码期望具有构造函数注入的模型。当然,这些模型具有您可能定义的常规属性。这些属性被填充,正如预期的那样,所以好处是在绑定具有构造函数注入的模型时获得正确的行为。
    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;
    }
}

Here's how the binder would be registered:

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());
});

我不确定新的binder是否必须在完全相同的索引处注册,您可以进行实验。

最后,这是您如何使用它:

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
}

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

希望这有帮助。


我尝试了相似的代码,并且它可以工作,直到我在控制器的操作中添加了[FromBody]。 在这一行“var propertyBinders = context.Metadata.Properties.ToDictionary(property => property,context.CreateBinder);”中,我们似乎丢失了有关BindingSource的信息。 - Igor
为什么需要显式使用 [FromBody] 标记? - Alexander Christov

0

你尝试在“配置服务”方法中添加服务以提供服务了吗?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接