将接口传递给 ASP.NET MVC 控制器操作方法

发布于 2024-09-15 13:52:42 字数 1776 浏览 4 评论 0原文

在我的 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)...

但这不起作用:我收到 无法创建接口实例 异常。我认为模型的实例将被传递到控制器操作中,但显然情况并非如此。

我确信我错过了一些简单的事情。或者也许我刚刚处理这一切都是错误的。有人可以帮我吗?

In my ASP.NET MVC app, I have an interface which acts as the template for several different view models:

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

    Validator Validate();
}

So, my view models are defined like this:

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
    }
}

Then I currently have a separate controller action to do the validation for each different type, using model binding:

[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);
}

This works fine—but if I had 30 different view model types then there would have to be 30 separate controller actions, all with identical code apart from the method signature, which seems like bad practice.

My question is, how can I consolidate these validation actions so that I can pass any kind of view model in and call it's Validate() method, without caring about which type it is?

At first I tried using the interface itself as the action parameter:

public ActionResult MyViewModelValidator(IMyViewModel model)...

But this didn't work: I get a Cannot create an instance of an interface exception. I thought an instance of the model would be passed into the controller action, but apparently this is not the case.

I'm sure I'm missing something simple. Or perhaps I've just approached this all wrong. Can anyone help me out?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

猫弦 2024-09-22 13:52:42

无法使用该接口的原因是因为序列化。当请求传入时,它仅包含表示对象的字符串键/值对:

"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);
}

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

The reason why you cannot use the interface is because of serialization. When a request comes in it only contains string key/value pairs that represent the object:

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

When the action method gets invoked the MVC runtime tries to create values to populate the method's parameters (via a process called model binding). It uses the type of the parameter to infer how to create it. As you've noticed, the parameter cannot be an interface or any other abstract type because the runtime cannot create an instance of it. It needs a concrete type.

If you want to remove repeated code you could write a helper:

[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);
}

However, you will still need a different action method for each model type. Perhaps there are other ways you could refactor your code. It seems the only difference in your model classes is the validataion behavior. You could find a different way to encode the validation type in your model class.

诺曦 2024-09-22 13:52:42

您可以检查以下内容: http://msdn.microsoft.com/en-us/杂志/hh781022.aspx

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

You could check this: http://msdn.microsoft.com/en-us/magazine/hh781022.aspx.

This is caused because DefaultModelBinder has no way of knowing what concrete type of IMyViewModel should create.
For solution that, you create custom model binder and indicate how to create and bind an instance of interface.

黑色毁心梦 2024-09-22 13:52:42

我想我会创建一个实现 IMyViewModel 的抽象基类。我将使 Validate 成为一个抽象方法,并需要在继承自 MyAbstractViewModel 的具体视图模型中进行重写。在控制器内部,如果需要,您可以使用 IMyViewModel 接口,但绑定和序列化确实需要一个具体的类来绑定。我的 0.02 美元。

I think I would create an abstract base class that implemented IMyViewModel. I would make Validate an abstract method and require overriding in my concrete view models that inherited from MyAbstractViewModel. Inside your controller, you can work with the IMyViewModel interface if you want, but binding and serialization really needs a concrete class to bind. My $.02.

许久 2024-09-22 13:52:42

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

You could consider using a base class instead of the interface.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文