从剃须刀网页迁移到ASP.NET MVC 5 - 模型绑定和表格

如果您已使用ASP.NET Razor Web页面框架构建了站点,则您可能希望在某些时候查看将它们迁移到ASP.NET MVC。本教程是一系列三个中的最后一系列,探讨了如何通过逐步逐步迁移到将WebMatrix面包店模板站点迁移到ASP.NET MVC 5.此系列中的先前教程查看了视图的角色和控制器,以及模型的数据访问和视图模型方面。最后部分涵盖模型结合并形成张贴。下载(c。24MB),具有已完成的应用程序 可在github上使用.

订购表格和邮件服务

这final two parts of the migration involve the creation of the order form and the mailing service. The order part of the system will require the following:

  • An Order class to represent an order from a customer
  • 用于订单表单的视图模型
  • An OrderController to prepare the order form view and to accept submitted orders
  • 包含订单表单的视图。
  • 订单提交成功时显示显示

Order class encapsulates details about an order.

namespace Bakery.Models
{
    public class Order
    {
        public Product Product { get; set; }
        public int Quantity { get; set; }
        public string ShippingAddress { get; set; }
        public string EmailAddress { get; set; }
    }
}

OrderformModel. is a view model that represents the order form UI:

using System.ComponentModel.DataAnnotations;

namespace Bakery.Models
{
    public class OrderformModel.
    {
        public int ProductId { get; set; }
        public int OrderQty { get; set; }
        [Required]
        public string OrderShipping { get; set; }
        [Required(ErrorMessage="You must provide an email address.")]
        public string OrderEmail { get; set; }
    }
}

从上一个视图模型的不同之处在于它包括验证元素。这是通过使用包括的 dataannotation. attributes. The OrderShipping property is decorated with the Required attribute as is the OrderEmail property. The OrderEmail property is also provided with a custom error message. The OrderShipping property makes do with the default error message for the Required attribute which is "This field is required". The validators work in much the same way as the Validation helpers in Web Pages: when used with Html helpers and the jQuery unobtrusive validation library, they provide client-side validation without any additional effort on your part. They also provide server-side validation.

模型绑定

这property names in the view model are designed to match the field names from the original form in the Bakery template. This is deliberate as it allows the application to take advantage of 模型绑定,这是一种过程,即在请求中传递的值与变量或对象相匹配,基于表单字段的名称属性和属性或变量名称。我会很快掩盖这有点细节。与此同时,我们需要一个新的路线定义。

这"Order Now" links in the home page point to /order/id_of_product. No route definition caters for this pattern so one needs to be added. In addition we want to make sure that only numbers are accepted for the {id} parameter. There are two ways to add this route definition. The first is to make another call to MapRoute in the app_start \ rateconfig.cs. file like so:

routes.MapRoute(
    "Order",
    "order/{id}",
    new { controller = "Order", action = "Index" },
    constraints: new { id = "\\d+" }
    );

这anonymous type passed in to the constraints parameter contains a regular expression that constrains the parameter to numbers only. There is nothing wrong with this approach, but MVC 5 introduced a new option: 基于属性的路由, where routes are defined using attributes on controller actions. Attribute-based routing is not enabled by default. You enable it by adding the highlighted line of code to the RegisterRoutes method:

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapMvcAttributeRoutes();

这next step is to add the OrderController class. Right click on the 控制器 文件夹并选择 添加»控制器

迁移到MVC

选择 MVC 5控制器 - 空 from the options

迁移到MVC

命名课程 OrderController.cs.

迁移到MVC

用以下替换默认代码:

using Bakery.Models;
using Bakery.Services;
using System.Web.Mvc;

namespace Bakery.Controllers
{
    public class OrderController : Controller
    {
        //
        // GET: /Order/
        [Route("order/{id:int}")]
        public ActionResult Index(int id)
        {
            ViewBag.Title = "Place Your Order";
            IProductsService service = new ProductsService();
            OrderformModel. model = new OrderformModel. {
                Product = service.GetProduct(id)
            };
            return View(model);
        }
    }
}

这route is defined in the Route attribute place just before the Index method. The contstraint is specified in the curly braces. When you click on an 现在下单 link, the id of the product you want to order is passed in to the Index method as a parameter. The method creates a new OrderformModel. instance and sets the value of its Product property to the product represented by the id value passed in. This is obtained from the database by the ProductService. The OrderformModel. instance is passed to the View, which needs to be created next. A quick way to do this is to right click in the Index method in the controller and choose 添增视图...

迁移到MVC

选择 空的 从模板选择和 OrderformModel. 从模型选择:

迁移到MVC

这确保了视图被强烈打字。现在,您可以复制和粘贴HTML和剃刀(顶部代码块下方的部分) ORDER.CS.html. 文件在WebMatrix面包店站点中,使少数突出显示的改变如下所示:

@model Bakery.Models.OrderformModel.

<ol id="orderProcess">
    <li><span class="step-number">1</span>选择 Item</li>
    <li class="current"><span class="step-number">2</span>Details &amp; Submit</li>
    <li><span class="step-number">3</span>Receipt</li>
</ol>
<h1>Place Your Order: @Model.Product.Name</h1>
<form action="" method="post">
    @Html.ValidationSummary()

    <fieldset class="no-legend">
        <legend>Place Your Order</legend>
        <img class="product-image order-image" src="~/Images/Products/Thumbnails/@Model.Product.ImageName" alt="Image of @Model.Product.Name" />
        <ul class="orderPageList" data-role="listview">
            <li>
                <div>
                    <p class="description">@Model.Product.Description</p>
                </div>
            </li>
            <li class="email">
                <div class="fieldcontainer" data-role="fieldcontain">
                    <label for="orderEmail">Your Email Address</label>
                    @Html.TextBoxFor(m => m.OrderEmail)
                    <div>@Html.ValidationMessageFor(m => m.OrderEmail)</div>
                </div>
            </li>
            <li class="shipping">
                <div class="fieldcontainer" data-role="fieldcontain">
                    <label for="orderShipping">Shipping Address</label>
                    @Html.TextAreaFor(m => m.OrderShipping, new { rows = 4 })
                    <div>@Html.ValidationMessageFor(m => m.OrderShipping)</div>
                </div>
            </li>
            <li class="quantity">
                <div class="fieldcontainer" data-role="fieldcontain">
                    <label for="orderQty">Quantity</label>
                    <input type="text" id="orderQty" name="orderQty" value="1" />
                    x
                    <span id="orderPrice">@string.Format("{0:f}", Model.Product.Price)</span>
                    =
                    <span id="orderTotal">@string.Format("{0:f}", Model.Product.Price)</span>
                </div>
            </li>
        </ul>
        <p class="actions">
            <input type="hidden" name="Product.Id" value="@Model.Product.Id" />
            <input type="submit" value="Place Order" data-role="none" data-inline="true" />
        </p>
    </fieldset>
</form>

@section Scripts {
    <script src="~/Scripts/jquery.validate.min.js"></script>
    <script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>

    <script type="text/javascript">
        $(function () {
            var price = parseFloat($("#orderPrice").text()).toFixed(2),
                total = $("#orderTotal"),
                orderQty = $("#orderQty");

            orderQty.change(function () {
                var quantity = parseInt(orderQty.val());
                if (!quantity || quantity < 1) {
                    orderQty.val(1);
                    quantity = 1;
                } else if (quantity.toString() !== orderQty.val()) {
                    orderQty.val(quantity);
                }
                total.text("$" + (price * quantity).toFixed(2));
            });
        });
    </script>
}

这changes bring in the MVC Html form helpers and the MVC version of the validation helper. Each of the helpers look similar to their Web Pages counterpart, except that they have 为了 在他们的名字结束时: textareafor, ValidationMessagefor. 等等。这些是强大的助手和与模型一起工作。

迁移到MVC

When the form is submitted, an email is generated and sent to the user. The next stage is to create a service that takes care of this. The service will only have one method to start - SendOrderConfirmation. It will take an Order object as a parameter. Add a new class to the 楷模 folder and name it Order.cs。然后放入以下代码:

namespace Bakery.Models
{
    public class Order
    {
        public Product Product { get; set; }
        public int Quantity { get; set; }
        public string ShippingAddress { get; set; }
        public string EmailAddress { get; set; }
    }
}

添加新接口 服务 folder like you did for the ProductService. Name this one imailservice.cs. 并用以下内容替换代码:

using Bakery.Models;

namespace Bakery.Services
{
    public interface IMailService
    {
        void SendOrderConfirmation(Order order);
    }
}

将类文件添加到 服务 文件夹,名称它 mailservice.cs. 并用以下内容替换现有代码:

using Bakery.Models;
using System;
using System.Web.Helpers;

namespace Bakery.Services
{
    public class MailService : IMailService
    {
        public void SendOrderConfirmation(Order order) 
        {
            var body = "Thank you, we have received your order for " + order.Quantity + " unit(s) of " + order.Product.Name + "!<br/>";
            var orderShipping = order.ShippingAddress;
            var customerEmail = order.EmailAddress;

                //Replace carriage returns with HTML breaks for HTML mail
                var formattedOrder = orderShipping.Replace("\r\n", "<br/>");
                body += "Your address is: <br/>" + formattedOrder + "<br/>";
            
            body += "Your total is $" + (order.Product.Price * order.Product.Price) + ".<br/>";
            body += "We will contact you if we have questions about your order.  Thanks!<br/>";

            try {
                //SMTP Configuration for Hotmail
                WebMail.SmtpServer = "smtp.live.com";
                WebMail.SmtpPort = 25;
                WebMail.EnableSsl = true;

                //Enter your Hotmail credentials for UserName/Password and a "From" address for the e-mail
                WebMail.UserName = "";
                WebMail.Password = "";
                WebMail.From = "";
                WebMail.Send(to: customerEmail, subject: "Fourth Coffee - New Order", body: body);
            }
            catch (Exception) {
                // only placed here to allow app to run without configuring email
            }
        }
    }
}

这body of the SendOrderConfirmation method features code lifted straight out of the IsPost block at the top of the ORDER.CS.html. file in the WebMatrix Bakery template. The method accepts an object of type Order, which encapsulates details of the current order. Then it creates an email and takes care of sending it.

Before that can happen, something needs to create an instance of the MailService and pass and order to it. The OrderService will be responsible for that. Add another interface to the 服务 文件夹并命名它 iorderservice.cs.。修改文件的内容,以便它看起来像下面的代码。

using Bakery.Models;
namespace Bakery.Services
{
    public interface IOrderService
    {
        void ProcessOrder(Order order);
    }
}

Add another class file to the 服务 文件夹并命名它 OrderService.cs. Alter the code so that it matches the code in the next section:

using Bakery.Models;

namespace Bakery.Services
{
    public class OrderService : IOrderService 
    {
        public void ProcessOrder(Order order) 
        {
            IMailService service = new MailService();
            service.SendOrderConfirmation(order);
        }
    }
}

Now you have an OrderService which is based on an IOrderService interface containing one method that accepts an Order object, instantiates an instance of the MailService you created earlier and passes the Order object on to it.

Finally, add the following method to the OrderController:

[HttpPost]
[Route("order/{id:int}")]
public ActionResult Index(OrderformModel. model) 
{
    if (ModelState.IsValid) 
    {
        Order order = new Order {
            Product = model.Product,
            ShippingAddress = model.OrderShipping,
            EmailAddress = model.OrderEmail,
            Quantity = model.OrderQty
        };
        OrderService service = new OrderService();
        service.ProcessOrder(order);
        return View("Success");
    }
    else 
    {
        if (model.Product.Id > 0) 
        {
            ProductsService service = new ProductsService();
            model = new OrderformModel. {
                Product = service.GetProduct(model.Product.Id)
            };
            return View(model);
        }
        else 
        {
            return RedirectToRoute("Default");
        }
    }
}

这new Index method is decorated with the same route attribute as the existing method, but this one also features an additional attribute: HttpPost. In other words, this method has been marked to accept only POST requests (form submissions). This method also expects an OrderformModel. to be passed to it. Model Binding will examine the OrderformModel. type, and for each public property on the type, it will attempt to find a matching parameter in the Request object. It will apply the value of the matching request parameter to the property of the model variable. The ModelState.IsValid test is the MVC equivalent to the Web Pages Validation.IsValid() method. It tests the OrderformModel. to ensure that it meets the validation rules applied to it. The Order object is constructed from the validated view model and then passed to the OrderService.

笔记: At this stage you might be wondering why you had to basically clone the property values from the OrderformModel. object that came in with the request to an Order object that looks pretty similar, and then pass that to the OrderService. Why not alter the OrderService.ProcessOrder method to accept an OrderformModel. type instead and do without the Order class altogether? The answer to this is 关注点分离。查看模型(尽管是名称)是演示层的一部分,只能在控制器和视图之间旅行。您应该在模型中有一个视图模型 - 避名者围栏 - View Models不应被允许进入,并且由于服务是模型层的一部分,因此它们不能接受视图模型作为参数。

如果View模型失败验证,则再次检索产品详细信息并以任何验证错误一起重新显示。一旦订单成功处理,用户就会显示成功视图。视图的内容直接从WebMatrix站点抬起,如下所示。

<ol id="orderProcess">
    <li><span class="step-number">1</span>选择 Item</li>
    <li><span class="step-number">2</span>Details &amp; Submit</li>
    <li class="current"><span class="step-number">3</span>Receipt</li>
</ol>
<h1>Order Confirmation</h1>


<div class="message order-success">
    <h2>Thank you for your order!</h2>
    <p>We are processing your order and have sent a confirmation email. Thank you for your business and enjoy!</p>
</div>

这view itself differs from the previous views in that it doen't have a corresponding action method in the controller. It is added directly to the 查看\订单 folder.

概括

这一系列教程已经浏览了将剃刀网页网站迁移到ASP.NET MVC站点的过程。您已经了解了如何从现有的标记部分构建的视图 .cshtml. 文件,以及如何将页面顶部迁移到模型中的逻辑,并保持清晰的分隔符合不同的操作。您还获得了MVC中移动部件的基本介绍,并具有工作应用。应该注意的是,在本教程中采取的代码组织采取的方法只是一个开始。例如,您已被引入接口的概念,但本文尚未对其实际值进行展示。这可能形成另一天的话题。