在 MVC 中编辑视图模型的更好方法

Better Way To Edit A View Model in MVC

我实际上想出了一个工作示例,说明如何在 MVC 中显示 (GET) 和编辑 (POST) 由三个模型组成的视图模型。但是,我的 MVC 技能有限,我正在寻找关于 "the right way" 我应该这样做的建议。 有问题的部分是我将表单字段单独发回而不是发送到视图模型,这是我不知道该怎么做的事情。

这是我的模型

姓名

public partial class Name
{
    public Name()
    {
        this.Addresses = new HashSet<Address>();
        this.Emails = new HashSet<Email>();
    }

    public int ID { get; set; }
    public string FIRST_NAME { get; set; }
    public string LAST_NAME { get; set; }

    public virtual ICollection<Address> Addresses { get; set; }
    public virtual ICollection<Email> Emails { get; set; }
}

地址

public partial class Address
{
    public int ADDRESS_ID { get; set; }
    public int NameID { get; set; }
    public string ADDRESS_1 { get; set; }
    public string CITY { get; set; }
    public string STATE { get; set; }
    public string ZIP { get; set; }

    public virtual Name Name { get; set; }
}

电子邮件

public partial class Email
{
    public int EMAIL_ID { get; set; }
    public int NameID { get; set; }
    public string EMAIL { get; set; }

    public virtual Name Name { get; set; }
}

我的视图模型由三个模型的所有字段组成。

public class ContactFormViewModel
{
    public int? ID { get; set; }
    public string FIRST_NAME { get; set; }
    public string LAST_NAME { get; set; }
    public string ADDRESS_1 { get; set; }
    public string CITY { get; set; }
    public string STATE { get; set; }
    public string ZIP { get; set; }
    public string EMAIL { get; set; }
}

Edit页面的GET方法(在Controller中)

    // GET: Names/Edit/5
    //The GET method takes the id from the URL and passes it into the query to return data for the specific record
    public ActionResult Edit(int id)
    {
        //This query is an outer join of the Name, Address and Email models/tables
        var query = from n in db.Names
                    join a in db.Addresses
                    on n.ID equals a.NameID into na
                    from a in na.DefaultIfEmpty()
                    join e in db.Emails
                    on n.ID equals e.NameID into ne
                    from e in ne.DefaultIfEmpty()
                    where n.ID == id
                    //Creates a new instance of the view model, populated with the query data
                    select new ContactFormViewModel
                    {
                        ID = id,
                        FIRST_NAME = n.FIRST_NAME,
                        LAST_NAME = n.LAST_NAME,
                        ADDRESS_1 = a.ADDRESS_1,
                        CITY = a.CITY,
                        STATE = a.STATE,
                        ZIP = a.ZIP,
                        EMAIL = e.EMAIL
                    };

        //Returns the query to the view
        return View(query);
    }

Edit页面的POST方法(在Controller中)

    // POST: Names/Edit/5
    [HttpPost]
    [ValidateAntiForgeryToken]
    //The POST method takes the individual form field data and passes it to queries that update all three models separately 
    public ActionResult Edit(int id, string FIRST_NAME, string LAST_NAME, string ADDRESS_1, string CITY, string STATE, string ZIP, string EMAIL)
    {
        if (ModelState.IsValid)
        {
            //Query the database for the row to be updated. 
            var queryN =
                from n in db.Names
                where n.ID == id
                select n;

            var queryA =
                from a in db.Addresses
                where a.NameID == id
                select a;

            var queryE =
                from e in db.Emails
                where e.NameID == id
                select e;

            //Assign the form field data to the fields in the model 
            foreach (Name n in queryN)
            {
                n.FIRST_NAME = FIRST_NAME;
                n.LAST_NAME = LAST_NAME;
            }

            //If there are no address records, insert
            if (!queryA.Any())
            {
                //New instance of Address
                var address = new Address
                {
                    NameID = id,
                    ADDRESS_1 = ADDRESS_1,
                    CITY = CITY,
                    STATE = STATE,
                    ZIP = ZIP

                };
                db.Addresses.Add(address);
            }
            //Else, if there are address records, then update
            else
            {
                foreach (Address a in queryA)
                {
                    a.ADDRESS_1 = ADDRESS_1;
                    a.CITY = CITY;
                    a.STATE = STATE;
                    a.ZIP = ZIP;
                }
            }

            //If there are no email records, insert
            if (!queryE.Any())
            {
                //New instance of Email
                var email = new Email
                {
                    NameID = id,
                    EMAIL = EMAIL
                };
                db.Emails.Add(email);
            }
            //Else, if there are email records, then update
            else
            {
                foreach (Email e in queryE)
                {
                    e.EMAIL = EMAIL;
                }
            }


            //// Submit the changes to the database. 
            try
            {
                db.SaveChanges();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
                // Provide for exceptions.
            }

        }
        return RedirectToAction("Index");
    }

景色

@model IQueryable<MDTestApplication.ViewModel.ContactFormViewModel>

@{
    ViewBag.Title = "Edit";
}

<h2>Edit</h2>

@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()

    <div class="form-horizontal">
        <h4>Name</h4>
        <hr />

        @*Uses foreach loop to get all field data from the view model*@
        @foreach (var item in Model)
        {
            @Html.ValidationSummary(true, "", new { @class = "text-danger" })
            @Html.HiddenFor(modelItem => item.ID)

            <div class="form-group">
                @Html.LabelFor(modelItem => item.FIRST_NAME, htmlAttributes: new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @*Using razor syntax to output the value*@
                    @*Using form field 'Name' attribute for posting back to the controller*@
                    <input type="text" name="FIRST_NAME" value="@item.FIRST_NAME" class="form-control" />
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(modelItem => item.LAST_NAME, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                <input type="text" name="LAST_NAME" value="@item.LAST_NAME" class="form-control" />
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(modelItem => item.ADDRESS_1, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                <input type="text" name="ADDRESS_1" value="@item.ADDRESS_1" class="form-control" />
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(modelItem => item.CITY, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                <input type="text" name="CITY" value="@item.CITY" class="form-control" />
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(modelItem => item.STATE, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                <input type="text" name="STATE" value="@item.STATE" class="form-control" />
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(modelItem => item.ZIP, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                <input type="text" name="ZIP" value="@item.ZIP" class="form-control" />
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(modelItem => item.EMAIL, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                <input type="text" name="EMAIL" value="@item.EMAIL" class="form-control" />
            </div>
        </div>

    }
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </div>
    </div>
}

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

更新

下面是我添加到 Alex 的回答中的附加插入和更新代码。您将为地址和电子邮件使用相同的设置。

foreach (var address in model.Addresses)
    {
        var addressToUpdate = name.Addresses.FirstOrDefault(a => AddressId== address.AddressId);
        if (addressToUpdate != default(Address))
        {
            // preform an update
            addressToUpdate.AddressId = address.AddressId;
            addressToUpdate.City = address.City;
            addressToUpdate.State = address.State;
            addressToUpdate.Zip = address.Zip;                           
        }
        else
        {
            //perform an insert
            var newAddress = new Address
            {
                NameID = model.ID,
                Address1 = address.Address1,
                City = address.City,
                State = address.State,
                Zip = address.Zip
            };
            db.Addresses.Add(newAddress);
        }
    }

您试图做的是提交一个可变长度列表,这不是 asp mvc 可以自行处理的。让我解释一下:

您有一个表单,表单内部是一个 foreach 循环,它为多条记录创建可编辑的字段。 ASP MVC 可以使用其表单助手处理更新单个记录,但是当您有多个记录需要更新时,ASP MVC 不会自动为每个记录创建索引号,这使得它它不可能跟踪哪个 属性 属于哪个记录。当您提交表单时,MVC 不会为您创建此索引,因此您需要找到替代解决方案。

我强烈建议您查看此博客并合并为实现表单提交而提供的帮助程序:

http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/

首先让我从命名约定开始。

这个:

public int ADDRESS_ID { get; set; }
public int NameID { get; set; }

Is BAD 你根本没有命名约定,有些属性是 PascalCase,有些是带下划线的大写字母。我强烈建议您安装一些工具来强制您应用一组样式和一致性规则(例如 StyleCop)。一般来说,属性使用 PascalCase 是很常见的。

应用后,您的模型将如下所示:

public partial class Address
{
    public int AddressId { get; set; }
    public int NameId { get; set; }
    public string Address1 { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string Zip { get; set; }

    public virtual Name Name { get; set; }
}

第二件事: 如果我理解正确,您正在尝试为一个用户编辑数据: 他(或她)的名字和姓氏、地址列表和一些电子邮件。如果我是对的,那么您的 View 和 ViewModel 都是错误的。您的 ViewModel 可能如下所示:

public class ContactFormViewModel
{
    public int NameId { get; set; } 
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public IList<Address> Addresses { get; set; }      
    public IList<Emails> { get; set; }
}

控制器(已更新):

// GET: Names/Edit/5
//The GET method takes the id from the URL and passes it into the query to 

//return data for the specific record
    public ActionResult Edit(int id)
    {
        //You don't need the joins since you have navigation properies!
        var name = db.Names.FirstOrDefault(n => n.ID == id);
        ContactFormViewModel model;
        if(name == default(Name))
        {
            model = new ContactFormViewModel{
                 Addresses = new List<Address>(),
                 Emails = new List<Email>()
            };
        }
        else {
            model = new ContactFormViewModel
            {
                     NameId = name.NameId ,
                     FirstName = name.FirstName,
                     LastName = name.LastName ,
                     Addresses = name.Addresses.ToList(),
                     Emails = name.Emails.ToList(),
             };
        } 
        if(!model.Addresses.Any())
        {
            model.Addresses.Add(new Address());
        }
        if(!model.Emails.Any())
        {
            model.Emails.Add(new Email());
        }
        //Returns the query to the view
        return View(model);
    }

查看:

    @model MDTestApplication.ViewModel.ContactFormViewModel

    @{
        ViewBag.Title = "Edit";
    }

    <h2>Edit</h2>

    @using (Html.BeginForm())
    {
        @Html.AntiForgeryToken()

        <div class="form-horizontal">
            <h4>Name</h4>
            <hr />


             @Html.ValidationSummary(true, "", new { @class = "text-danger" })
             @Html.HiddenFor(model => model.NameId)

                <div class="form-group">
                    @Html.LabelFor(model => model.FirstName, htmlAttributes: new { @class = "control-label col-md-2" })             
                    <div class="col-md-10">
                       @Html.EditorFor(model => model.FirstName, new { @class = "FirstName" })
                    </div>          
                </div>

                <div class="form-group">
                    @Html.LabelFor(model => model.LastName, htmlAttributes: new { @class = "control-label col-md-2" })              
                    <div class="col-md-10">
                       @Html.EditorFor(model => item.LastName, new { @class = "FirstName" })
                    </div>          
                </div>
                @for (int i = 0; i < Model.Addresses.Count; i++)
                {
                    @Html.HiddenFor(model => model.Addresses[i].AddressId)
                    <div class="form-group">
                        @Html.LabelFor(model => model.Addresses[i].Address1, htmlAttributes: new { @class = "control-label col-md-2" })             
                        <div class="col-md-10">
                            @Html.EditorFor(model => model.Addresses[i].Address1, new { @class = "FirstName" })
                        </div>          
                    </div>
                    <div class="form-group">
                        @Html.LabelFor(model => model.Addresses[i].City, htmlAttributes: new { @class = "control-label col-md-2" })             
                        <div class="col-md-10">
                            @Html.EditorFor(model => model.Addresses[i].City, new { @class = "FirstName" })
                        </div>          
                    </div>
                    /// Continue here with all the address properties
                }

                @for (int i = 0; i < Model.Emails.Count; i++)
                {
                    @Html.HiddenFor(model => model.Emails[i].EmailId)
                    <div class="form-group">
                        @Html.LabelFor(model => model.Emails[i].Email, htmlAttributes: new { @class = "control-label col-md-2" })               
                        <div class="col-md-10">
                            @Html.EditorFor(model => model.Emails[i].Email, new { @class = "FirstName" })
                        </div>          
                    </div>              
                }
            <div class="form-group">
                <div class="col-md-offset-2 col-md-10">
                    <input type="submit" value="Save" class="btn btn-default" />
                </div>
            </div>
        </div>
    }

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

在控制器中编辑操作:

    public ActionResult Edit(ContactFormViewModel model)
    {   
        if (ModelState.IsValid)
        {
            //Query the database for the row to be updated. 
            var name = db.Names.FirstOrDefault( n => n.NameId == model.NameId);
            if(name != default(Name))
            {
                name.FirstName = model.FirstName;
                name.LastName = model.LastName;
                bool hasAddresses = name.Addresses.Any();
                foreach(var address in model.Addresses)
                {
                    var addressToUpdate = name.Addresses.FirstOrDefault(a => a.AddressId == address.AddressId);
                    if(addressToUpdate != default(Address))
                    {
                       // preform an update
                    }
                    else
                    {
                       //perform an insert
                    }
                }

                foreach(var email in model.Emails)
                {
                    var emailToUpdate = name.Emails.FirstOrDefault(a => a.EmailId == email.EmailId);
                    if(emailToUpdate != default(Email))
                    {
                       // preform an update
                    }
                    else
                    {
                       //perform an insert
                    }
                }
                db.SaveChanges();
            }
        }
    }