Compare commits

..

18 Commits

Author SHA1 Message Date
f5fb773395 11.1.4.6 Adding Model Validation; Listing 11-14. Adding Validation Messages to the Edit.cshtml File 2021-09-23 19:29:36 +10:00
1e70fef3a7 11.1.4.6 Adding Model Validation; Listing 11-13.  Applying Validation Attributes to the Product.cs File 2021-09-23 19:27:50 +10:00
9afd2e2cfd 11.1.4.5 Displaying a Confirmation Message; Listing 11-12. Handling the ViewBag Message in the _AdminLayout.cshtml Fil 2021-09-23 19:27:08 +10:00
c0b4f638f9 11.1.4.4 Handling Edit POST Requests; UNIT TEST: EDIT SUBMISSIONS 2021-09-23 16:46:45 +10:00
57a8f62c14 11.1.4.4 Handling Edit POST Requests; Listing 11-11. Adding the POST-Handling Edit Action Method in the AdminController.cs File 2021-09-23 16:41:28 +10:00
610a360fb2 11.1.4.3 Updating the Product Repository; Listing 11-10. Implementing the SaveProduct Method in the EFProductRepository.cs File 2021-09-23 16:37:27 +10:00
85f354b5ba 11.1.4.3 Updating the Product Repository; Listing 11-9. Adding a Method to the IProductRespository.cs File 2021-09-23 16:31:52 +10:00
2047c92b96 11.1.4.2 Creating the Edit View; Listing 11-8 Updating the Edit.cshtml File 2021-09-23 16:29:18 +10:00
911ab9f5c8 11.1.4.2 Creating the Edit View; Listing 11-7 Using Model Metadata in the Product.cs File 2021-09-23 12:54:55 +10:00
436f78cf71 Listing 11-6 The Coontents of the Edit.cshtml File (Edited) 2021-09-23 12:54:24 +10:00
f1cdbf375b 11.1.4.2 Creating the Edit View; Listing 11-6 The Contents of the Edit.cshtml File 2021-09-23 12:48:39 +10:00
35c520e1ed 11.1.4.1 Creating the Edit Action Method; UNIT TEST: THE EDIT ACTION METHOD 2021-09-23 12:05:39 +10:00
d33320ce73 11.1.4.1 Creating the Edit Action Method; Listing 11-5 Adding the Edit Action Method in the AdminController.cs File 2021-09-23 12:00:31 +10:00
8c22c6e26a 11.1.3 Implementing the List View; Listing 11-4 Modifying the Index.cshtml View 2021-09-23 11:54:48 +10:00
6801b717df 11.1.3 Implementing the List View; Listing 11-3 The Contents of the Views/Admin/Index.cshmtl File 2021-09-23 11:20:56 +10:00
8318883c0a 11.1.2 Creating a New Layout; Listing 11-2 The Contents of the _AdminLayout.cshtml File 2021-09-23 11:11:02 +10:00
de9d07f75f 11.1.1 Creating a CRUD Controller; UNIT TEST: THE INDEX ACTION 2021-09-23 11:05:52 +10:00
6e6ae67b21 11.1.1 Creating a CRUD Controller; Listing 11-1 The Contents of the AdminController.cs File 2021-09-23 10:53:58 +10:00
10 changed files with 335 additions and 0 deletions

View File

@ -10,5 +10,6 @@ namespace SportsStore.Domain.Abstract
public interface IProductRepository
{
IEnumerable<Product> Products { get; }
void SaveProduct(Product product);
}
}

View File

@ -12,5 +12,25 @@ namespace SportsStore.Domain.Concrete
{
get { return context.Products; }
}
public void SaveProduct(Product product)
{
if (product.ProductID == 0)
{
context.Products.Add(product);
}
else
{
Product dbEntry = context.Products.Find(product.ProductID);
if (dbEntry != null)
{
dbEntry.Name = product.Name;
dbEntry.Description = product.Description;
dbEntry.Price = product.Price;
dbEntry.Category = product.Category;
}
}
context.SaveChanges();
}
}
}

View File

@ -1,17 +1,30 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web.Mvc;
namespace SportsStore.Domain.Entities
{
public class Product
{
[HiddenInput(DisplayValue = false)]
public int ProductID { get; set; }
[Required(ErrorMessage = "Please enter a product name")]
public string Name { get; set; }
[DataType(DataType.MultilineText)]
[Required(ErrorMessage = "Please enter a description")]
public string Description { get; set; }
[Required]
[Range(0.01, double.MaxValue, ErrorMessage = "Please enter a positive price")]
public decimal Price { get; set; }
[Required(ErrorMessage = "Please specify a category")]
public string Category { get; set; }
}
}

View File

@ -0,0 +1,130 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SportsStore.Domain.Abstract;
using SportsStore.Domain.Entities;
using SportsStore.WebUI.Controllers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
namespace SportsStore.UnitTests
{
[TestClass]
public class AdminTests
{
[TestMethod]
public void Index_Contains_All_Products()
{
// 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"},
});
// Arrange - create a controller
AdminController target = new AdminController(mock.Object);
// Action
Product[] result = ((IEnumerable<Product>)target.Index().ViewData.Model).ToArray();
// Assert
Assert.AreEqual(result.Length, 3);
Assert.AreEqual("P1", result[0].Name);
Assert.AreEqual("P2", result[1].Name);
Assert.AreEqual("P3", result[2].Name);
}
[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"},
});
// 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"},
});
// Arrange - create the controller
AdminController target = new AdminController(mock.Object);
// Act
Product result = (Product)target.Edit(4).ViewData.Model;
// Assert
Assert.IsNull(result);
}
[TestMethod]
public void Can_Save_Valid_Changes()
{
// Arrange - create mock repository
Mock<IProductRepository> mock = new Mock<IProductRepository>();
// Arrange - create the controller
AdminController target = new AdminController(mock.Object);
// Arrange - create a product
Product product = new Product { Name = "Test" };
// Act - try to save the product
ActionResult result = target.Edit(product);
// Assert - check that the repository was called
mock.Verify(m => m.SaveProduct(product));
// Assert - check the method result type
Assert.IsNotInstanceOfType(result, typeof(ViewResult));
}
[TestMethod]
public void Cannot_Save_Invalid_Changes()
{
// Arrange - create mock repository
Mock<IProductRepository> mock = new Mock<IProductRepository>();
// Arrange - create the controller
AdminController target = new AdminController(mock.Object);
// Arrange - create a product
Product product = new Product { Name = "Test" };
// Arrange - add an error to the model state
target.ModelState.AddModelError("error", "error");
// Act - try to save the product
ActionResult result = target.Edit(product);
// Assert - check that the repository was not called
mock.Verify(m => m.SaveProduct(It.IsAny<Product>()), Times.Never());
// Assert - check the method result type
Assert.IsInstanceOfType(result, typeof(ViewResult));
}
}
}

View File

@ -87,6 +87,7 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="AdminTests.cs" />
<Compile Include="App_Start\NinjectWebCommon.cs" />
<Compile Include="UnitTest1.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />

View File

@ -0,0 +1,49 @@
using SportsStore.Domain.Abstract;
using SportsStore.Domain.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace SportsStore.WebUI.Controllers
{
public class AdminController : Controller
{
private IProductRepository repository;
public AdminController(IProductRepository repo)
{
repository = repo;
}
// GET: Admin
public ViewResult Index()
{
return View(repository.Products);
}
public ViewResult Edit(int productId)
{
Product product = repository.Products
.FirstOrDefault(p => p.ProductID == productId);
return View(product);
}
[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);
}
}
}
}

View File

@ -131,6 +131,7 @@
<ItemGroup>
<Compile Include="App_Start\NinjectWebCommon.cs" />
<Compile Include="App_Start\RouteConfig.cs" />
<Compile Include="Controllers\AdminController.cs" />
<Compile Include="Controllers\CartController.cs" />
<Compile Include="Controllers\NavController.cs" />
<Compile Include="Controllers\ProductController.cs" />
@ -158,6 +159,9 @@
<Content Include="Views\Cart\Summary.cshtml" />
<Content Include="Views\Cart\Checkout.cshtml" />
<Content Include="Views\Cart\Completed.cshtml" />
<Content Include="Views\Shared\_AdminLayout.cshtml" />
<Content Include="Views\Admin\Index.cshtml" />
<Content Include="Views\Admin\Edit.cshtml" />
<None Include="Web.Debug.config">
<DependentUpon>Web.config</DependentUpon>
</None>

View File

@ -0,0 +1,46 @@
@model SportsStore.Domain.Entities.Product
@{
ViewBag.Title = "Admin: Edit" + Model.Name;
Layout = "~/Views/Shared/_AdminLayout.cshtml";
}
<div class="panel">
<div class="panel-heading">
<h3>Edit @Model.Name</h3>
</div>
@using (Html.BeginForm())
{
<div class="panel-body">
@Html.HiddenFor(m => m.ProductID)
@foreach (var property in ViewData.ModelMetadata.Properties)
{
if (property.PropertyName != "ProductID")
{
<div class="form-group">
<label>@(property.DisplayName ?? property.PropertyName)</label>
@if (property.PropertyName == "Description")
{
@Html.TextArea(property.PropertyName, null,
new { @class = "form-control", rows = 5 })
}
else
{
@Html.TextBox(property.PropertyName, null,
new { @class = "form-control" })
}
@Html.ValidationMessage(property.PropertyName)
</div>
}
}
</div>
<div class="panel-footer">
<input type="submit" value="Save" class="btn btn-primary" />
@Html.ActionLink("Cancel and return to List", "Index", null,
new { @class = "btn btn-default" })
</div>
}
</div>

View File

@ -0,0 +1,47 @@
@model IEnumerable<SportsStore.Domain.Entities.Product>
@{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_AdminLayout.cshtml";
}
<h2>Index</h2>
<p>
@Html.ActionLink("Create New", "Create")
</p>
<div class="panel panel-default">
<div class="panel-heading">
<h3>All Products</h3>
</div>
<div class="panel-body">
<table class="table table-striped table-condensed table-bordered">
<tr>
<th class="text-right">ID</th>
<th>Name</th>
<th class="text-right">Price</th>
<th class="text-center">Actions</th>
</tr>
@foreach (var item in Model)
{
<tr>
<td class="text-right">@item.ProductID</td>
<td>@Html.ActionLink(item.Name, "Edit", new { item.ProductID })</td>
<td class="text-right">@item.Price.ToString("c")</td>
<td class="text-center">
@using (Html.BeginForm("Delete", "Admin"))
{
@Html.Hidden("ProductID", item.ProductID)
<input type="submit"
class="btn btn-default btn-xs"
value="Delete" />
}
</td>
</tr>
}
</table>
</div>
<div class="panel-footer">
@Html.ActionLink("Add a new product", "Create", null, new { @class = "btn btn-default" })
</div>
</div>

View File

@ -0,0 +1,24 @@
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width"/>
<link href="~/Content/bootstrap.css" rel="stylesheet" />
<link href="~/Content/bootstrap-theme.css" rel="stylesheet" />
<link href="~/Content/ErrorStyles.css" rel="stylesheet" />
<title></title>
</head>
<body>
<div>
@if (TempData["message"] != null)
{
<div class="alert alert-success">@TempData["message"]</div>
}
@RenderBody()
</div>
</body>
</html>