Autofac"动作注射"与ASP.NET MVC模型绑定

时间:2015-02-01 06:52:09

标签: c# asp.net-mvc autofac

我正在使用Autofac和ASP.NET MVC,我想知道我在web项目中的视图模型设置是否是一个好方法。在过去,我只在Controller级别使用了构造函数注入,但认为看看是否所有内容都可以通过Autofac注入会很有趣。

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

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具有有效的表单数据,其余的视图模型属性也是空的。

这是Autofac和ASP.NET MVC代码的组合(为了可读性而取出了一些验证和注释):

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

我意识到我总是使用默认的模型绑定器,但如果我需要其他模型绑定器,则可以增强它。

它可能是Autofac中的错误(事实模型绑定不会发生,但DI工作)或者我滥用框架?

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

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

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:

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

1 个答案:

答案 0 :(得分:0)

回答你的问题(我认为这是你的问题):

  

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

我不认为它是Autofac中的一个错误,我相信模型解析在MVC将它的属性绑定到视图模型之前就已经发生了,所以你什么时候想要这些属性出现在视图模型中?

我有完全相同的问题:阅读this question

编辑:

这是您的autofac注册,其中 builder 是您的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

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