为什么要叫getter?

时间:2018-01-31 05:46:46

标签: asp.net-mvc modelbinders

在我的模型中使用以下方法:

public bool ShowShipping() { return Modalities.Scheduled.Has(Item.Modality); }

但之前它是这样的一个属性:

public bool ShowShipping { get { return Modalities.Scheduled.Has(Item.Modality); } }

访问页面时,整个模型将填充包含Item属性的数据。项目包含需要在视图上显示的数据,但不包含需要回发的数据。所以在回发后(是的,post动作将模型作为参数),Item属性保留为null。

这应该不是问题,因为只有一行代码访问ShowShipping,它位于视图上。所以我希望永远不会访问它,除非填充了Item。但是在回发后,我得到一个错误,该错误发生在我的帖子操作的第一行被击中之前,它在ShowShipping中显示一个空引用错误。所以我必须假设错误正在发生,因为它将表单数据序列化为模型的新实例...但是为什么它会在序列化中调用此属性,因为整个解决方案中访问它的唯一位置是视图中的一行?

1 个答案:

答案 0 :(得分:4)

在System.Web.Mvc 5.2.3.0版中,DefaultModelBinder确实执行了验证,可能违反了关注点分离,并且没有办法通过任何设置或配置将其完全关闭。其他SO帖子提到在您的Global.asax.cs Application_Start()方法中使用以下代码关闭值类型的隐式required属性。

DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;

请参阅:https://stackoverflow.com/a/2224651(它引用了直接来自asp.net团队的答案的论坛)。

但是,这还不够。由于DefaultModelBinder.BindProperty(...)方法中的代码,所有模型类的getter仍将执行。从源代码...

https://github.com/mono/aspnetwebstack/blob/master/src/System.Web.Mvc/DefaultModelBinder.cs

215  // call into the property's model binder
216  IModelBinder propertyBinder = Binders.GetBinder(propertyDescriptor.PropertyType);
217  object originalPropertyValue = propertyDescriptor.GetValue(bindingContext.Model);
218  ModelMetadata propertyMetadata = bindingContext.PropertyMetadata[propertyDescriptor.Name];
219  propertyMetadata.Model = originalPropertyValue;
220  ModelBindingContext innerBindingContext = new ModelBindingContext()
221  {
222      ModelMetadata = propertyMetadata,
223      ModelName = fullPropertyKey,
224      ModelState = bindingContext.ModelState,
225      ValueProvider = bindingContext.ValueProvider
226  };
227  object newPropertyValue = GetPropertyValue(controllerContext, innerBindingContext, propertyDescriptor, propertyBinder);

第217行是违规者。显然,它会在从请求设置值之前调用getter(此方法的最终目的),以便它可以将第ModelBindingContext参数中的原始值传递给第227行的GetPropertyValue(...)方法。我找不到任何原因。

我在模型类中广泛使用了计算所得的属性,如果属性表达式依赖于先前未设置的数据,则肯定会引发异常,因为这可能表明代码中其他地方存在错误。 DefaultModelBinder行为破坏了设计。

为解决我的问题,我编写了一个自定义模型联编程序,该联编程序重写了BindProperty(...)方法并删除了对getter的调用。该代码只是原始源代码的副本,减去217和219行。由于未使用模型验证,因此我也删除了243至259行,并且该代码引用了派生类无法访问的私有方法(另一个DefaultModelBinder.BindProperty(...)方法的问题设计)。这是自定义模型活页夹。

public class NoGetterModelBinder : DefaultModelBinder {

   protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor) {

      string fullPropertyKey = CreateSubPropertyName(bindingContext.ModelName, propertyDescriptor.Name);
      if (!bindingContext.ValueProvider.ContainsPrefix(fullPropertyKey)) return;
      IModelBinder propertyBinder = Binders.GetBinder(propertyDescriptor.PropertyType);
      ModelMetadata propertyMetadata = bindingContext.PropertyMetadata[propertyDescriptor.Name];
      ModelBindingContext innerBindingContext = new ModelBindingContext() {

         ModelMetadata = propertyMetadata,
         ModelName = fullPropertyKey,
         ModelState = bindingContext.ModelState,
         ValueProvider = bindingContext.ValueProvider,

      };
      object newPropertyValue = GetPropertyValue(controllerContext, innerBindingContext, propertyDescriptor, propertyBinder);
      propertyMetadata.Model = newPropertyValue;
      ModelState modelState = bindingContext.ModelState[fullPropertyKey];
      if (modelState == null || modelState.Errors.Count == 0) {

         if (OnPropertyValidating(controllerContext, bindingContext, propertyDescriptor, newPropertyValue)) {

            SetProperty(controllerContext, bindingContext, propertyDescriptor, newPropertyValue);
            OnPropertyValidated(controllerContext, bindingContext, propertyDescriptor, newPropertyValue);

         }

      } else {

         SetProperty(controllerContext, bindingContext, propertyDescriptor, newPropertyValue);

      }

   }

}

您可以将该类放在您的Web项目中的任何位置,我只是将其放在Global.asax.cs中。然后,再次在Global.asax.cs的Application_Start()中,添加以下代码行,使其成为所有类的默认模型绑定程序...

ModelBinders.Binders.DefaultBinder = new NoGetterModelBinder();

这将防止在模型类上调用吸气剂。