Autofac“动作注入"功能与ASP.NET MVC模型绑定 [英] Autofac "action injection" with ASP.NET MVC model binding

查看:70
本文介绍了Autofac“动作注入"功能与ASP.NET MVC模型绑定的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在将Autofac与ASP.NET MVC一起使用,并且想知道在Web项目中设置视图模型是否是一种好方法.过去,我只在Controller级别使用了构造器注入,但是认为查看是否可以通过Autofac注入所有内容会很有趣.

I'm using Autofac with ASP.NET MVC and am wondering if my view model setup in the web project is a good approach or not. In the past I only used constructer injection at the Controller level but thought it would be interesting to see if everything could be injected via Autofac.

假设我有一个看起来像这样的视图模型:

Let's say I have a view model that looks this:

public class CollegeViewModel : BaseViewModel
{
    private CollegeModel _model { get; set; }

    public int Id { get; set; }
    public string Name { get; set; }

    public CollegeViewModel(ICsnCache cache, CollegeModel model)
    {
        this._cache = cache;
        this._model = model;
    }

    public void Populate() { /* TODO: */ }
    public void Update() { /* TODO: */ }
}

  • 缓存用于访问代码集(即用于下拉列表)
  • 该模型具有从视图模型中调用的方法(例如Save())
  • 这是使事情变得有些奇怪的地方.我发现自己必须创建一个从System.Web.Mvc.Async.AsyncControllerActionInvoker派生的自定义动作调用程序. Autofac具有其中之一已经是Autofac.Integration.Mvc.ExtensibleActionInvoker.我发现Autofac内置的一个问题是它停止了默认模型绑定.也就是说,它成功注入了我的依赖关系,但是即使POST具有有效的表单数据,其余的视图模型属性也为空.

    This is where things get a bit strange. I found myself having to create a custom action invoker which derives from System.Web.Mvc.Async.AsyncControllerActionInvoker. Autofac has one of these already Autofac.Integration.Mvc.ExtensibleActionInvoker. The problem I found with the one built into Autofac is that it stopped the default model binding. i.e. it succeeded in injecting my dependencies but then the rest of the view model properties were empty even though the POST had valid form data.

    这是Autofac和ASP.NET MVC代码的组合(出于可读性考虑,进行了一些验证和注释):

    This is a combination of Autofac and ASP.NET MVC code (taken out some validation and comments for readability):

    public class CustomExtensibleActionInvoker : System.Web.Mvc.Async.AsyncControllerActionInvoker
    {
        protected override object GetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor)
        {
            object value = null;
            try
            {
                value = base.GetParameterValue(controllerContext, parameterDescriptor);
            }
            catch (MissingMethodException) { }
    
            if (value == null)
            {
                // We got nothing from the default model binding, so try to resolve it.
                var context = Autofac.Integration.Mvc.AutofacDependencyResolver.Current.RequestLifetimeScope;
                value = context.ResolveOptional(parameterDescriptor.ParameterType);
    
                // This is the part I added, which is from the ASP.NET MVC source code
                ModelBindingContext bindingContext = new ModelBindingContext()
                {
                    FallbackToEmptyPrefix = (parameterDescriptor.BindingInfo.Prefix == null), // only fall back if prefix not specified
                    ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => value, parameterDescriptor.ParameterType),
                    ModelName = parameterDescriptor.BindingInfo.Prefix ?? parameterDescriptor.ParameterName,
                    ModelState = controllerContext.Controller.ViewData.ModelState,
                    PropertyFilter = GetPropertyFilter(parameterDescriptor),
                    ValueProvider = controllerContext.Controller.ValueProvider,
                };
    
                value = System.Web.Mvc.ModelBinders.Binders.DefaultBinder.BindModel(controllerContext, bindingContext);
            }
            return value;
        }
    
        private Predicate<string> GetPropertyFilter(ParameterDescriptor parameterDescriptor)
        {
            ParameterBindingInfo bindingInfo = parameterDescriptor.BindingInfo;
            return propertyName => IsPropertyAllowed(propertyName, bindingInfo.Include, bindingInfo.Exclude);
        }
    
        private bool IsPropertyAllowed(string propertyName, ICollection<string> includeProperties, ICollection<string> excludeProperties)
        {
            bool includeProperty = (includeProperties == null) || (includeProperties.Count == 0) || includeProperties.Contains(propertyName, StringComparer.OrdinalIgnoreCase);
            bool excludeProperty = (excludeProperties != null) && excludeProperties.Contains(propertyName, StringComparer.OrdinalIgnoreCase);
            return includeProperty && !excludeProperty;
        }
    }
    

    我意识到我一直在使用默认的模型联编程序,但是如果我需要其他模型联编程序可以对此进行增强.

    I realise I'm always using the default model binder, but that could be enhanced if I ever needed other model binders.

    这可能是Autofac中的错误(事实模型绑定未发生,但DI起作用了)还是我滥用了框架?

    Could it be a bug in Autofac (the fact model binding doesn't happen but the DI works) or am I misusing the framework?

    请注意,这确实有效,但是由于这是早期,我担心自己可能会增加复杂性,也许自己应该自己处理一些依赖项注入.

    Note that this does work, but since it's early days I'm worried I might be adding complexity and maybe should just handle some dependency injection myself.

    编辑(已调整Ruskin的代码以适合我的应用程序):

    Edit (tweaked Ruskin's code to suit my app):

    public class MyCustomModelBinder : DefaultModelBinder
    {
        /// <summary>
        /// If the model type is registered in our Autofac configuration,
        /// use that, otherwise let MVC create the model as per usual
        /// </summary>        
        protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
        {
            var item = DependencyResolver.Current.GetService(modelType);
    
            if (item != null)
            {
                return item;
            }
            return base.CreateModel(controllerContext, bindingContext, modelType);
        }
    }
    

    Global.asax.cs:

    Global.asax.cs:

    protected void Application_Start()
    {
        // removed other code for readability    
        System.Web.Mvc.ModelBinders.Binders.DefaultBinder = new MyCustomModelBinder();
    }
    

    推荐答案

    回答您的问题(我想这是您的问题):

    To answer your question (I think that's your question):

    我发现Autofac内置的一个问题是它停止了默认模型绑定.也就是说,它成功注入了我的依赖项,但是即使POST具有有效的表单数据,其余的视图模型属性也为空.

    The problem I found with the one built into Autofac is that it stopped the default model binding. i.e. it succeeded in injecting my dependencies but then the rest of the view model properties were empty even though the POST had valid form data.

    我不认为这是Autofac中的错误,我相信模型解析早在MVC将其属性绑定到视图模型中之前就已经发生了,那么您何时期望这些属性出现在视图模型中?

    I don't think it's a bug in Autofac, I believe the model resolution happens well before the MVC has bound it's properties into the view model, so when are you expecting the properties to be present in the view model?

    我遇到了完全相同的问题:阅读

    I had the exact same issue: have a a read of this question

    这是您的autofac注册,其中 builder 是您的ContainerBuilder ...

    This is your autofac registration where builder is your ContainerBuilder...

    var types = LoadMyViewModels(); // Do some reflection to get all your viewmodels
    foreach (var type in types)
    {
        Type modelBinderType = typeof(MyCustomModelBinder<>);
        Type[] typeArgs = { modelType };
    
        Type genType = modelBinderType.MakeGenericType(typeArgs);
        object viewmodel = Activator.CreateInstance(genType);
        ModelBinders.Binders.Add(modelType, (IModelBinder)viewmodel);
    
        var registeredType = builder.RegisterType(modelType).AsSelf();
    }
    

    CustomModelBinder

    CustomModelBinder

    [ModelBinderType]
    public class MyCustomModelBinder<T> : DefaultModelBinder where T : class
    {
        [NotNull]
        protected override object CreateModel([NotNull] ControllerContext controllerContext, [NotNull] ModelBindingContext bindingContext, [NotNull] Type modelType)
        {
            var item = DependencyResolver.Current.GetService<T>();
    
            if (item != null)
            {
                return item;
            }
            throw new ArgumentException(string.Format("model type {0} is not registered", modelType.Name));
        }
    }
    

    这篇关于Autofac“动作注入"功能与ASP.NET MVC模型绑定的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆