问题
I have one base class and two derived classes:
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;}
}
In my controller ProfileController.cs I have the following two actions:
public virtual ActionResult Detail(int id)
{
var userModel = _userService.Get(id);
return view(usermodel);
}
public virtual ActionResult Save(UserModel userModel)
{
_userService.Save(userModel);
}
I have one view to show the profile of both Students and Teacher. My problem is the following:
when saving, using the action Save(UserModel userMode), the additional properties of StudentModel and TeacherModel (StudentProperty and TeacherProperty respectively) are obviously not bound to UserModel. So my questio is:
What is the right way to set up the Controller Action so that I can pass a derived class, either a StudentModel or TeacherModel?
FYI, I have tried a custom binder (see below), however, I don't know if this is a good way to handle this.
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;
}
回答1:
The biggest problem with polymorphic model binding is one of security. After all, you are effectively allowing the client to control how the data gets interpreted. You have to be careful that, for example, a user can't modify the post and tell the server that your UserModel is actually an AdministratorModel and that you are now an administrator.
In your use case, I don't know what your app does.. but assuming it's some kind of record keeping application, imagine that a student can make themselves a teacher just by altering the type submitted to the server, and now they can change their own grades, or those of other students.
If, however, this is not really an issue, then a fairly simple mechanism is to simply do this:
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);
}
The custom model binder approach you proposed in your question is also fine, and a better choice if this is used in multiple methods, but is not quite so obvious to someone maintaining the application.
In fact, a better solution in a case like this, assuming that your model type is based on some kind of security mechanism, is to instantiate the correct model based on the role of the user. So, when your request comes in, you check the users permissions, and if they are in the Teacher role, you instantiate a TeacherModel object and if they're a student you instantiate a StudentModel object, and that way there is nothing the end user can do to change how that works.
ie, something like this:
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);
}
回答2:
I've done this exact behavior before. What I did was the view would dynamically change the form action based on the type. Each typed model bound back had an independent method for that type.
So I had models something similar to:
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 {...}
View:
@model Property
@using(Html.BeginForm(Model.GetType().Name, "Property", ...))
{
}
Controller:
public class PropertyController : Controller
{
[HttpPost]
public ActionResult Industrial(Industrial model)
{
...
}
[HttpPost]
public ActionResult Commercial(Commercial model)
{
...
}
// etc
}
My fear over creating a single method with a custom model binder was that I would start to do class specific functionality based on type, which creates a very big method that is responsible for multiple types (which breaks Separation of Concerns).
来源:https://stackoverflow.com/questions/27894272/controller-action-with-derived-classes