我正在使用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: */ }
}
这是事情变得有点奇怪的地方。我发现自己必须创建一个派生自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();
}
答案 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));
}
}