"DRY up" 使用 SelectListItem 时的 MVC 模型
"DRY up" MVC models when using SelectListItem
我刚刚开始使用 MVC,我能够在网上找到大多数 DRY 原则的示例。虽然我没有全部使用,因为我发现有些代码更难阅读。
我找到了一个我找不到示例的方法,但我觉得一定有办法做到这一点。
基本上现在我在控制器中填充模型选择列表类型对象,这意味着我必须重用代码。我知道我可以把它放在一个方法中,但我想知道是否无论如何都要把它放在模型中,以便在使用 model/viewmodel 时调用此操作来填充选择列表内容。
我在下面放了一个我的代码示例。
型号
using System;
using System.Data.Entity;
namespace MyWebsite.Models
{
// You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
namespace CustomerWebsites.Models
{
public class CustomerWebsites
{
public int Id { get; set; }
public Guid UserGuid { get; set; }
public string WebsiteAddress { get; set; }
public string WebsiteType { get; set; }
public DateTime ReleaseDate { get; set; }
public string Description { get; set; }
public decimal Budget { get; set; }
public DateTime CreationDate { get; set; }
public string DevelopmentStatus { get; set; }
public int CompletedPercentage { get; set; }
public bool Completed { get; set; }
public decimal TotalCost { get; set; }
public decimal TotalPaid { get; set; }
}
public class CustomerWebsitesDBContext : DbContext
{
public CustomerWebsitesDBContext()
: base("DefaultConnection")
{
}
public static CustomerWebsitesDBContext Create()
{
return new CustomerWebsitesDBContext();
}
public DbSet<CustomerWebsites> CustomerWebsites { get; set; }
}
}
}
ViewModel
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace MyWebsite.ViewModels
{
public class CreateCustomerWebsites
{
public int Id { get; set; }
public Guid UserGuid { get; set; }
[Required]
public string WebsiteAddress { get; set; }
public string WebsiteType { get; set; }
public DateTime ReleaseDate { get; set; }
public string Description { get; set; }
public decimal Budget { get; set; }
public DateTime CreationDate { get; set; }
public string DevelopmentStatus { get; set; }
public int CompletedPercentage { get; set; }
public bool Completed { get; set; }
public decimal TotalCost { get; set; }
public decimal TotalPaid { get; set; }
public IEnumerable<SelectListItem> AllUsers { get; set; }
}
}
控制器
// GET: CustomerWebsites/Create
public ActionResult Create()
{
var db = new ApplicationDbContext();
var users = db.Users.ToArray();
var allUsers = users.Select(x => new SelectListItem
{
Value = x.Id,
Text = x.Email
});
var model = new CreateCustomerWebsites
{
AllUsers = allUsers
};
return View(model);
}
// POST: CustomerWebsites/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(CreateCustomerWebsites model)
{
if (ModelState.IsValid)
{
var userGuid = new Guid(User.Identity.GetUserId());
var developmentStatus = "Pending MyWebsite Review";
if (User.IsInRole("Administrator"))
{
userGuid = model.UserGuid;
developmentStatus = model.DevelopmentStatus;
}
db.CustomerWebsites.Add(new CustomerWebsites
{
UserGuid = userGuid,
WebsiteAddress = model.WebsiteAddress,
CreationDate = DateTime.Now,
ReleaseDate = model.ReleaseDate,
Budget = model.Budget ,
Description = model.Description,
DevelopmentStatus = developmentStatus,
CompletedPercentage = model.CompletedPercentage,
Completed = model.Completed,
TotalCost = model.TotalCost,
TotalPaid = model.TotalPaid
});
db.SaveChanges();
return RedirectToAction("Index");
}
var dbUsers = new ApplicationDbContext();
var users = dbUsers.Users.ToArray();
var allUsers = users.Select(x => new SelectListItem
{
Value = x.Id,
Text = x.Email
});
model = new CreateCustomerWebsites
{
AllUsers = allUsers
};
return View(model);
}
查看
<h2>Create</h2>
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>CustomerWebsites</h4>
<hr />
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
@if (User.IsInRole("Administrator"))
{
<div class="form-group">
@Html.LabelFor(model => model.UserGuid, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.DropDownListFor(model => model.UserGuid, Model.AllUsers, "-- Select a user --")
@Html.ValidationMessageFor(model => model.UserGuid, "", new { @class = "text-danger" })
</div>
</div>
}
<div class="form-group">
@Html.LabelFor(model => model.WebsiteAddress, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.WebsiteAddress, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.WebsiteAddress, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.WebsiteType, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.WebsiteType, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.WebsiteType, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.ReleaseDate, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.ReleaseDate, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.ReleaseDate, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.Description, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.TextAreaFor(model => model.Description, new { rows = "10", @class = "form-control" })
@Html.ValidationMessageFor(model => model.Description, "", new { @class = "text-danger" })
</div>
</div>
@if (User.IsInRole("Administrator"))
{
<div class="form-group">
@Html.LabelFor(model => model.DevelopmentStatus, htmlAttributes: new {@class = "control-label col-md-2"})
<div class="col-md-10">
@Html.EditorFor(model => model.DevelopmentStatus, new {htmlAttributes = new {@class = "form-control"}})
@Html.ValidationMessageFor(model => model.DevelopmentStatus, "", new {@class = "text-danger"})
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.CompletedPercentage, htmlAttributes: new {@class = "control-label col-md-2"})
<div class="col-md-10">
@Html.EditorFor(model => model.CompletedPercentage, new {htmlAttributes = new {@class = "form-control"}})
@Html.ValidationMessageFor(model => model.CompletedPercentage, "", new {@class = "text-danger"})
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.Completed, htmlAttributes: new {@class = "control-label col-md-2"})
<div class="col-md-10">
<div class="checkbox">
@Html.EditorFor(model => model.Completed)
@Html.ValidationMessageFor(model => model.Completed, "", new {@class = "text-danger"})
</div>
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.TotalCost, htmlAttributes: new {@class = "control-label col-md-2"})
<div class="col-md-10">
@Html.EditorFor(model => model.TotalCost, new {htmlAttributes = new {@class = "form-control"}})
@Html.ValidationMessageFor(model => model.TotalCost, "", new {@class = "text-danger"})
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.TotalPaid, htmlAttributes: new {@class = "control-label col-md-2"})
<div class="col-md-10">
@Html.EditorFor(model => model.TotalPaid, new {htmlAttributes = new {@class = "form-control"}})
@Html.ValidationMessageFor(model => model.TotalPaid, "", new {@class = "text-danger"})
</div>
</div>
}
<div class="form-group">
@Html.LabelFor(model => model.Budget, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Budget, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.Budget, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
Basically at the moment I populate model selectlist type objects in
the controller, this means I have to reuse the code. I know I could
just put this in a method but I was wondering if there anyway to put
it in the model so that anytime the model/viewmodel is used this
action is called to populate the the selectlist contents.
在您的视图模型中放置任何方法通常不是一个好主意,尤其是对于数据访问和填充。视图模型应该是纯数据容器,没有知识或行为。您通过从控制器操作填充 SelectList 来做正确的事情。
就 DRY 而言,它对我来说已经很 DRY 了。您只是在重复一两行代码。您可以使用 AutoMapper 之类的工具为您进行投影,使其更干一些:
var users = dbUsers.Users.ToArray();
model = new CreateCustomerWebsites
{
AllUsers = Mapper.Map<IEnumerable<SelectListItem>>(users)
};
...但是这样做,您将不得不添加更多代码来定义映射。您也可以像您在问题中提到的那样将投影移动到控制器上的私有方法,但这也意味着添加更多代码,并将一些相关代码从控制器操作主体中移走。而且您实际上只删除了 2 行代码(来自每个需要为下拉列表添加水合物的操作)。就个人而言,我真的看不出你现在的做法有什么问题。
另一种选择是编写 ActionFilterAttribute
以在 ResultExecuted
期间填充 SelectList。但要点是,不要从 ViewModel 执行此操作:在操作执行期间执行此操作。
这可能是满足您需求的 OTT,但我刚才看过这个并且还试图解决每次填充选择列表时都执行数据库查找的事实。
我有一个位于控制器和 dbcontext 之间的服务 class,因此在您的示例中我会有一个名为 UserService.cs
的 class。服务 class 处理业务逻辑并合理地保持控制器方法 'thin'。在 UserService class 中,您有一个名为 GetAsSelectList()
:
的方法
public SelectList GetAsSelectList()
{
var b = CacheHelper.GetCacheItem("UserSelectList", UsersDelegate, CacheHelper.SlidingParam, CacheHelper.AbsoluteParam);
return new SelectList((IEnumerable)b, "Id", "Name");
}
这使用 CacheHelper class 来检查内存中是否存在选择列表。如果是,那么它 returns 它,从而节省了数据库查找。如果不在缓存中,它会运行下面的方法来生成选择列表并将其存储在名称为 'UserSelectList'.
的缓存中
private object UsersDelegate()
{
return (from c in _context.Set<Users>()
select new
{
c.Id, c.Name
}).ToList();
}
实际CacheHelper
class可以查到here
使用这种方法为我节省了数据库查找的工作量,因为我正在使用多个选择列表填充表单,其中包含很少(如果有的话)更改的数据。
我刚刚开始使用 MVC,我能够在网上找到大多数 DRY 原则的示例。虽然我没有全部使用,因为我发现有些代码更难阅读。
我找到了一个我找不到示例的方法,但我觉得一定有办法做到这一点。
基本上现在我在控制器中填充模型选择列表类型对象,这意味着我必须重用代码。我知道我可以把它放在一个方法中,但我想知道是否无论如何都要把它放在模型中,以便在使用 model/viewmodel 时调用此操作来填充选择列表内容。
我在下面放了一个我的代码示例。
型号
using System;
using System.Data.Entity;
namespace MyWebsite.Models
{
// You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
namespace CustomerWebsites.Models
{
public class CustomerWebsites
{
public int Id { get; set; }
public Guid UserGuid { get; set; }
public string WebsiteAddress { get; set; }
public string WebsiteType { get; set; }
public DateTime ReleaseDate { get; set; }
public string Description { get; set; }
public decimal Budget { get; set; }
public DateTime CreationDate { get; set; }
public string DevelopmentStatus { get; set; }
public int CompletedPercentage { get; set; }
public bool Completed { get; set; }
public decimal TotalCost { get; set; }
public decimal TotalPaid { get; set; }
}
public class CustomerWebsitesDBContext : DbContext
{
public CustomerWebsitesDBContext()
: base("DefaultConnection")
{
}
public static CustomerWebsitesDBContext Create()
{
return new CustomerWebsitesDBContext();
}
public DbSet<CustomerWebsites> CustomerWebsites { get; set; }
}
}
}
ViewModel
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace MyWebsite.ViewModels
{
public class CreateCustomerWebsites
{
public int Id { get; set; }
public Guid UserGuid { get; set; }
[Required]
public string WebsiteAddress { get; set; }
public string WebsiteType { get; set; }
public DateTime ReleaseDate { get; set; }
public string Description { get; set; }
public decimal Budget { get; set; }
public DateTime CreationDate { get; set; }
public string DevelopmentStatus { get; set; }
public int CompletedPercentage { get; set; }
public bool Completed { get; set; }
public decimal TotalCost { get; set; }
public decimal TotalPaid { get; set; }
public IEnumerable<SelectListItem> AllUsers { get; set; }
}
}
控制器
// GET: CustomerWebsites/Create
public ActionResult Create()
{
var db = new ApplicationDbContext();
var users = db.Users.ToArray();
var allUsers = users.Select(x => new SelectListItem
{
Value = x.Id,
Text = x.Email
});
var model = new CreateCustomerWebsites
{
AllUsers = allUsers
};
return View(model);
}
// POST: CustomerWebsites/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(CreateCustomerWebsites model)
{
if (ModelState.IsValid)
{
var userGuid = new Guid(User.Identity.GetUserId());
var developmentStatus = "Pending MyWebsite Review";
if (User.IsInRole("Administrator"))
{
userGuid = model.UserGuid;
developmentStatus = model.DevelopmentStatus;
}
db.CustomerWebsites.Add(new CustomerWebsites
{
UserGuid = userGuid,
WebsiteAddress = model.WebsiteAddress,
CreationDate = DateTime.Now,
ReleaseDate = model.ReleaseDate,
Budget = model.Budget ,
Description = model.Description,
DevelopmentStatus = developmentStatus,
CompletedPercentage = model.CompletedPercentage,
Completed = model.Completed,
TotalCost = model.TotalCost,
TotalPaid = model.TotalPaid
});
db.SaveChanges();
return RedirectToAction("Index");
}
var dbUsers = new ApplicationDbContext();
var users = dbUsers.Users.ToArray();
var allUsers = users.Select(x => new SelectListItem
{
Value = x.Id,
Text = x.Email
});
model = new CreateCustomerWebsites
{
AllUsers = allUsers
};
return View(model);
}
查看
<h2>Create</h2>
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>CustomerWebsites</h4>
<hr />
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
@if (User.IsInRole("Administrator"))
{
<div class="form-group">
@Html.LabelFor(model => model.UserGuid, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.DropDownListFor(model => model.UserGuid, Model.AllUsers, "-- Select a user --")
@Html.ValidationMessageFor(model => model.UserGuid, "", new { @class = "text-danger" })
</div>
</div>
}
<div class="form-group">
@Html.LabelFor(model => model.WebsiteAddress, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.WebsiteAddress, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.WebsiteAddress, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.WebsiteType, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.WebsiteType, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.WebsiteType, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.ReleaseDate, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.ReleaseDate, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.ReleaseDate, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.Description, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.TextAreaFor(model => model.Description, new { rows = "10", @class = "form-control" })
@Html.ValidationMessageFor(model => model.Description, "", new { @class = "text-danger" })
</div>
</div>
@if (User.IsInRole("Administrator"))
{
<div class="form-group">
@Html.LabelFor(model => model.DevelopmentStatus, htmlAttributes: new {@class = "control-label col-md-2"})
<div class="col-md-10">
@Html.EditorFor(model => model.DevelopmentStatus, new {htmlAttributes = new {@class = "form-control"}})
@Html.ValidationMessageFor(model => model.DevelopmentStatus, "", new {@class = "text-danger"})
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.CompletedPercentage, htmlAttributes: new {@class = "control-label col-md-2"})
<div class="col-md-10">
@Html.EditorFor(model => model.CompletedPercentage, new {htmlAttributes = new {@class = "form-control"}})
@Html.ValidationMessageFor(model => model.CompletedPercentage, "", new {@class = "text-danger"})
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.Completed, htmlAttributes: new {@class = "control-label col-md-2"})
<div class="col-md-10">
<div class="checkbox">
@Html.EditorFor(model => model.Completed)
@Html.ValidationMessageFor(model => model.Completed, "", new {@class = "text-danger"})
</div>
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.TotalCost, htmlAttributes: new {@class = "control-label col-md-2"})
<div class="col-md-10">
@Html.EditorFor(model => model.TotalCost, new {htmlAttributes = new {@class = "form-control"}})
@Html.ValidationMessageFor(model => model.TotalCost, "", new {@class = "text-danger"})
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.TotalPaid, htmlAttributes: new {@class = "control-label col-md-2"})
<div class="col-md-10">
@Html.EditorFor(model => model.TotalPaid, new {htmlAttributes = new {@class = "form-control"}})
@Html.ValidationMessageFor(model => model.TotalPaid, "", new {@class = "text-danger"})
</div>
</div>
}
<div class="form-group">
@Html.LabelFor(model => model.Budget, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Budget, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.Budget, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
Basically at the moment I populate model selectlist type objects in the controller, this means I have to reuse the code. I know I could just put this in a method but I was wondering if there anyway to put it in the model so that anytime the model/viewmodel is used this action is called to populate the the selectlist contents.
在您的视图模型中放置任何方法通常不是一个好主意,尤其是对于数据访问和填充。视图模型应该是纯数据容器,没有知识或行为。您通过从控制器操作填充 SelectList 来做正确的事情。
就 DRY 而言,它对我来说已经很 DRY 了。您只是在重复一两行代码。您可以使用 AutoMapper 之类的工具为您进行投影,使其更干一些:
var users = dbUsers.Users.ToArray();
model = new CreateCustomerWebsites
{
AllUsers = Mapper.Map<IEnumerable<SelectListItem>>(users)
};
...但是这样做,您将不得不添加更多代码来定义映射。您也可以像您在问题中提到的那样将投影移动到控制器上的私有方法,但这也意味着添加更多代码,并将一些相关代码从控制器操作主体中移走。而且您实际上只删除了 2 行代码(来自每个需要为下拉列表添加水合物的操作)。就个人而言,我真的看不出你现在的做法有什么问题。
另一种选择是编写 ActionFilterAttribute
以在 ResultExecuted
期间填充 SelectList。但要点是,不要从 ViewModel 执行此操作:在操作执行期间执行此操作。
这可能是满足您需求的 OTT,但我刚才看过这个并且还试图解决每次填充选择列表时都执行数据库查找的事实。
我有一个位于控制器和 dbcontext 之间的服务 class,因此在您的示例中我会有一个名为 UserService.cs
的 class。服务 class 处理业务逻辑并合理地保持控制器方法 'thin'。在 UserService class 中,您有一个名为 GetAsSelectList()
:
public SelectList GetAsSelectList()
{
var b = CacheHelper.GetCacheItem("UserSelectList", UsersDelegate, CacheHelper.SlidingParam, CacheHelper.AbsoluteParam);
return new SelectList((IEnumerable)b, "Id", "Name");
}
这使用 CacheHelper class 来检查内存中是否存在选择列表。如果是,那么它 returns 它,从而节省了数据库查找。如果不在缓存中,它会运行下面的方法来生成选择列表并将其存储在名称为 'UserSelectList'.
的缓存中private object UsersDelegate()
{
return (from c in _context.Set<Users>()
select new
{
c.Id, c.Name
}).ToList();
}
实际CacheHelper
class可以查到here
使用这种方法为我节省了数据库查找的工作量,因为我正在使用多个选择列表填充表单,其中包含很少(如果有的话)更改的数据。