UNIT TEST: THE EDIT ACTION METHOD

Một phần của tài liệu Giáo trình lập trình ASP.NET Apress pro ASP NET MVC3 framework pre release (Trang 236 - 244)

We want to test for two behaviors in the Edit action method. The first is that we get the product we ask for when we provide a valid ID value. Obviously, we want to make sure that we are editing the product we expected. The second behavior is that we don’t get any product at all when we request an ID value that is not in the repository. Here are the test methods:

[TestMethod]

public void Can_Edit_Product() { // Arrange - create the mock repository

Mock<IProductRepository> mock = new Mock<IProductRepository>();

mock.Setup(m => m.Products).Returns(new Product[] { new Product {ProductID = 1, Name = "P1"}, new Product {ProductID = 2, Name = "P2"}, new Product {ProductID = 3, Name = "P3"},

}.AsQueryable());

// Arrange - create the controller

AdminController target = new AdminController(mock.Object);

// Act

Product p1 = target.Edit(1).ViewData.Model as Product;

Product p2 = target.Edit(2).ViewData.Model as Product;

Product p3 = target.Edit(3).ViewData.Model as Product;

// Assert

Assert.AreEqual(1, p1.ProductID);

Assert.AreEqual(2, p2.ProductID);

Assert.AreEqual(3, p3.ProductID);

}

[TestMethod]

public void Cannot_Edit_Nonexistent_Product() { // Arrange - create the mock repository

Mock<IProductRepository> mock = new Mock<IProductRepository>();

mock.Setup(m => m.Products).Returns(new Product[] { new Product {ProductID = 1, Name = "P1"}, new Product {ProductID = 2, Name = "P2"}, new Product {ProductID = 3, Name = "P3"}, }.AsQueryable());

// Arrange - create the controller

AdminController target = new AdminController(mock.Object);

// Act

Product result = (Product)target.Edit(4).ViewData.Model;

// Assert

Assert.IsNull(result);

}

Creating the Edit View

Now that we have an action method, we can create a view for it to render. Right-click in the Edit action method and select Add View. Leave the view name as Edit, check the option for a strongly typed view, and ensure that the Product class is selected as the model class, as shown in Figure 9-8.

Figure 9-8. Creating the Edit view

There is a scaffold view for the Edit CRUD operation, which you can select if you are interested in seeing what Visual Studio creates. We will use our own markup again, so we have selected Empty from the list of scaffold options. Don’t forget to check the option to apply a layout to the view and select _AdminLayout.cshtml as the view to use. Click the Add button to create the view, which will be placed in the Views/Admin folder. Edit the view so that the content matches Listing 9-9.

9. Listing 9-9. The Edit View

@model SportsStore.Domain.Entities.Product

@{

ViewBag.Title = "Admin: Edit " + @Model.Name;

Layout = "~/Views/Shared/_AdminLayout.cshtml";

}

<h1>Edit @Model.Name</h1>

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

<input type="submit" value="Save" />

@Html.ActionLink("Cancel and return to List", "Index") }

Instead of writing out markup for each of the labels and inputs by hand, we have called the

Html.EditorForModel helper method. This method asks the MVC Framework to create the editing interface for us, which it does by inspecting the model type—in this case, the Product class.

To see the page that is generated from the Edit view, run the application and navigate to /Admin/Index. Click one of the product names, and you will see the page shown in Figure 9-9.

Figure 9-9. The page generated using the EditorForModel helper method

Let’s be honest—the EditorForModel method is convenient, but it doesn’t produce the most attractive results. In addition, we don’t want the administrator to be able to see or edit the ProductID attribute, and the text box for the Description property is far too small.

We can give the MVC Framework directions about how to create editors for properties by using model metadata,. This allows us to apply attributes to the properties of the new model class to influence the output of the Html.EditorForModel method. Listing 9-10 shows how to use metadata on the Product class in the

SportsStore.Domain project.

10. Listing 9-10. Using Model Metadata

using System.ComponentModel.DataAnnotations;

using System.Web.Mvc;

namespace SportsStore.Domain.Entities { public class Product {

[HiddenInput(DisplayValue=false)]

public int ProductID { get; set; } public string Name { get; set; } [DataType(DataType.MultilineText)]

public string Description { get; set; } public decimal Price { get; set; } public string Category { get; set; } }

}

The HiddenInput attribute tells the MVC Framework to render the property as a hidden form element, and the DataType attribute allows us to specify how a value is presented and edited. In this case, we have selected the MultilineText option. The HiddenInput attribute is part of the System.Web.Mvc namespace, which means that we must add a reference to the System.Web.Mvc assembly in the SportsStore.Domain project. The other attributes are contained in the System.ComponentModel.DataAnnotations namespace, whose containing assembly is included in an MVC application project by default.

Figure 9-10 shows the Edit page once the metadata has been applied. You can no longer see or edit the ProductId property, and you have a multiline text box for entering the description. However, the UI still looks pretty poor.

Figure 9-10. The effect of applying metadata

We can make some simple improvements using CSS. When the MVC Framework creates the input fields for each property, it assigns different CSS classes to them. When you look at the source for the page shown in Figure 9- 10, you can see that the textarea element that has been created for the product description has been assigned the "text- box-multi-line" CSS class:

...

<div class="editor-field">

<textarea class="text-box multi-line" id="Description" name="Description">...description text...</textarea>

...

To improve the appearance of the Edit view, add the styles shown in Listing 9-11 to the Admin.css file in the Content folder of the SportsStore.WebUI project.

11. Listing 9-11. CSS Styles for the Editor Elements .editor-field { margin-bottom: .8em; }

.editor-label { font-weight: bold; } .editor-label:after { content: ":" } .text-box { width: 25em; }

.multi-line { height: 5em; font-family: Segoe UI, Verdana; } Figure 9-11 shows the effect these styles have on the Edit view.

Figure 9-11. Applying CSS to the editor elements

The rendered view is still pretty basic, but it is functional and will do for our administration needs.

As you saw in this example, the page a template view helper like EditorForModel creates won’t always meet your requirements. We’ll discuss using and customizing template view helpers in detail in Chapter 16.

Updating the Product Repository

Before we can process edits, we need to enhance the product repository so that we can save changes. First, we will add a new method to the IProductRepository interface, as shown in Listing 9-12.

12. Listing 9-12. Adding a Method to the Repository Interface using System.Linq;

using SportsStore.Domain.Entities;

namespace SportsStore.Domain.Abstract { public interface IProductRepository {

IQueryable<Product> Products { get; } void SaveProduct(Product product);

} }

We can then add this method to our Entity Framework implementation of the repository, the EFProductRepository class, as shown in Listing 9-13.

13. Listing 9-13. Implementing the SaveProduct Method using System.Linq;

using SportsStore.Domain.Abstract;

using SportsStore.Domain.Entities;

namespace SportsStore.Domain.Concrete {

public class EFProductRepository : IProductRepository { private EFDbContext context = new EFDbContext();

public IQueryable<Product> Products { get { return context.Products; } }

public void SaveProduct(Product product) { if (product.ProductID == 0) {

context.Products.Add(product);

}

context.SaveChanges();

} } }

The implementation of the SaveChanges method adds a product to the repository if the ProductID is 0;

otherwise, it applies any changes to the existing product.

Handling Edit POST Requests

At this point, we are ready to implement an overload of the Edit action method that will handle POST requests when the administrator clicks the Save button. The new method is shown in Listing 9-14.

14. Listing 9-14. Adding the POST-Handling Edit Action Method [HttpPost]

public ActionResult Edit(Product product) { if (ModelState.IsValid) {

repository.SaveProduct(product);

TempData["message"] = string.Format("{0} has been saved", product.Name);

return RedirectToAction("Index");

} else {

// there is something wrong with the data values return View(product);

} }

We check that the model binder has been able to validate the data submitted to the user. If everything is OK, we save the changes to the repository, and then invoke the Index action method to return the user to the list of products.

If there is a problem with the data, we render the Edit view again so that the user can make corrections.

After we have saved the changes in the repository, we store a message using the Temp Data feature. This is a key/value dictionary, similar to the session data and View Bag features we have used previously. The key difference is that TempData is deleted at the end of the HTTP request.

Notice that we return the ActionResult type from the Edit method. We’ve been using the ViewResult type until now. ViewResult is derived from ActionResult, and it is used when you want the framework to render a view.

However, other types of ActionResults are available, and one of them is returned by the RedirectToAction method.

We use that in the Edit action method to invoke the Index action method.

We can’t use ViewBag in this situation because the user is being redirected. ViewBag passes data between the controller and view, and it can’t hold data for longer than the current HTTP request. We could have used the session data feature, but then the message would be persistent until we explicitly removed it, which we would rather not have to do. So, the Temp Data feature is the perfect fit. The data is restricted to a single user’s session (so that users don’t see each other’s TempData) and will persist until we have read it. We will read the data in the view rendered by the action method to which we have redirected the user.

Một phần của tài liệu Giáo trình lập trình ASP.NET Apress pro ASP NET MVC3 framework pre release (Trang 236 - 244)

Tải bản đầy đủ (PDF)

(603 trang)