【问题标题】:Passing an interface to an ASP.NET MVC Controller Action method将接口传递给 ASP.NET MVC 控制器操作方法
【发布时间】:2011-04-03 15:22:03
【问题描述】:

在我的 ASP.NET MVC 应用程序中,我有一个界面,它充当多个不同视图模型的模板:

public interface IMyViewModel
{
    Client Client1 { get; set; }
    Client Client2 { get; set; }

    Validator Validate();
}

所以,我的视图模型是这样定义的:

public interface MyViewModel1 : IMyViewModel
{
    Client Client1 { get; set; }
    Client Client2 { get; set; }

    // Properties specific to MyViewModel1 here

    public Validator Validate()
    {
        // Do ViewModel-specific validation here
    }
}

public interface MyViewModel2 : IMyViewModel
{
    Client Client1 { get; set; }
    Client Client2 { get; set; }

    // Properties specific to MyViewModel2 here

    public Validator Validate()
    {
        // Do ViewModel-specific validation here
    }
}

然后我目前有一个单独的控制器操作来对每种不同的类型进行验证,使用模型绑定:

[HttpPost]
public ActionResult MyViewModel1Validator(MyViewModel1 model)
{
    var validator = model.Validate();

    var output = from Error e in validator.Errors
                 select new { Field = e.FieldName, Message = e.Message };

    return Json(output);
}

[HttpPost]
public ActionResult MyViewModel2Validator(MyViewModel2 model)
{
    var validator = model.Validate();

    var output = from Error e in validator.Errors
                 select new { Field = e.FieldName, Message = e.Message };

    return Json(output);
}

这很好用——但如果我有 30 种不同的视图模型类型,则必须有 30 个单独的控制器操作,除了方法签名之外,所有的代码都具有 相同 代码,这似乎是一种不好的做法。

我的问题是,如何整合这些验证操作,以便我可以传入任何类型的视图模型并调用它的 Validate() 方法,而无需关心它是哪种类型?

一开始我尝试使用界面本身作为动作参数:

public ActionResult MyViewModelValidator(IMyViewModel model)...

但这不起作用:我得到了一个Cannot create an instance of an interface 异常。我以为模型的一个实例会被传递到控制器动作中,但显然情况并非如此。

我确定我错过了一些简单的东西。或者,也许我只是错误地处理了这一切。谁能帮帮我?

【问题讨论】:

    标签: c# asp.net-mvc oop viewmodel model-binding


    【解决方案1】:

    你不能使用接口的原因是因为序列化。当请求进来时,它只包含代表对象的字符串键/值对:

    "Client1.Name" = "John"
    "Client2.Name" = "Susan"
    

    当调用动作方法时,MVC 运行时会尝试创建值来填充方法的参数(通过称为模型绑定的过程)。它使用参数的类型来推断如何创建它。正如您所注意到的,参数不能是接口或任何其他抽象类型,因为运行时无法创建它的实例。它需要一个具体的类型。

    如果你想删除重复的代码,你可以写一个助手:

    [HttpPost]         
    public ActionResult MyViewModel1Validator(MyViewModel1 model)         
    {         
        return ValidateHelper(model);         
    }         
    
    [HttpPost]         
    public ActionResult MyViewModel2Validator(MyViewModel2 model)         
    {         
        return ValidateHelper(model);         
    }
    
    private ActionResult ValidateHelper(IMyViewModel model) {
        var validator = model.Validate();         
    
        var output = from Error e in validator.Errors         
                     select new { Field = e.FieldName, Message = e.Message };         
    
        return Json(output);
    }
    

    但是,对于每种模型类型,您仍然需要不同的操作方法。也许还有其他方法可以重构代码。您的模型类中的唯一区别似乎是验证行为。您可以找到一种不同的方式来对模型类中的验证类型进行编码。

    【讨论】:

    • 我最终选择了这种方法纯粹是由于时间限制,但您也解释了为什么对象在传递到控制器时还不是实例,这对了解很有用。跨度>
    【解决方案2】:

    你可以检查这个:http://msdn.microsoft.com/en-us/magazine/hh781022.aspx

    这是因为 DefaultModelBinder 无法知道 IMyViewModel 应该创建什么具体类型。 对于解决方案,您创建自定义模型绑定器并指示如何创建和绑定接口实例。

    【讨论】:

    • +1 恕我直言,这是要走的路,胜过所有其他答案(其中一些显然尚未在现实​​世界中经过测试)。
    【解决方案3】:

    我想我会创建一个实现 IMyViewModel 的抽象基类。我将使 Validate 成为一个抽象方法,并要求在从 MyAbstractViewModel 继承的具体视图模型中进行覆盖。在您的控制器内部,您可以根据需要使用 IMyViewModel 接口,但绑定和序列化确实需要一个具体的类来绑定。我的 $.02。

    【讨论】:

    • 我应该添加一条额外的注释 - 我实际上不会将抽象类传递给控制器​​,而是将具体类传递给控制器​​。您可能有一个验证服务或其他可以处理抽象类或接口的方法,但为了清楚起见,您应该真正期待您在控制器中得到什么。
    【解决方案4】:

    您可以考虑使用基类而不是接口。

    【讨论】:

    • 这会导致类似的错误消息。它也不能创建抽象类型的实例。
    • 你可以使用非抽象基类。
    猜你喜欢
    • 2011-07-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-14
    相关资源
    最近更新 更多