停止 ASP.NET MVC 调用模型中的所有 getter class

Stop ASP.NET MVC from calling all getters in a model class

如果您在模型 class 上定义以下两个属性,这将在模型绑定期间崩溃并出现 NullReferenceException

        public Customer Customer { get; private set; } //set in the action method
        public bool Name => Customer.Name;

这是因为 Customer 在模型绑定期间仍然为空,并且 ASP.NET MVC 为 Name 调用 getter。

堆栈是:

   System.ComponentModel.ReflectPropertyDescriptor.GetValue(Object component) +525
   System.Web.Mvc.ModelMetadata.get_Model() +34
   System.Web.Mvc.DataAnnotationsModelValidator.Validate(Object container) +151
   System.Web.Mvc.<Validate>d__1.MoveNext() +387
   System.Web.Mvc.DefaultModelBinder.OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext) +163
   System.Web.Mvc.DefaultModelBinder.BindComplexElementalModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Object model) +83
   System.Web.Mvc.DefaultModelBinder.BindComplexModel(ControllerContext controllerContext, ModelBindingContext bindingContext) +1754
   System.Web.Mvc.ControllerActionInvoker.GetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor) +460
   System.Web.Mvc.ControllerActionInvoker.GetParameterValues(ControllerContext controllerContext, ActionDescriptor actionDescriptor) +137
   System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext controllerContext, String actionName) +982
   System.Web.Mvc.<>c__DisplayClass22.<BeginExecuteCore>b__1e() +39
   System.Web.Mvc.Async.AsyncResultWrapper.<.cctor>b__0(IAsyncResult asyncResult, Action action) +21
   System.Web.Mvc.Controller.EndExecuteCore(IAsyncResult asyncResult) +53
   System.Web.Mvc.Async.WrappedAsyncVoid`1.CallEndDelegate(IAsyncResult asyncResult) +36
   System.Web.Mvc.Controller.EndExecute(IAsyncResult asyncResult) +38
   System.Web.Mvc.MvcHandler.<BeginProcessRequest>b__5(IAsyncResult asyncResult, ProcessRequestState innerState) +44
   System.Web.Mvc.Async.WrappedAsyncVoid`1.CallEndDelegate(IAsyncResult asyncResult) +65
   System.Web.Mvc.MvcHandler.EndProcessRequest(IAsyncResult asyncResult) +38
   System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +399
   System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +137

从堆栈来看,模型验证似乎正在查询 all getters。我没有使用模型验证。

我该如何处理这种情况?我可以让 ASP.NET MVC 在没有任何(明显的)原因的情况下不调用所有 getter 吗?

因此,Model Binder 新建了您的模型的一个实例,然后可能正在对模型的属性进行反射,以查找与 FormCollection 中的值匹配的命名。发生的事情是,当调用危险的 Name 道具时, Customer 道具为空,因此为 NullRef。

.NET 检查这些属性的顺序实际上可能不是随机的,但您的代码将通过这样处理而得到很大改进。直接在默认情况下可为空的 Class 上调用 method/prop 是一个糟糕的主意,除非您检查它是否为空。您在这里有两个选择,或者 (1) 重新设计您的模型 class 以便构造函数初始化 "Customer" 属性,或者 (b) 在 "Name" 中添加空检查] 方法。

这是最简单的方法,可以在抓取它时对其进行 null 检查:

public bool Name => Customer?.Name ?? false;

这并没有解决根本问题,即您的模型将可空属性链接在一起。不要担心模型的构造函数会弄乱模型绑定。 Model Binder 将 (1) 初始化您的模型 class,然后 (2) 尝试对其进行水合。因此,在模型的构造函数中初始化 Customer class/prop 不会影响 UI 字段的任何映射以表示 Customer 的字段。

MVC version 5.2.3中的DefaultModelBinder除了binding之外还有validation,没有办法完全关闭。其他 SO 帖子提到在 Global.asax.cs、Application_Start() 方法中使用以下代码行关闭值类型的隐式必需属性...

DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;

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

鉴于您的堆栈跟踪,这可能会解决您眼前的问题。但是,这可能还不够,因为 DefaultModelBinder 甚至在其验证代码之外调用 getter 时没有说明原因(源代码没有说明为什么这样做)。

为了解决我的项目中的问题,我一直使用类似于您的示例的计算属性,我实现了一个基于原始 DefaultModelBinder 源代码的自定义模型联编程序,该联编程序不调用任何 getter。

在此处查看更详细的解释和我的完整解决方案: