为什么 getter 被调用?

Why is getter being called?

我的模型中有以下方法:

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

但以前是这样的属性:

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

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

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

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

DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;

参见:(它引用了直接来自 asp.net 团队的答案的论坛)。

然而,这还不够。由于 DefaultModelBinder.BindProperty(...) 方法中的代码,所有模型 class 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 参数中的原始值传递给 GetPropertyValue(...) 方法在第 227 行。我找不到任何原因。

我在我的模型 classes 中广泛使用计算属性,如果 属性 表达式依赖于以前未设置的数据,肯定会抛出异常,因为这表明代码中其他地方存在错误. DefaultModelBinder 行为破坏了设计。

为了解决我的问题,我编写了一个自定义模型绑定器,它覆盖了 BindProperty(...) 方法并删除了对 getter 的调用。此代码只是原始源代码的副本,减去了第 217 和 219 行。我还删除了第 243 行到第 259 行,因为我没有使用模型验证,并且该代码引用了派生的 class 不引用的私有方法可以访问(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);

      }

   }

}

你可以把那个 class 放在你的 web 项目的任何地方,我只是把它放在 Global.asax.cs 里。然后,再次在 Global.asax.cs 中,在 Application_Start() 中添加以下代码行,使其成为所有 classes...

的默认模型绑定器
ModelBinders.Binders.DefaultBinder = new NoGetterModelBinder();

这将阻止 getters 在您的模型 classes 上被调用。