【问题标题】:Proper way to populate a ViewModel?填充 ViewModel 的正确方法?
【发布时间】:2015-03-15 17:58:50
【问题描述】:

我正在开发一个用于工作的 webapp,并且我正在使用标准的 CRUD 样式交互。但是,我不希望用户更新某些字段,因此我将它们从视图中删除。但是,如果我没有明确设置这些字段,那么当模型在数据库中更新时它们会被清除。

我关心为我的 ViewModel 填充字段的正确方法是什么。

我想出的粗略想法是这样的:

我的视图模型:

public class EditSoftwareTrackingViewModel 
{
    public EditSoftwareTrackingViewModel(SoftwareTracking model)
    {
        Id = model.Id;
        SoftwareId = model.SoftwareId;
        ComputerId = model.ComputerId;
        SoftwareActionId = model.SoftwareActionId;
        LastModified = model.LastModified;
        Computer = model.Computer;
        Software = model.Software;
        SoftwareAction = model.SoftwareAction;
    }
    public int Id { get; set; }
    [DisplayName("Software")]
    public int SoftwareId { get; set; }
    [DisplayName("Computer")]
    public int ComputerId { get; set; }
    [DisplayName("Software Action")]
    public int SoftwareActionId { get; set; }
    [DisplayName("Last Modified")]
    public DateTime? LastModified { get; set; }

    public virtual Computer Computer { get; set; }
    public virtual Software Software { get; set; }
    public virtual SoftwareAction SoftwareAction { get; set; }
}

我的主要模特

[Table("asset.SoftwareTracking")]
public partial class SoftwareTracking
{
    public int Id { get; set; }
    [DisplayName("Software")]
    public int SoftwareId { get; set; }
    [DisplayName("Computer")]
    public int ComputerId { get; set; }
    [DisplayName("Date Entered")]
    public DateTime? EnteredDate { get; set; }
    [DisplayName("Software Action")]
    public int SoftwareActionId { get; set; }
    [DisplayName("Last Modified")]
    public DateTime? LastModified { get; set; }

    public virtual Computer Computer { get; set; }
    public virtual Software Software { get; set; }
    public virtual SoftwareAction SoftwareAction { get; set; }
}

我的控制器使用视图模型

    public ActionResult Edit(int? id)
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        EditSoftwareTrackingViewModel softwaretracking = new EditSoftwareTrackingViewModel(db.SoftwareTrackings.Find(id));
        if (softwaretracking == null)
        {
            return HttpNotFound();
        }
        GeneratePageData(softwaretracking.Software.Id);
        return View(softwaretracking);
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Edit(EditSoftwareTrackingViewModel softwaretracking)
    {
        if (ModelState.IsValid)
        {
            softwaretracking.LastModified = DateTime.Now;
            var softwareTrack = db.SoftwareTrackings.Find(softwaretracking.Id);
            softwareTrack = new SoftwareTracking
            {
                Computer = softwaretracking.Computer,
                ComputerId = softwaretracking.ComputerId,
                LastModified = softwaretracking.LastModified,
                Software = softwaretracking.Software,
                SoftwareAction = softwaretracking.SoftwareAction,
                SoftwareActionId = softwaretracking.SoftwareActionId,
                SoftwareId = softwaretracking.SoftwareId,
                EnteredDate = softwareTrack.EnteredDate
            };

            db.Entry(softwareTrack).State = EntityState.Modified;
            db.SaveChanges();
            return RedirectToAction("Index");
        }
        GeneratePageData(softwaretracking.Software.Id);
        return View(softwaretracking);
    }

还有更好的选择吗?还是应该继续以这种方式创建我的视图模型?

编辑

我的业务逻辑和视图

    private void GeneratePageData(int? id = null)
    {

        ViewBag.Computers = new SelectList(db.Computers, "Id", "ComputerName");
        ViewBag.SoftwareActions = new SelectList(db.SoftwareActions, "Id", "ActionPerformed");

        var usedSoft = (from softTrack in db.SoftwareTrackings
                        where (softTrack.SoftwareActionId != 3)
                        select softTrack.Software);

        var softwareList = (from soft in db.Softwares
                            where (
                                ((from softTrack in db.SoftwareTrackings
                                  where (softTrack.SoftwareActionId != 3 && softTrack.SoftwareId == soft.Id)
                                  select softTrack.Software).Count() < soft.KeyQuantity)
                                && !(soft.AssetStatusId == 4 || soft.AssetStatusId == 5)
                                || soft.Id == id)
                            select soft).ToList();

        ViewBag.SoftwareList = softwareList.Select(t => new SelectListItem
        {
            Text = t.SoftwareIdNameFull,
            Value = t.Id.ToString()
        });

    }

我的看法

@model Lighthouse_Asset_Manager.Models.EditSoftwareTrackingViewModel

@{
    ViewBag.Title = "Edit Software Install";
    Layout = "";
}

<div class="modal-header">
    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">
        &times;
    </button>
    <h4 class="modal-title" id="myModalLabel">Edit Software Install</h4>
</div>
<div class="modal-body">

    @using (Html.BeginForm(null, null, FormMethod.Post, new { id = "computerForm" }))
    {
        @Html.AntiForgeryToken()
        @Html.HiddenFor(model => model.Id)

        <div class="form-horizontal">
            @Html.ValidationSummary(true)


            <div class="form-group">
                @Html.LabelFor(model => model.SoftwareId, "Software", new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.DropDownList("SoftwareId", (IEnumerable<SelectListItem>)ViewBag.SoftwareList, "-- Select --", new
                    {
                        @style = "width:100%",
                        @class = "select2"
                    })
                    @Html.ValidationMessageFor(model => model.SoftwareId)
                </div>
            </div>

            <div class="form-group">
                @Html.LabelFor(model => model.ComputerId, "Computer", new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.DropDownList("ComputerId", (IEnumerable<SelectListItem>)ViewBag.Computers, "-- Select --", new
                    {
                        @style = "width:100%",
                        @class = "select2"
                    })
                    @Html.ValidationMessageFor(model => model.ComputerId)
                </div>
            </div>

            <div class="form-group">
                @Html.LabelFor(model => model.SoftwareActionId, "Action", new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.DropDownList("SoftwareActionId", (IEnumerable<SelectListItem>)ViewBag.SoftwareActions, "-- Select --", new
                    {
                        @style = "width:100%",
                        @class = "form-control"
                    })
                    @Html.ValidationMessageFor(model => model.SoftwareActionId)
                </div>
            </div>


            <div class="form-actions no-color">
                <button type="submit" class="btn btn-primary btn-sm"><i class="fa fa-floppy-o"></i> Edit Install Record</button>
                <button type="button" class="btn btn-default" data-dismiss="modal">
                    Cancel
                </button>
            </div>
        </div>
    }
</div>

【问题讨论】:

  • 使用视图模型是正确的方法,但是在您的POST方法中,您需要从数据库中获取原始SoftwareTracking对象并将您的视图模型属性映射到它,然后保存原始对象。旁注:您不需要(也不应该)需要在 POST 方法中调用 GeneratePageData(softwaretracking.Software.Id);(尝试基于原始模型重置视图模型属性无论如何都会被忽略)
  • 我更新了我上面的代码,所以它确实可以工作,但是,我不知道还能做些什么来让它更干净、更高效。我有 GeneratePageData 的原因是因为我用视图中显示的某些列表填充 ViewBag。这些是从 get 方法中携带的吗?
  • 不,您需要在返回视图之前重新填充任何选择列表 - 如果这就是它所做的一切,那么它就可以了。由于您有一个视图模型,您应该在视图模型中包含SelectList 属性,而不是在ViewBag 中,并且您的视图模型包含一个似乎不必要的LastModified 属性(只需在数据模型中设置值发回并保存时的控制器 - 我假设它不是用户应该能够编辑的东西)。您还可以查看automapper 之类的工具,使其更轻松、更简洁
  • 我实际上正在执行一些非常复杂的 linq 语句来创建选择列表。我认为将业务逻辑排除在模型之外是最佳做法吗?
  • 我同意。我只是建议您的视图模型包含 SelectLists 的属性,并且您仍然有一个通用的私有方法来分配您在 GET 和(假设您返回视图)POST 方法中调用的那些属性。我使用类似private void ConfigureEditModel(MyViewModel model) { model.MyFirstSelectList = someLinqQuery; }的东西。

标签: c# asp.net-mvc entity-framework asp.net-mvc-5 viewmodel


【解决方案1】:

在不从 Db 加载对象的情况下修补更新模型。试试Attach

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Edit(EditSoftwareTrackingViewModel softwaretracking)
    {
        if (ModelState.IsValid)
        {
            var softwareTrack = new SoftwareTracking
            {
                 Computer = softwaretracking.Computer,
                 ComputerId = softwaretracking.ComputerId,
                 LastModified = softwaretracking.LastModified,
                 Software = softwaretracking.Software,
                 SoftwareAction = softwaretracking.SoftwareAction,
                 SoftwareActionId = softwaretracking.SoftwareActionId,
                 SoftwareId = softwaretracking.SoftwareId,
                 EnteredDate = softwareTrack.EnteredDate
            };
            db.SoftwareTrackings.Attach(softwareTrack);

            db.Entry(softwareTrack).Property(a => a.Computer).IsModified = true;
            db.Entry(softwareTrack).Property(a => a.ComputerId).IsModified = true;
            db.Entry(softwareTrack).Property(a => a.LastModified).IsModified = true;
            db.Entry(softwareTrack).Property(a => a.Computer).IsModified = true;
            db.Entry(softwareTrack).Property(a => a.Software).IsModified = true;
            db.Entry(softwareTrack).Property(a => a.SoftwareAction).IsModified = true;

            db.Entry(softwareTrack).Property(a => a.SoftwareActionId).IsModified = true;
            db.Entry(softwareTrack).Property(a => a.SoftwareId).IsModified = true;
            db.SaveChanges();
            return RedirectToAction("Index");
        }
        GeneratePageData(softwaretracking.Software.Id);
        return View(softwaretracking);
    }

关于第二个问题是使用ViewModel还是直接使用Model。这确实是一个见仁见智的问题,每种方法都有其优点和缺点。我对此没有强烈的意见,我只是想指出这些利弊供您考虑:

  • 直接使用模型使我们免于创建视图模型,从而使源代码更小并避免映射逻辑,但它会混合关注点。由于您将相同的模型用于域逻辑和与客户端的通信,因此如果我们不考虑这一点,对模型的任何更改都可能传播到客户端。
  • 使用 viewModel 是分离关注点的好方法,但它需要更多的努力和映射逻辑(可能会稍微降低性能)。为了有效地应用 ViewModel,我建议使用映射器:https://github.com/AutoMapper/AutoMapper/wiki/Getting-started

【讨论】:

  • 如果我走这条路,使用 ViewModel 将是多余的,因为没有必要使用 ViewModel 来消除字段。我可以简单地标记我想要更改的字段。
  • @Jdsfighter:您必须向实体框架表明您需要更新哪些字段,因为实体框架无法意识到这一点,因为您在另一个上下文中更新您的数据。根据您的情况,您可能需要将 ViewModel 转换为您的模型并标记要更新的字段。或者,如果两个模型之间没有太多不匹配,您可以直接使用模型。
  • 模型是相同的,只是视图模型中排除了某些属性。
  • @Jdsfighter:如果有不匹配。我们通常会创建一个 Mapper 来从 ViewModel 和 Model 映射,以便我们可以重用这个映射代码。一个很好的库是github.com/AutoMapper/AutoMapper/wiki/Getting-started
  • @Jdsfighter:通常情况下,ViewModel应该是来自Model的flattened,并且不包含复杂的域结构。
【解决方案2】:

您应该使用 AutoMapper 使 Model 和 ViewModel 之间的映射更清晰。首先使用此代码创建映射器。

Mapper.CreateMap<SoftwareTracking, EditSoftwareTrackingViewModel>();
Mapper.CreateMap<EditSoftwareTrackingViewModel, SoftwareTracking>();

当您想从模型创建视图模型时,请执行以下操作:

public ActionResult Edit(int? id)
{
    SoftwareTracking tracking = db.SoftwareTrackings.Find(id);
    EditSoftwareTrackingViewModel viewmodel = 
        Mapper.Map<SoftwareTracking, EditSoftwareTrackingViewModel>(tracking);
    return View(viewmodel);
}

当您想将视图模型中的信息填充回模型时,请执行此操作

public ActionResult Edit(EditSoftwareTrackingViewModel vm)
{
    if (ModelState.IsValid)
    {
        vm.LastModified = DateTime.Now;
        var softwareTrack = db.SoftwareTrackings.Find(softwaretracking.Id);
        softwareTrack = 
           Mapper.Map<EditSoftwareTrackingViewModel, SoftwareTracking>(vm, softwareTrack);

        db.Entry(softwareTrack).State = EntityState.Modified;
        db.SaveChanges();
        return RedirectToAction("Index");
    }

【讨论】:

    【解决方案3】:

    您使用视图模型的方法是一种很好的方法。 this question 的答案解释了一些好处,包括防止过度发布攻击、使用特定于视图的显示和验证属性以及包括特定于视图的属性,例如 SelectLists。诸如automapper 之类的工具可以轻松地在您的数据和视图模型之间进行映射,并减少控制器中的代码。我建议对您的视图模型进行一些更改。 LastModifiedComputerSoftwareSoftwareAction 属性不是必需的(您不绑定到这些属性),我会在模型中包含 SelectList 属性而不是 ViewBag

    查看模型

    public class EditSoftwareTrackingViewModel 
    {
      public int Id { get; set; }
      [Display(Name="Software")]
      public int SoftwareId { get; set; }
      [Display(Name="Computer")]
      public int ComputerId { get; set; }
      [Display(Name="Software Action")]
      public int SoftwareActionId { get; set; }
      public SelectList Computers { get; set; }
      public SelectList SoftwareActions{ get; set; }
      public SelectList SoftwareList{ get; set; }
    }
    

    然后更改GeneratePageData()方法以接受视图模型

    private void GeneratePageData(EditSoftwareTrackingViewModel model)
    {
      model.Computers = new SelectList(db.Computers, "Id", "ComputerName");
      ....
    

    在视图中(总是最好使用强类型帮助器)

    @Html.DropDownListFor(m => m.SoftwareId, Model.SoftwareList, "-- Select --", new { @class = "select2" })
    

    还有一些需要注意的事项。

    • 您应该使用[Display(Name="..")] 属性(不是 [DisplayName(..)])
    • 设置LastModified 属性时,应考虑使用 UCT时间。
    • 视图中不需要Id 属性的隐藏输入 (假设您使用默认的 {controller}/{action}/{id} 路由 mapping) - 它被添加到路由值中并且无论如何都会被绑定
    • 除非您特别想要表单的id 属性,否则您可以 只需使用@using(Html.BeginForm()) {
    • 您不需要LabelFor() 中的第二个参数 - 它可以只是 Html.LabelFor(m => m.SoftwareId, new { @class = "control-label col-md-2" }) 因为您已在 [Display] 中指定它 属性

    最后,如果您想进一步简化视图,您可以考虑自定义 EditorTemplates 或 html 帮助器,如 this answer 中所示,这将允许您替换

    <div class="form-group">
      @Html.LabelFor(model => model.SoftwareId, new { @class = "control-label col-md-2" })
      <div class="col-md-10">
        @Html.DropDownListFor(m => m.SoftwareId, Model.SoftwareList, "-- Select --", new { @class = "select2" })
        @Html.ValidationMessageFor(model => model.SoftwareId)
      </div>
    </div>
    

    与(自定义EditorTemplate

    @Html.EditorFor(m => m.SoftwareId, "BootstrapSelect", Model.SoftwareList)
    

    或(自定义HtmlHelper

    @Html.BootstrapDropDownFor(m => m.SoftwareId, Model.SoftwareList)
    

    【讨论】:

    • 里面有一些很棒的建议。非常感谢您的意见。
    • 在旁注中,您提到了从模型中设置 LastModified 属性。一个人将如何实现这一目标。我似乎无法理解它。
    • 假设您不希望用户编辑它,那么您唯一一次设置它的值 (DateTime.Now) 是在保存之前立即在控制器中(即将视图模型映射到数据模型,设置数据模型的最后修改日期,然后保存数据模型
    • 这就是我目前的处理方式。我只是在映射 ViewModel 后更新模型并将其发布到数据库。我还更新了我的所有模型以包含 SelectLists,以便我只需将它们的数据加载到视图中,而不再从控制器中执行逻辑。
    • 是的,我知道,只是之前您在视图模型中拥有该属性,然后更新了视图模型,然后将其映射到数据模型(前两个步骤并不是真正必要的 :)
    【解决方案4】:

    这是模型类

    [Table("CURRENCY")]
        public class CurrencyClass : ICurrency
        {
            private Int32 mCURRENCY_ID = default(Int32);
            [Key]
            public virtual Int32 CURRENCY_ID  
            {
                get { return mCURRENCY_ID; }
                set { mCURRENCY_ID = value; }
            }
            private string mCURRENCY_NAME = default(string); 
            public virtual string CURRENCY_NAME 
            { 
                get { return mCURRENCY_NAME;}
                set { mCURRENCY_NAME = value;}
            }
            private string mCURRENCY_DESC = default(string);
            public  virtual string CURRENCY_DESC 
            {
                get { return mCURRENCY_DESC; }
                set { mCURRENCY_DESC = value; }
            }
            private string mCURRENCY_SYMBOLE = default(string);
            public virtual string CURRENCY_SYMBOLE 
            {
                get { return mCURRENCY_SYMBOLE; }
                set { mCURRENCY_SYMBOLE = value; }
            }
            private Int32 mcreated_by = default(Int32);
            public virtual Int32 created_by 
            {
                get { return mcreated_by; }
                set { mcreated_by = value; } 
            }
            private DateTime mcreated_date = default(DateTime);
            public virtual DateTime created_date 
            {
                get { return mcreated_date; }
                set { mcreated_date = value; } 
            }
            private Int32 mmodified_by = default(Int32);
            public virtual Int32 modified_by 
            {
                get { return mmodified_by; }
                set { mmodified_by = value; } 
            }
            private DateTime mmodified_date = default(DateTime);
            public virtual DateTime modified_date 
            {
                get { return mmodified_date; }
                set { mmodified_date = value; }
            }
        }
    

    这是视图模型

    public class CurrencyViewModel
        {
            [Key]
            public Int32 CURRENCY_Id { get; set; }
            [Required(ErrorMessage="Currency Name is required")]
            public string CURRENCY_NAME { get; set; }
            [Required(ErrorMessage="Currency Description is required")]
            public string CURRENCY_DESC { get; set; }
            [Required(ErrorMessage = "Currency Symbole is Required")]
            public string CURRENCY_SYMBOLE { get; set; }
        }
    

    这就是行动

    [HttpPost]
            [ActionName("Create")]
            public ActionResult Create(CurrencyViewModel vm)
            {
                if (!ModelState.IsValid)
                {
                    return View("Create");
                }   
    
                obj.CURRENCY_NAME = vm.CURRENCY_NAME;
                obj.CURRENCY_DESC = vm.CURRENCY_DESC;
                obj.CURRENCY_SYMBOLE = vm.CURRENCY_SYMBOLE;
                obj.created_by = 1;
                obj.created_date = DateTime.Now;
                obj.modified_by = 1;
                obj.modified_date = DateTime.Now;
                db.Currencies.Add(obj);
                db.SaveChanges();
    
                return RedirectToAction("Index");
            }
    

    【讨论】:

      猜你喜欢
      • 2016-03-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-20
      • 1970-01-01
      • 1970-01-01
      • 2012-08-30
      • 2014-02-28
      相关资源
      最近更新 更多