如何在运行时从现有数据模型中的数据构建自定义的动态ViewModel?

时间:2012-11-14 04:29:41

标签: c# asp.net sql-server asp.net-mvc-3

所以我正在开发一个Web应用程序,并遇到了一个新的挑战,我已经坚持了几个星期。我将提供有关我的应用程序和数据模型的背景信息,以及所需的最终结果。

基本应用信息&问题的背景: 我的应用程序旨在作为景观承包商可用于帮助管理其业务运营的工具。我的应用程序将提供一个他们可以创建帐户的位置,然后输入他们的所有客户信息(“客户”数据模型),以及他们为每个客户所做的工作的信息(“工作”数据模型) 。客户端和作业之间存在一对多关系(一个客户端可以有多个作业,但任何给定作业只能有一个客户端)。

背景: 我有两个简单的数据模型,“客户端”和“工作”。我的应用程序是在ASP.net MVC3框架中构建的。使用Entity Framework脚手架机制,我为每个数据模型(创建,读取,更新,删除)创建了基本的CRUD视图。这在很大程度上是很好的(我可以创建新的客户端和作业,并且足够简单地编辑现有的客户端和作业)。

业务问题: 我需要在我的应用程序中允许批量创建新作业。我希望我的用户(景观承包商)能够输入他们当天完成的所有割草工作。因此,我想让我对此过程的观点填充一个包含所有活动客户端的表 - 每个客户端旁边都有一个复选框。然后我希望用户能够为他们为新工作(修剪他们的草坪)的每个客户复选框,并提交表单(输入完成的工作),其结果将是为每个客户创建的新工作。

技术问题: 我最好的猜测是我需要在控制器中创建一个自定义ViewModel并将其发送到视图,其中ViewModel将是基于当前活动客户端创建的新作业列表。然后在视图中,复选框可以将Client_ID(客户端的唯一标识符)作为其值(这将是ViewModel的一部分。当用户提交表单时,视图会将ViewModel传递回控制器。然后控制器可以查看ViewModel作业列表,并为每个已选中复选框的ViewModel作业创建新作业。

所以,我的问题是 - 如何使用控制器执行以下操作: 1.)根据客户端列表中的数据(“客户端”数据模型)在运行时构建ViewModel作业列表? 2.)我怎样才能将其传递给视图? 3.)一旦它返回到控制器,我如何迭代列表并相应地修改我的其他数据模型(创建新的“作业”项目)?

我创建了自定义ViewModel,其中包含构建新作业条目所需的客户端和作业的属性(客户端名称,客户端地址,客户端ID,作业备注,工作人员,工作人员大小,工作时间,复选框以指示完成等)。假设用户有50个客户,他将草坪作为活跃客户。我想构建一个包含50行的ViewModel(代表每个可能修剪草坪的客户端)。然后我想将它发送到视图并用复选框显示它,这表示草坪是否被切割。当模型返回到视图时,控制器将在复选框中选中带有检查的行,并在该表中创建新的“作业”行。

提前感谢您提供的任何帮助,我知道这对您们中的许多人来说可能很容易。我是C#和MVC3的新手。

更新 这是我的代码 -

工作模式

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Entity;
using System.ComponentModel.DataAnnotations;

namespace LawnTracker.Models
{
    public class Job
    {
        [Key]
        public int Job_ID { get; set; }
        public int Client_ID { get; set; }
        public int Account_ID { get; set; }
        public string Name { get; set; }
        public string Location { get; set; }
        public string Notes { get; set; }
        public string SvcRoute { get; set; }
        public string Service { get; set; }
        public string Date { get; set; }
        public double SvcPriceOverride { get; set; }
        public float SvcQty { get; set; }
        public string UofM { get; set; }
        public bool Invoiced { get; set; }
        public string Crew { get; set; }
        public int TechCount { get; set; }
        public string TimeStart { get; set; }
        public string TimeFinish { get; set; }
        public double TimeSpent { get; set; }
        public string Image1 { get; set; }
        public string Image2 { get; set; }
        public string Image3 { get; set; }
        public double MulchUsed { get; set; }
        public double FertUsed { get; set; }
        public double HerbUsed { get; set; }
        public string NextDue { get; set; }

    }


}

我的MowingJobViewModel模型 -

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace LawnTracker.Models
{
    public class MowingJobViewModel
    {
        public int Client_ID { get; set; }

        public bool Completed { get; set; }

        public string Name { get; set; }

        public string Street { get; set; }

        public string City { get; set; }

        public double SvcPriceOverride { get; set; }

        public string UofM { get; set; }

        public int SvcQty { get; set; }

        public string Notes { get; set; }

        public string Date { get; set; }

        public string Crew { get; set; }

        public int TechCount { get; set; }

        public string SvcRoute { get; set; }

        public string Schedule { get; set; }
    }
}

我的JobController -

 // GET: /Job/CreateMowing

        public ActionResult CreateMowing(string route = "", string sched = "")
        {
            List<SelectListItem> listItems = new List<SelectListItem>();
            listItems.Add(new SelectListItem()
            {
                Value = "Lump Sum",
                Text = "Lump Sum"
            });
            listItems.Add(new SelectListItem()
            {
                Value = "Hours",
                Text = "Hours"
            });


            ViewBag.Units = new SelectList(listItems, "Value", "Text");
            ViewBag.Routes = db.Clients.Select(r => r.SvcRoute).Distinct();
            ViewBag.Sched = db.Clients.Select(r => r.MowSched).Distinct();

            var model = from r in db.Clients
                        orderby r.SvcRoute
                        where (r.Mowing == true) &&
                        (r.Status == "Active") &&
                        (r.SvcRoute == route || (route == "")) &&
                        (r.MowSched == sched || (sched == "")) 
                        select r;
            if (model.Count() > 0)
            {
                ViewBag.total = model.Select(r => r.MowPrice).Sum();
            }
            else
            {
                ViewBag.total = 0.00;
            }

           /* Build a list of MowingJobViewModel objects based on the above defined list of clients
            * who are subscribed to mowing and active. This will enable batch entry for new jobs done.
            * This list of MowingJobViewModel objects will be sent to the client after a HTTP GET
            * request for the CreateMowing view.  The user will be able to check boxes associated
            * with each client in the client list.  When the form is submitted, the controller
            * receives the model back with the updated information (completed, notes, etc.) about
            * each job.  Then the controller must update the jobs table, adding the new jobs based on 
            * the view model returned from the view / client.
            * 
            */

            //Create a new list of MowingJobViewModel objects
             IEnumerable<MowingJobViewModel> mjList = new List<MowingJobViewModel>();

            //iterate through the list of clients built from earlier (in model)...
             foreach (var item in model)
             {  
             //create new MowingJobViewModel object MJ and add it to the list
             mjList.Add(new MowingJobViewModel()
             {
               Client_ID = item.Client_ID,
               Completed = false,
               Name = (item.FirstName + " " + item.LastName),
               Street = item.Address1,
               City = item.City,
               SvcPriceOverride = item.MowPrice,
               UofM = "Lump Sum",
               SvcQty = 1,
               Notes = "",
               Date = "",
               Crew = "",
               TechCount = 2,
               SvcRoute = item.SvcRoute,
               Schedule = item.MowSched,
              });

             }

             return View(mjList);

             }

**I don't have my view ("CreateMowing.cshtml") worked out correctly, but here is what I have-**

@model IEnumerable<LawnTracker.Models.MowingJobViewModel>

@{
    ViewBag.Title = "Enter Mowing Jobs";
}

<h2>Enter Mowing Jobs</h2>

<div style="float: left; clear:both; width: 100%;">
    <b>Total Jobs: @Html.Encode(Model.Count())</b><br />
    <b>Total Revenue: $@Html.Encode(ViewBag.total)</b><br /><br />
</div>
<p></p>

<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>

<div style="float: right; clear:both; width: 100%;">
     @using (Html.BeginForm("CreateMowing", "Job", FormMethod.Get)) 
    {
    <table>
    <tr>
        <th></th>

        <th>
            Route
        </th>

        <th>Schedule</th>
        <th></th>
    </tr>
    <tr>
        <td>
            Show:
        </td>

        <td>
            @Html.DropDownList("route", new SelectList(ViewBag.Routes), "--ALL--")            
        </td>

        <td>
            @Html.DropDownList("sched", new SelectList(ViewBag.Sched), "--ALL--")
        </td>
        <td>            
            <input type="submit" value="Filter" />
        </td>
    </tr>
</table><br /><br />

    }
</div>

<table>
    <tr>
        <th>
            Completed
        </th>
        <th>
            First Name
        </th>
        <th>
            Last Name
        </th>
        <th>
            Street
        </th>

        <th>
            City
        </th>

        <th>
            Service Route
        </th>
        <th>
            Price
        </th>

        <th>
            Units
        </th>

        <th>
            Qty
        </th>
        <th>
            Notes
        </th>

        <th>
            Schedule
        </th>

    </tr>

@foreach (var item in Model) {
    <tr>
        <td>
            <input type="checkbox" name="invoiced" value="@item.Client_ID" >
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.FirstName)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.LastName)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Address1)
        </td>

        <td>
            @Html.DisplayFor(modelItem => item.City)
        </td>

        <td>
            @Html.DisplayFor(modelItem => item.SvcRoute)
        </td>

        <td>
            @Html.DisplayFor(modelItem => item.MowPrice)
        </td>

        <td>

        </td>

        <td>

        </td>
        <td>

        </td>

        <td>
            @Html.DisplayFor(modelItem => item.MowSched)
        </td>

     </tr>
}

</table>


<div>
    <br />
    @Html.ActionLink("Back to List", "Index")
</div>

1 个答案:

答案 0 :(得分:0)

您创建了一个视图模型,其中包含创建作业时特定客户端的显示属性/数据输入字段。由于你没有给它起个名字,我称之为NewJobEntry。正如您所猜测的那样,您现在需要一个具有List<NewJobEntry>类型属性的视图模型来表示可以一次创建的可变数量的新作业。

您的控制器将填充列表,为每个客户端添加NewJobEntry个实例。

您的视图在提供包含列表的模型时,可以枚举列表并为每个NewJobEntry生成一行。

将数据发布回控制器后,默认的ASP.NET MVC活页夹可以处理列表,但存在一些怪癖。只要字段名称具有顺序索引,默认活页夹就会自动为列表中的对象加水合。要确保正确生成这些索引,您需要使用视图中具有顺序索引的lambda表达式进行绑定:

Html.CheckboxFor(model => model.JobList[i].IsSelected)

在枚举列表时,您可以考虑使用for循环而不是foreach循环。

您还需要注意复选框,因为未经检查的复选框实际上不会出现在发送到控制器的发布数据中。这将破坏绑定,因为在post数据中发送的索引可能不再是顺序的(只要有未选中的复选框,就会出现间隙)。因此,我建议您为客户端ID包含一个隐藏字段。这样,post数据将始终包含至少一个字段,其中包含每个列表条目的相应顺序索引。

Html.HiddenFor(model => model.JobList[i].ClientId)

现在你的帖子数据将是:

JobList_0_IsSelected=true
JobList_0_ClientId=12345
JobList_1_ClientId=12346
JobList_2_IsSelected=true
JobList_2_ClientId=12347

默认活页夹将处理此问题并重建您的列表。请注意,如果没有客户端ID的隐藏字段,则会丢失索引1,并且默认绑定器将无法正确重建列表。

希望有所帮助!