【问题标题】:Controller Action with Derived Classes带有派生类的控制器操作
【发布时间】:2015-03-09 18:19:55
【问题描述】:

我有一个基类和两个派生类:

public class UserModel {
    public int Id {get; set; }
    public string Name {get; set; }
    public UserType UserType {get; set;}
}

public class StudentModel : UserModel {
    public string StudentProperty {get; set;}
}

public class TeacherModel : UserModel {
    public string TeacherProperty {get; set;}
}

在我的控制器 ProfileController.cs 中,我有以下两个操作:

public virtual ActionResult Detail(int id)
{
    var userModel = _userService.Get(id);

    return view(usermodel);
}

public virtual ActionResult Save(UserModel userModel)
{
    _userService.Save(userModel);
}

我有一个视图来显示学生和教师的个人资料。我的问题如下:

保存时,使用动作Save(UserModel userMode),StudentModel和TeacherModel的附加属性(分别是StudentProperty和TeacherProperty)显然没有绑定到UserModel。所以我的问题是:

设置 Controller Action 以便我可以传递派生类(StudentModel 或 TeacherModel)的正确方法是什么?

仅供参考,我尝试了自定义活页夹(见下文),但是,我不知道这是否是处理此问题的好方法。

public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
    var form = controllerContext.HttpContext.Request.Form;

    switch (form["UserType"])
    {
        case "student":
            {
                var studentModel = bindingContext.Model as StudentModel;                        
                return studentModel;
            }
        case "Teacher":
            {
                var teacherModel = bindingContext.Model as TeacherModel;
                medico.TeacherProperty = form["TeacherProperty"];
                return teacherModel;
            }
    }

    return bindingContext.Model;
}

【问题讨论】:

  • FYI This article 有一些关于创建自定义抽象模型绑定器的详细信息。
  • @StephenMuecke - 这是一篇好文章,但是我不愿意允许传入的请求选择它想要实例化的任何类型,而无需进行某种输入清理。如果 AbstractBinder 可以指定允许创建哪些特定类型会更好,甚至更好的是,允许它与 action 方法上的属性交互以定义这些。
  • @StephenMuecke - 更重要的是,您可能还希望包含某种安全机制,不允许某些用户或角色实例化某些类型,即使它们可能在该控制器上被允许。所以这个概念虽然很有趣,但需要更多的工作。

标签: c# asp.net-mvc polymorphism derived-class


【解决方案1】:

多态模型绑定的最大问题之一是安全性。毕竟,您实际上是在允许客户端控制数据的解释方式。您必须小心,例如,用户不能修改帖子并告诉服务器您的 UserModel 实际上是 AdministratorModel,而您现在是管理员。

在您的用例中,我不知道您的应用程序是做什么的。但假设它是某种记录保存应用程序,想象一下学生只需更改提交给服务器的类型就可以让自己成为老师,现在他们可以更改自己或其他学生的成绩。

但是,如果这不是一个真正的问题,那么一个相当简单的机制就是这样做:

public virtual ActionResult Save(UserModel userModel)
{
    TeacherModel tmodel = null;
    StudentModel smodel = null;
    if (userModel.UserType == UserType.Teacher) {
        tmodel = new TeacherModel();
        UpdateModel<TeacherModel>(tmodel);
    }
    else {
        smodel = new StudentModel();
        UpdateModel<StudentModel>(smodel);
    }

    _userService.Save((UserModel)tmodel ?? smodel);
}

您在问题中提出的自定义模型绑定器方法也很好,如果在多种方法中使用它是一个更好的选择,但对于维护应用程序的人来说不是那么明显。

事实上,在这种情况下,假设您的模型类型基于某种安全机制,更好的解决方案是根据用户的角色实例化正确的模型。所以,当你的请求进来时,你检查用户的权限,如果他们是教师角色,你实例化一个 TeacherModel 对象,如果他们是一个学生,你实例化一个 StudentModel 对象,这样最终用户就什么都没有了可以改变它的工作方式。

即,像这样:

public virtual ActionResult Save(UserModel userModel)
{
    TeacherModel tmodel = null;
    StudentModel smodel = null;
    // lookup user in database and verify the type of user they are
    var user = UserManager.GetUser(userModel.UserId)
    if (user.Role == "Teacher")
        tmodel = new TeacherModel();
        UpdateModel<TeacherModel>(tmodel);
    }
    else {
        smodel = new StudentModel();
        UpdateModel<StudentModel>(smodel);
    }

    _userService.Save((UserModel)tmodel ?? smodel);
}

【讨论】:

  • 感谢您的回复。我认为根据我的需要,添加自定义活页夹确实有点矫枉过正,所以我一直在尝试按照您的建议直接在操作中执行此操作。我遇到的一个问题是对UpdateModel&lt;TeacherModel&gt;(userModel);UpdateModel&lt;StudentModel&gt;(userModel); 的调用给我一个编译错误“参数类型'UserModel' 不可分配给参数类型'TeacherModel'”我认为这是因为TeacherModel 派生自UserModel 而不是其他方式。你知道我该如何解决这个问题吗?
  • @GabrielLopez - 是的,我应该仔细检查一下。查看我的更新
  • 非常感谢!这正是我所需要的。
  • @GabrielLopez - 请注意,如果验证失败,UpdateModel 会抛出异常,这是您可能不想要的。所以要么捕获异常(不理想),要么使用 TryUpdateModel(首选方法,我没有为简单起见)。
【解决方案2】:

我以前做过这种确切的行为。我所做的是视图会根据类型动态更改表单操作。绑定的每个类型化模型都有一个用于该类型的独立方法。

所以我的模型类似于:

public abstract class Property {...}

public class Industrial : Property {...}

public class Commercial : Property {...}

public abstract class Residence : Property {... }

public class Condo : Residence {...}

public class Residential : Residence {...}

查看:

@model Property

@using(Html.BeginForm(Model.GetType().Name, "Property", ...))
{
}

控制器:

public class PropertyController : Controller
{
  [HttpPost]
  public ActionResult Industrial(Industrial model)
  {
    ...
  }

  [HttpPost]
  public ActionResult Commercial(Commercial model)
  {
    ...
  }

  // etc
}

我担心使用自定义模型绑定器创建单个方法是因为我会开始基于类型执行特定于类的功能,这会创建一个负责多种类型的非常大的方法(这会破坏Separation of Concerns)。

【讨论】:

  • 谢谢埃里克。考虑到我没有那么多派生类,我认为这是一个可行的解决方案。我不太担心打破关注点分离,因为我的控制器只填充模型,而我的服务层实际上调用不同的提供者来管理每个派生类的逻辑。如果我不能用一个动作让它工作,我会走这条路。
猜你喜欢
  • 2014-10-31
  • 2015-12-09
  • 1970-01-01
  • 1970-01-01
  • 2011-10-09
  • 2020-10-14
  • 2013-06-29
  • 2013-03-23
  • 1970-01-01
相关资源
最近更新 更多