Asp.net mvc 在ASP.NET MVC ViewModel类中获取数据?
对于那些在ASP.NET MVC中创建ViewModels(供类型化视图使用)的用户,您更喜欢从ViewModel中的服务/存储库中获取数据,还是从控制器类中获取数据 例如,我们首先让ViewModels本质上是DTO,并允许我们的控制器获取数据(非常简单的示例假设用户只能更改员工姓名):Asp.net mvc 在ASP.NET MVC ViewModel类中获取数据?,asp.net-mvc,Asp.net Mvc,对于那些在ASP.NET MVC中创建ViewModels(供类型化视图使用)的用户,您更喜欢从ViewModel中的服务/存储库中获取数据,还是从控制器类中获取数据 例如,我们首先让ViewModels本质上是DTO,并允许我们的控制器获取数据(非常简单的示例假设用户只能更改员工姓名): 公共类EmployeeViewModel { 公共字符串名称;//回发 public int Num;//发回 public IEnumerable Dependents;//静态 公共IEnumerable
公共类EmployeeViewModel
{
公共字符串名称;//回发
public int Num;//发回
public IEnumerable Dependents;//静态
公共IEnumerable;//静态
}
公共类EmployeeController()
{
...
公共行动结果员工(int empNum)
{
Models.EmployeeViewModel=新模型.EmployeeViewModel();
model.Name=\u empSvc.FetchEmployee(empNum).Name;
model.Num=empNum;
model.Dependents=\u peopleSvc.FetchDependentsForView(empNum);
model.partners=\u peopleSvc.FetchDependentsForView(empNum);
返回视图(模型);
}
[接受动词(HttpVerbs.Post)]
公共行动结果员工(Models.EmployeeViewModel)
{
if(!\u empSvc.ValidateAndSaveName(model.Num,model.Name))
{
model.Dependents=\u peopleSvc.FetchDependentsForView(model.Num);
model.partners=\u peopleSvc.FetchDependentsForView(model.Num);
返回视图(模型);
}
this.RedirectToAction(c=>c.Index());
}
}
这一切似乎都很好,直到我们开始创建带有许多下拉列表等的大视图(40多个字段)。由于屏幕将有一个GET和POST操作(如果存在验证错误,POST将返回一个视图),我们将复制代码并使ViewModels比它们可能应该的更大
我认为另一种方法是通过ViewModel中的服务获取数据。我担心的是,我们会从ViewModel中填充一些数据,从Controller中填充一些数据(例如,在上面的示例中,名称将从Controller中填充,因为它是一个已发布的值,而家属和配偶将通过ViewModel中的某种类型的GetStaticData()函数填充)
想法?我遇到了同样的问题。当代码对于动作方法来说太大时,我开始为每个动作创建类。是的,您将在类和控制器方法中进行一些数据检索。另一种方法是将所有数据检索都放在类中,但有一半的类不是您真正需要的,它们是为了一致性而创建的,或者所有数据检索都放在控制器方法中,但是,这些方法中的一些太复杂,需要抽象为类。。。所以选择你的毒药。我宁愿有一点前后矛盾,并为这份工作找到正确的解决方案 至于将行为放入ViewModel,我不这么认为,ViewModel的重点是成为一个用于设置和从视图中提取值的瘦类 在某些情况下,我将转换方法放在ViewModel中。例如,我需要将ViewModel转换为相应的实体,或者需要使用实体中的数据加载ViewModel 为了回答您的问题,我更喜欢从控制器/操作方法中的中检索数据 通常使用下拉菜单,我创建一个下拉菜单服务。下拉列表往往是跨视图的相同数据。使用服务中的下拉列表,我可以在其他视图上使用它们和/或缓存它们 根据布局的不同,40多个字段可能会创建一个混乱的视图。根据数据的类型,我将尝试使用某种选项卡或向导界面跨越多个视图中的许多字段 还有更多;-)您可以获取模型绑定器或操作筛选器。对于第二种选择,请查看Jimmy Bogard的博客。我个人是用模型装订的。我使用的ViewModel如下所示:。它由我的自定义模型活页夹处理:
public object BindModel(ControllerContext c, BindingContext b)
{
var id = b.ValueProvider[b.ModelName]; // don't remember exact syntax
var repository = ServiceLocator.GetInstance(GetRepositoryType(b.ModelType));
var obj = repository.Get(id);
if (obj == null)
b.ModelState.AddModelError(b.ModelName, "Not found in database");
return obj;
}
public ActionResult Action(EntityViewModel<Order> order)
{
if (!ModelState.IsValid)
...;
}
公共对象绑定模型(ControllerContext c,BindingContext b)
{
var id=b.ValueProvider[b.ModelName];//不记得确切的语法
var repository=ServiceLocator.GetInstance(GetRepositoryType(b.ModelType));
var obj=repository.Get(id);
if(obj==null)
b、 AddModelError(b.ModelName,“未在数据库中找到”);
返回obj;
}
公共操作结果操作(EntityViewModel订单)
{
如果(!ModelState.IsValid)
...;
}
您还可以在中看到模型绑定器访问存储库的示例
至于视图模型中的静态数据,我仍在探索方法。例如,可以让视图模型记住实体而不是列表,以及
公共类MyViewModel
{
公共MyViewModel(订单、IEmployeesSvc\u svc)
{
}
public IList<Employee> GetEmployeesList()
{
return _svc.GetEmployeesFor(order.Number);
}
public IList GetEmployeesList()
{
返回_svc.GetEmployeesFor(订单号);
}
}
您可以决定如何将_svc注入ViewModel,但它基本上与控制器相同。请注意,ViewModel也是由MVC通过无参数构造函数创建的,因此您可以使用ServiceLocator或扩展MVC来创建ViewModel—例如,在自定义模型绑定器中。或者,您可以使用Jimmy Bogard的方法,使用AutoMapper,它也支持IoC容器
这里常见的方法是,每当我看到重复代码时,我都会设法消除它。执行域视图模型编组和存储库查找的100个控制器操作是一个糟糕的情况。单一型号活页夹采用通用方式是一种很好的解决方案。这里有另一种解决方案:
要点如下:
public IList<Employee> GetEmployeesList()
{
return _svc.GetEmployeesFor(order.Number);
}
public class WholeViewModel
{
public Part1ViewModel ModelPart1 { get; set; }
public Part2ViewModel ModelPart2 { get; set; }
}
container.Resolve(typeof(IViewModelMapper<>).MakeGenericType(mysourcetype, mydesttype))
public ActionResult Details(MyTypeIndexViewModel model)
{
if( ModelState.IsValid )
{
return View(model);
}
else
{
// Handle the case where the ModelState is invalid
// usually because they've requested MyType/Details/x
// and there's no matching MyType in the repository
// e.g. return RedirectToAction("Index")
}
}
public object BindModel
(
ControllerContext controllerContext,
BindingContext bindingContext
)
{
// Get the Primary Key from the requestValueProvider.
// e.g. bindingContext.ValueProvider["id"]
int id = ...;
// Get an instance of your service layer via your
// favourite dependancy injection framework.
// Or grab the controller's copy e.g.
// (controllerContext.Controller as MyController).Service
IMyTypeService service = ...;
MyType myType = service.GetMyTypeById(id)
if (myType == null)
{
// handle the case where the PK has no matching MyType in the repository
// e.g. bindingContext.ModelState.AddModelError(...)
}
MyTypeIndexViewModel model = new MyTypeIndexViewModel(myType);
// If you've got more repository calls to make
// (e.g. populating extra fields on the model)
// you can do that here.
return model;
}
public class MyTypeIndexViewModel
{
public MyTypeIndexViewModel(MyType source)
{
// Bind all the properties of the ViewModel in here, or better
// inherit from e.g. MyTypeViewModel, bind all the properties
// shared between views in there and chain up base(source)
}
}
// place this somewhere in your globals, or base controller constructor
Mapper.CreateMap<Employee, EmployeeViewModel>();
public class EmployeeController()
{
private IEmployeeService _empSvc;
private ISpouseService _peopleSvc;
public EmployeeController(
IEmployeeService empSvc, ISpouseService peopleSvc)
{
// D.I. hard at work! Auto-wiring up our services. :)
_empSvc = empSvc;
_peopleSvc = peopleSvc;
// setup all ViewModels here that the controller would use
Mapper.CreateMap<Employee, EmployeeViewModel>();
Mapper.CreateMap<Spouse, SpouseViewModel>();
}
public ActionResult Employee(int empNum)
{
// really should have some validation here that reaches into the domain
//
var employeeViewModel =
Mapper.Map<Employee, EmployeeViewModel>(
_empSvc.FetchEmployee(empNum)
);
var spouseViewModel =
Mapper.Map<Spouses, SpousesViewModel>(
_peopleSvc.FetchSpouseByEmployeeID(empNum)
);
employeeViewModel.SpouseViewModel = spouseViewModel;
return View(employeeViewModel);
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Employee(int id, FormCollection values)
{
try
{
// always post to an ID, which is the employeeID
var employee = _empSvc.FetchEmployee(id);
// and bind using the built-in UpdateModel helpers.
// this will throw an exception if someone is posting something
// they shouldn't be posting. :)
UpdateModel(employee);
// save employee here
this.RedirectToAction(c => c.Index());
}
catch
{
// check your domain model for any errors.
// check for any other type of exception.
// fail back to the employee screen
RedirectToAction(c => c.Employee(id));
}
}
}