Asp.net mvc 如何保持MVC视图、模型和模型绑定器尽可能干净?

Asp.net mvc 如何保持MVC视图、模型和模型绑定器尽可能干净?,asp.net-mvc,Asp.net Mvc,我是MVC新手,随着我对整个框架的了解越来越多,我发现ModelBinder越来越难以维护 让我解释一下 我正在写一个基本的CRUDover数据库应用程序。我的领域模型将会非常丰富。为了使我的控制器尽可能精简,我对其进行了设置,以便在执行创建/编辑命令时,该操作的参数是我的域模型的一个填充丰富的实例。为此,我实现了一个自定义模型绑定器 因此,该自定义模型绑定器非常特定于视图和模型。我决定只覆盖MVC2附带的DefaultModelBinder。如果绑定到我的模型的字段只是一个文本框(或者简单的东

我是MVC新手,随着我对整个框架的了解越来越多,我发现ModelBinder越来越难以维护

让我解释一下

我正在写一个基本的CRUDover数据库应用程序。我的领域模型将会非常丰富。为了使我的控制器尽可能精简,我对其进行了设置,以便在执行创建/编辑命令时,该操作的参数是我的域模型的一个填充丰富的实例。为此,我实现了一个自定义模型绑定器

因此,该自定义模型绑定器非常特定于视图和模型。我决定只覆盖MVC2附带的DefaultModelBinder。如果绑定到我的模型的字段只是一个文本框(或者简单的东西),我就委托给基本方法。但是,当我使用下拉列表或更复杂的东西时(UI规定日期和时间是单独的数据输入字段,但对于模型,它是一个属性),我必须执行一些检查和一些手动数据搜索

最终的结果是,我在视图和活页夹之间有一些非常紧密的联系。我在架构上对此很满意,但从代码维护的角度来看,这是一场噩梦

例如,我在这里绑定的模型是Log类型(这是我将在操作中作为参数获取的对象)。“ServiceStateTime”是日志上的属性。“log.ServiceStartDate”和“log.ServiceStartTime”的表单值完全是任意的,来自表单上的两个文本框(Html.TextBox(“log.ServiceStartTime”,…))

Log.ServiceEndTime是一个类似的字段

我觉得这个不太干。首先,如果我将ServiceStartTime或ServiceEndTime重构为不同的字段名,文本字符串可能会丢失(尽管我选择的重构工具R#在这方面做得非常好,但它不会导致构建时失败,只会被手动测试捕获)。其次,如果我决定任意更改描述符“log.ServiceStartDate”和“log.ServiceStartTime”,我将遇到同样的问题。对我来说,运行时无提示错误是最糟糕的错误

因此,我在这里看到了一些可以提供帮助的选项,我希望从遇到这些问题的人那里得到一些意见:

  • 将视图和模型绑定器之间的任何公共文本字符串重构为附加到我从控制器传递到aspx/ascx视图的ViewModel对象的常量字符串。但这会污染ViewModel对象
  • 围绕所有交互提供单元测试。我是单元测试的大力支持者,还没有开始充实这个选项,但我有一种直觉,那就是它不会把我从枪击中解救出来
如果重要的话,系统中的日志和其他实体将使用Fluent NHibernate持久化到数据库中。我真的想让我的控制器尽可能薄

所以,这里的任何建议都是非常欢迎的

谢谢

您可以使用ViewModel:

class ViewModelClass 
{
    [DateValidationAttribute]
    public property DateTime ServiceStartTimeDate { get; set; }
    [TimeValidationAttribute]
    public property DateTime ServiceStartTimeTime { get; set; }
}
DefaultModelBinder不需要任何修改就可以绑定这些字段。然后,您可以编写函数来组合这些字段:

class DateUtil
{
     public static DateTime CombineDateAndTime(DateTime Date, DateTime Time);
}
然后从数据库中获取实体:

var entity = context.GetUpdatedEntity(id);
entity.ServiceStartTime = DateUtil.CombineDateAndTime(viewModel.ServiceStartTimeDate,viewModel.ServiceStartTimeTime);
如果您真的想在model binder中使用它,可以这样做:

public class DateAndTimeModelBinder : DefaultModelBinder
{
    protected override object GetPropertyValue(ControllerContext controllerContext,
                                           ModelBindingContext bindingContext,
                                           PropertyDescriptor propertyDescriptor,
                                           IModelBinder propertyBinder)
    {
        if (propertyDescriptor.PropertyType == typeof(DateTime))
        {
            string time = bindingContext.ValueProvider.GetValue(CreateSubPropertyName(bindingContext.ModelName, propertyDescriptor.Name + "Time")).ConvertTo(typeof(string)) as string;
            var date = base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
            if ((date != null) && (time != null))
                return CombineDateAndTime(date, time);
        }
        return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
    }
}

绑定DateTime字段时,binder会查看是否有其他表单字段结尾带有“Time”(如果我们绑定“ServiceStartTime”,它会查找“ServiceStartTime”,因此您会将日期部分绑定到“ServiceStartTime”字段,将时间部分绑定到“ServiceStartTime”)。如果有,它将增加其迄今为止的价值。您只需编写一次,它将适用于每个字段。上面的代码肯定不起作用,它需要一些调整,但显示了想法。

我一直在考虑越来越多的ViewModel类。不过,我担心的是,在大多数情况下,ViewModel和Model几乎是1对1时,我会使体系结构复杂化@MBonig:因此,您可以只向模型类添加带有setter的属性。“ServiceStartTimeTime”setter将只设置时间部分,“ServiceStartTimeDate”将设置日期部分。您也可以使用此ModelBinder,这是更好的解决方案。
public class DateAndTimeModelBinder : DefaultModelBinder
{
    protected override object GetPropertyValue(ControllerContext controllerContext,
                                           ModelBindingContext bindingContext,
                                           PropertyDescriptor propertyDescriptor,
                                           IModelBinder propertyBinder)
    {
        if (propertyDescriptor.PropertyType == typeof(DateTime))
        {
            string time = bindingContext.ValueProvider.GetValue(CreateSubPropertyName(bindingContext.ModelName, propertyDescriptor.Name + "Time")).ConvertTo(typeof(string)) as string;
            var date = base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
            if ((date != null) && (time != null))
                return CombineDateAndTime(date, time);
        }
        return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
    }
}