【MVC 4】5.SportsSore —— 一個真實的應用程式

来源:http://www.cnblogs.com/yc-755909659/archive/2016/03/30/5322384.html
-Advertisement-
Play Games

作者:[美]Adam Freeman 來源:《精通ASP.NET MVC 4》 前面建立的都是簡單的MVC程式,現在到了吧所有事情綜合在一起,以建立一個簡單但真實的電子商務應用程式的時候了。 在此打算建立的應用程式 — SportsStore (體育用品商店),將遵循隨處可見的線上商店所採取的經典方 ...


 作者:[美]Adam Freeman      來源:精通ASP.NET MVC 4》

前面建立的都是簡單的MVC程式,現在到了吧所有事情綜合在一起,以建立一個簡單但真實的電子商務應用程式的時候了。

在此打算建立的應用程式 — SportsStore (體育用品商店),將遵循隨處可見的線上商店所採取的經典方式。將創建一個客戶可以通過分類和頁面進行瀏覽的線上產品分類,一個客戶可以添加和刪除商品的購物車,和一個客戶能夠輸入其右擊地址細節的結算頁面。另外,還將創建一個包含創建、讀取、更新和刪除功能的管理區,以便對產品分類進行管理並對該區域進行保護,以使只有登錄的管理員才能進行修改。

此書打算建立的這個應用程式不只是一個膚淺的演示,而是要創建一個堅固且真實的、符合當前最實用要求的應用程式。由於要建立必要的底層結構,一開始的進度會有點慢。的確,若使用 WebForm,則可以更快地建立最初的功能,只要拖放一些與資料庫直接綁定的空間即可。但在 MVC 應用程式中所付出的這些初期工作,會帶來可維護、可擴展以及結構良好的代碼,且這些代碼對單元測試具有卓越支持。一旦恰當地建好了這種基本的底層架構,後面的事情就會快起來了。

1.開始

1.1 創建 Visual Studio 解決方案和項目

本文打算創建一個含有三個項目的Visual Studio 解決方案,一個項目包含域模型,一個是MVC 應用程式,第三個則包含單元測試。首先用"空白解決方案"模板創建一個名為"SportsStore"的新的Visual Studio 解決方案。

Visual Studio 解決方案是一個含有一個或多個項目的容器。示例應用程式需要三個項目,如下圖所示:

 

1.2 添加引用

參考前面的博文 【MVC 4】3.MVC 基本工具(創建示例項目、使用 Ninject)  和 【MVC 4】4.MVC 基本工具(Visual Studio 的單元測試、使用Moq)  對庫和項目做好正確的引用。所需的項目依賴性如下圖所示:

 

1.3 設置DI容器

之前的文章 【MVC 4】3.MVC 基本工具(創建示例項目、使用 Ninject) 展示過如何使用 Ninject 創建一個自定義依賴性解析器,以便 MVC 框架用它創建整個應用程式實例化對象。這裡打算採用不同的方法,即創建一個自定義的控制器工廠。用戶可以在其中添加自定義代碼,以改變MVC框架的(預設)行為,或者像這裡所做的一樣,將DI 限制到應用程式的一部分。常用的模式是用依賴性解析器來處理 DI,而用自定義控制器工廠來改變查找控制器類的方式,但在此例中,只打算使用控制器工廠。

在 SportsStore.WebUI 項目中創建一個名稱"Infreastructure"的文件夾,然後創建一個名為"NinjectContrillerFactory (Ninject 控制器工廠)"的類。

using Ninject;
using System;
using System.Web.Mvc;
using System.Web.Routing;

namespace SportsStore.WebUI.Infrastructure
{
    public class NinjectControllerFactory : DefaultControllerFactory
    {
        private IKernel ninjectKernel;

        public NinjectControllerFactory()
        {
            ninjectKernel = new StandardKernel();
            AddBindings();
        }

        protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
        {
            return controllerType == null
                ? null
                : (IController)ninjectKernel.Get(controllerType);
        }

        private void AddBindings()
        {
            //put bindings here

        }
    }
}

 

雖然沒有添加任何綁定,但在需要時,可以使用AddBindings 方法。必須告訴 MVC 希望使用此 NinjectControllerFactory 類來創建控制器對象,其辦法是在 SportsStore.WebUI 項目中 Global.asax.cs 文件的Application_Start 方法中添加一些代碼,如下粗體部分所示:

using SportsStore.WebUI.Infrastructure;using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;

namespace SportsStore.WebUI
{
    // 註意: 有關啟用 IIS6 或 IIS7 經典模式的說明,
    // 請訪問 http://go.microsoft.com/?LinkId=9394801

    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);

            ControllerBuilder.Current.SetControllerFactory(new NinjectControllerFactory());
        }
    }
}

 

1.4 運行程式

運行程式,會看到一個錯誤頁面,這是因為所請求的 URL 是與 Ninject 尚未進行綁定的控制器相關聯的:

如果進行到這一步,說明 Visual Studio 2012 和ASP.NET MVC 開發環境的準備工作進行的十分順利。

 

2.從域模型開始

MVC 應用程式中有太多的事情都是圍繞域模型而展開的,因此,域模型是開始工作的最佳位置。

由於這是一個電子商務應用程式,因此需要的最明顯的域實體是產品(Product)。在SportsSore.Domain 項目中創建一個名為 "Entities" 的新文件夾,然後在其中創建一個名為“Product”的類。

namespace SportsStore.Domain.Entities
{
    public class Product
    {
        public int ProductID { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public decimal Price { get; set; }
        public string Category { get; set; }
    }
}

上述清單遵循了在一起獨立的 Visual Studio 項目中定義域模型的約定,即類必須標記為 public,雖然不一定要遵循這一約定,但這麼做有助於保持模型與控制器分離。

 

2.1 創建一個抽象的存儲庫

現在需要某種方式來獲取資料庫中的Product 實體。正如前面博文所解釋的,人們希望持久化邏輯與域模型實體是分離的——此事通過使用存儲庫模式來實現。此刻,不必擔心會如何實現持久化,不過,將從定義它的介面來開始這一過程。

在 SportsStore.Domain 項目中創建一個名為 Abstract 的頂層新文件夾,並創建一個名為 IProductRepository 的新介面,代碼如下:

using SportsStore.Domain.Entities;
using System.Linq;

namespace SportsStore.Domain.Abstract
{
    public interface IProductRepository
    {
        IQueryable<Product> Products { get; }
    }
}

該介面使用了 IQueryable<T> 介面,以便能夠獲得一系列 Product 對象,而不必說明數據如何存儲、存儲在哪兒,以及如何接收數據。使用這一 IProductRepository 介面的類,可以獲取 Product 對象而不必知道它們來自哪兒或如何遞交它們,這是存儲庫模式的本質。在添加特性的整個開發過程中,將重新審視這一介面。

 

2.2 創建模仿存儲庫

現在,已經定義了一個抽象介面,可以實現持久化機制,並將其掛接到一個資料庫。本文打算在後面部分再做這件事情。為了能夠開始編寫應用程式的其他部分,本文打算創建一個 IProductRepository 介面的模仿實現。本文打算在 SportsStore.WebUI 項目的 NinjectControllerFactory 類的 AddBindings 方法中做這件事,代碼如下:

using Moq;
using Ninject;
using SportsStore.Domain.Abstract;
using SportsStore.Domain.Entities;
using System;
using System.Linq;
using System.Collections.Generic;
using System.Web.Mvc;
using System.Web.Routing;

namespace SportsStore.WebUI.Infrastructure
{
    public class NinjectControllerFactory : DefaultControllerFactory
    {
        private IKernel ninjectKernel;

        public NinjectControllerFactory()
        {
            ninjectKernel = new StandardKernel();
            AddBindings();
        }

        protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
        {
            return controllerType == null
                ? null
                : (IController)ninjectKernel.Get(controllerType);
        }

        private void AddBindings()
        {
            //put bindings here
            Mock<IProductRepository> mock = new Mock<IProductRepository>();
            mock.Setup(m=>m.Products).Returns(new List<Product>{
                new Product{Name="Football",Price=25},
                new Product{Name="Surf board",Price=179},
                new Product{Name="Running shoes",Price=95}
            }.AsQueryable());
            ninjectKernel.Bind<IProductRepository>().ToConstant(mock.Object);
        }
    }
}

此處必須對該問卷添加一些命名空間,但用來創建模仿存儲庫實現的過程使用的是 【MVC 4】4.MVC 基本工具(Visual Studio 的單元測試、使用Moq) 所介紹的同樣的 Moq 技術。AsQueryable方法是一個 LINQ 擴展方法,它將 IEnumerable<T> 轉換成 IQueryable<T>,此處需要它來匹配介面簽名。

人們希望,Ninject 無論何時接收到一個 IProductRepository 介面實現的請求,都返回同樣的模仿對象,這便是使用 ToConstant 方法的原因

...
ninjectKernel.Bind<IProductRepository>().ToConstant(mock.Object);
...

Ninject 會一直以該模仿對象來滿足對 IProductRepository 介面的請求,而不是每次都創建一個新的實現對象實例。

 

3.顯示產品列表

本小結將創建一個控制器和一個動作方法,它能夠顯示存儲庫中的產品細節。此刻,將只只針對模仿存儲庫中的數據。

3.1 添加控制器

新建控制器"ProductController",模板為"空 MVC 控制器",修改代碼如下

using SportsStore.Domain.Abstract;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace SportsStore.WebUI.Controllers
{
    public class ProductController : Controller
    {
        private IProductRepository repository;

        public ProductController(IProductRepository productRepository)
        {
            this.repository = productRepository;
        }

        public ViewResult List()
        {
            return View(repository.Products);
        }
    }
}

 像這樣調用 View 方法(未指定視圖名稱),是告訴框架為該動作方法渲染一個預設視圖。通過將 Product 對象的列表傳遞給這個 View 方法,這是在給框架提供數據,以便用這些數據填充強類型視圖中的 Model 對象。

 

3.2 添加視圖

現在需要為 List 動作方法添加預設視圖。添加對應的視圖文件 List.cshtml ,並渲染視圖文件如下:

@model IEnumerable<SportsStore.Domain.Entities.Product>

@{
    ViewBag.Title = "Products";
}

@foreach (var p in Model) { 
<div class="item">
    <h3>@p.Name</h3>
    @p.Description
    <h4>@p.Price.ToString("c")</h4>
</div>
}

 

3.3 設置預設路由

現在要做的全部工作是告訴 MVC 框架,抵達網站根的請求應該被映射到 ProductController 類的List 動作方法上。這可以通過編輯 Global.asax.cs 的 RegisterRoutes 方法實現,代碼如下:

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

namespace SportsStore.WebUI
{
    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Product", action = "List", id = UrlParameter.Optional }
            );
        }
    }
}

 

3.4 運行應用程式

至此,所有基礎工作均已就緒。此刻已經有一個含有一個動作方法的控制器,該動作方法在預設 URL 被請求時被調用,它依賴於存儲庫介面的一個模仿實現,該存儲庫介面生成了一些簡單的測試數據。這些測試數據被傳遞給與動作方法關聯在一起的視圖,而視圖對每個產品創建一個簡單的細節列表。運行該應用程式,效果圖如下:

這是ASP.NET MVC 框架典型的開發模式。

 

4.準備資料庫

前面已經可以顯示含有產品細節的簡單視圖,但其顯示的知識模仿的 IProductRepository 所返回的測試數據。在可以顯示真實數據的存儲庫之前,還需要建立一個資料庫,並用一些數據填充它。

本文打算以 SQL Server 作為資料庫,並用 Entity Framework(實體框架 - EF)來訪問資料庫,EF 是 .NET 的ORM(對象關係映射)框架。ORM 框架讓開發人員可以用規則的C# 對象來使用關係資料庫的表、列和行。 LINQ 可以與不同的數據源一起工作,其中之一就是 Entity Framework 。

 

4.1 創建資料庫

補充到 Visual Studio 2012 和 SQL Server 2012 中的一個很好的特性是 LocalDB。它是特別為開發者而設計的一個免管理的SQL Server 核心功能實現。使用該特性使我們在建立項目、以及後面將資料庫部署到完整版的 SQL Server 期間,可以跳過資料庫的設置過程。

第一個步驟是在 Visual Studio 中創建資料庫連接。從"View(視圖)"菜單中打開"Database Explorer(資料庫資源管理器)"視窗,點擊”Connect to Database(連接到資料庫)“按鈕。

根據提示,登錄資料庫,並新建資料庫 SportsStore

 

4.2 定義資料庫方案

新建的資料庫只需要一個數據表,用以存儲 Product 數據。右擊資料庫對應的"Tables(表)"條目,新增數據表。

這將會顯示創建新表的設計器。使用 T-SQL 視窗,輸入對應的SQL語句創建數據表 Products 。

點擊左上角的"更新"按鈕,會看到該語句的效果摘要。

點擊"更新資料庫",以執行該 SQL 語句,併在資料庫中創建 Products 表。

 

4.3 向資料庫添加數據

本文打算對該資料庫手工添加一些數據。

在“資料庫資源管理器”視窗中,展開 SportsStore 資料庫的“表”條目,右擊 Products 表,選擇“顯示表數據”,然後輸入下圖所示數據。可以用 Tab 鍵逐行移動游標。在一行的最後按 Tab 鍵,將移到下一行並更新資料庫中的數據。

 

 

4.4 創建實體框架上下文

Entity Framework 的最新版包含了一個叫做“Code-first(代碼先行)”的很好的特性。其思想是可以先定義模型中的類,然後再通過這些類生成資料庫。

這很適合綠地(Green-field)開發項目,但這些項目並不多見。因此,本文打算演示下 Code-First 的一種變異,以此把模型類與現有的資料庫關聯在一起。

第一步,是將Entity Framework (此處是6.1版本)添加到 SportsStore.Domain 項目中。通過 管理NuGet 包,安裝最新的 Entity Framework 包。

 

下一個步驟是創建一個將前面建立的簡單模型與資料庫關聯起來的上下文類(Context Class)。

創建一個新文件夾 "Concrete",併在其中添加一個名為"EFDbContext"的新類。代碼如下:

using SportsStore.Domain.Entities;
using System.Data.Entity;

namespace SportsStore.Domain.Concrete
{
    public class EFDbContext:DbContext
    {
        public DbSet<Product> Products { get; set; }
    }
}

為了利用 Code-First 特性,需要創建一個派生於 System.Data.Entity.DbContext 的類。這個類會為用戶要使用的資料庫中的每個表自動地定義一個屬性。

該屬性指定了表名,並把 DbSet 結果的類型參數指定為實體框架用來表示表行的模型。在這個例子中,該屬性名是 Products(資料庫中的表名)。即,希望用 Product 模型類型來表示 Products 表的各個行。

需要告訴 Entity Framework 如何連接到資料庫,為了完成這一工作,只需要在 SportsStore.WebUI 項目的 Web.config 文件中以上下文類同樣的名字添加一條資料庫連接字元串即可,如下所示

  <connectionStrings>
    <add name="EFDbContext" connectionString="Data Source=(localdb)\v11.0;Initial Catalog=SportsStore;Integrated Security=True" 
providerName
="System.Data.SqlClient" /> </connectionStrings>

 

4.5 創建 Product 存儲庫

現在,本文已經做好了真正實現 IProductRepository 類所需要的各種準備。 在 SportsStore.Domain 項目的 Concrete 文件夾中添加一個類,取名"EFProductRepository",代碼如下:

using SportsStore.Domain.Abstract;
using SportsStore.Domain.Entities;
using System.Linq;

namespace SportsStore.Domain.Concrete
{
    public class EFProductRepository:IProductRepository
    {
        private EFDbContext context = new EFDbContext();
        public IQueryable<Product> Products {
            get { return context.Products; }
        }
    }
}

這就是存儲庫類,它實現了 IProductRepository 介面,並使用了一個 EFDbContext 實例,以便用 Entity Framework 接收資料庫的數據。在對該存儲庫添加特性時,便會看到此處是如何使用 Entity Framework 的。

最後一步是吧 Ninject 對模仿存儲庫的綁定替換為對實際存儲庫的綁定。編輯 SportsStore.WebUI 項目中的 NinjectControllerFactory 類,使 AddBindings 方法如下所示:

using Moq;
using Ninject;
using SportsStore.Domain.Abstract;
using SportsStore.Domain.Entities;
using System;
using System.Linq;
using System.Collections.Generic;
using System.Web.Mvc;
using System.Web.Routing;
using SportsStore.Domain.Concrete;

namespace SportsStore.WebUI.Infrastructure
{
    public class NinjectControllerFactory : DefaultControllerFactory
    {
        private IKernel ninjectKernel;

        public NinjectControllerFactory()
        {
            ninjectKernel = new StandardKernel();
            AddBindings();
        }

        protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
        {
            return controllerType == null
                ? null
                : (IController)ninjectKernel.Get(controllerType);
        }

        private void AddBindings()
        {
            //put bindings here
            ninjectKernel.Bind<IProductRepository>().To<EFProductRepository>();
        }
    }
}

新的綁定以粗體顯示,它告訴我 Ninject ,用戶希望創建 EFProductRepository 類的實例來對 IProductRepository 介面的請求進行服務。再次運行應用程式,效果如下:

 

註意:記得更新 SportsStore.WebUI 項目中 Entity Framework的版本,與 SportsStore.Domain 項目中的版本保持一致。不然報錯。

 

5.添加分頁

從上圖可以看出,資料庫中的所有產品都顯示在一個單一的頁面上。本小結將添加對分頁的支持,以便在一個頁面上顯示一定數目的產品,用戶可以逐頁查看整個產品分類。要實現這一點,可以在 Product 控制器中的 List 方法上添加一個參數,如下所示:

using SportsStore.Domain.Abstract;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace SportsStore.WebUI.Controllers
{
    public class ProductController : Controller
    {
        private IProductRepository repository;
        //指明用戶希望每頁顯示4個產品
        public int PageSize = 4;

        public ProductController(IProductRepository productRepository)
        {
            this.repository = productRepository;
        }

        public ViewResult List(int page=1)
        {
            //從存儲庫獲取 Product 對象,
            //按主鍵順序排序,略過起始頁之前出現的產品數,
            //然後取出由 PaeSize 欄位指定的產品個數
            return View(repository.Products.OrderBy(p=>p.ProductID).Skip((page-1)*PageSize).Take(PageSize));
        }
    }
}

 

5.1 顯示頁面鏈接

如果運行這個應用程式,將看到只有四個條目顯示在頁面上。如果想查看另一頁,可以把查詢字元串參數加到 URL 的末尾,如下所示:

http://localhost:64245/?page=2

需要修改 URL 的埠號,使之與正在運行的 ASP.NET 開發伺服器埠號匹配。運用這種查詢字元串,可以對整個產品分類進行導航。

而為了方便客戶。需要在每個產品列表的底部渲染一些頁面的鏈接,以使客戶可以在不同的頁面之間導航。為了達到這一目的,本文打算實現一個可重用的 HTML 輔助器方法,它類似於之前 【MVC 4】1.第一個 MVC 應用程式 中使用的 Html.TextBoxFor 和 Html.BeginForm 方法。該輔助器方法將為所需要的導航鏈接生成 HTNL 標記。

(1) 添加視圖模型

為了支持 HTML 輔助器方法,本文打算把可用頁面數、當前頁、已經存儲庫中產品總數等方面的信息傳遞給視圖。做這種事最容易的辦法是創建一個視圖模型,在 SportsStore.WebUI 文件夾 Models 中新建類文件 PagingInfo.cs ,代碼如下:

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

namespace SportsStore.WebUI.Models
{
    public class PagingInfo
    {
        public int TotalItems { get; set; }
        public int ItemsPerPage { get; set; }
        public int CurrentPage { get; set; }

        public int TotalPages
        {
            get { return (int)Math.Ceiling((decimal)TotalItems / ItemsPerPage); }
        }
    }
}

視圖模型並不是域模型的一部分,它只是一種便於在控制器與視圖之間傳遞數據的類。為了強調這一點,將這個類放在 SportsStore.WebUI 項目中,以使它與域模型的類分離開(將視圖模型放在 MVC 框架項目的 Models 文件夾,而不是放在類庫項目中,這種做法足以說明視圖模型不是域模型,明確了概念,也使應用程式的結構更清晰)。

 

(2)添加 HTML 輔助器方法

現在有了這個視圖模型,便可以實現這個 HTML 輔助器方法了,該方法稱為“PageLinks”。在 SportsStore.WebUI 項目中創建一個新文件夾“HtmlHelpers”,並添加一個新的靜態類“PagingHelpers(分頁輔助器)”。類文件的內容如下所示:

using SportsStore.WebUI.Models;
using System;
using System.Text;
using System.Web.Mvc;

namespace SportsStore.WebUI.HtmlHelpers
{
    public static class PagingHelpers
    {
        public static MvcHtmlString PageLinks(this HtmlHelper html, PagingInfo pagingInfo, Func<int, string> pageUrl)
        {
            StringBuilder result = new StringBuilder();
            for (int i = 1; i <= pagingInfo.TotalPages; i++)
            {
                TagBuilder tag = new TagBuilder("a"); // 構造一個<a>標簽
                tag.MergeAttribute("href", pageUrl(i));
                tag.InnerHtml = i.ToString();
                if (i == pagingInfo.CurrentPage)
                    tag.AddCssClass("selected");
                result.Append(tag.ToString());
            }
            return MvcHtmlString.Create(result.ToString());
        }
    }
}

這個 PageLinks 擴展方法使用 PagingInfo 對象中提供的信息生成一組頁面鏈接的 HTML 。Func 參數提供了在其中傳遞委托的能力,該委托用於生成查看其它頁面的鏈接。

 

只有包含擴展方法的命名空間在範圍內時,其中的擴展方法才是可用的。在一個代碼文件中,這是用 using 語句來完成的;但對於一個 Razor 視圖,必須把一個配置條目添加到 Web.config 文件中,或在這個視圖上添加一條 @using 語句 。容易混淆的是,在一個 Razor 的 MVC 項目中有兩個 Web.config 文件:主配置文件位於應用程式的根目錄,而視圖專用的配置文件位於 Views 文件夾。需要修改的是 Views/Web.config 文件,如下所示:

  <system.web.webPages.razor>
    <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
    <pages pageBaseType="System.Web.Mvc.WebViewPage">
      <namespaces>
        <add namespace="System.Web.Mvc" />
        <add namespace="System.Web.Mvc.Ajax" />
        <add namespace="System.Web.Mvc.Html" />
        <add namespace="System.Web.Optimization"/>
        <add namespace="System.Web.Routing" />
        <add namespace="SportsStore.WebUI.HtmlHelpers"/>
      </namespaces>
    </pages>
  </system.web.webPages.razor>

在一個 Razor 視圖中需要引用的每一個命名空間,都需要以這種方式進行聲明,或在視圖中用 @using 語句進行聲明。

 

(3)添加視圖模型視圖

目前還沒做好使用 HTML 輔助器方法的準備,還需要把這個 PagingInfo 視圖模型類的一個實例提供給視圖。可以用 View Bag (視圖包)特性來做這件事,但是一個更好的辦法是把控制器發送給視圖所有數據封裝成一個單一的視圖模型類。為此,需要把一個新的名為“ProductsListViewModel” 的類添加到 SportsStore.WebUI 的 Models 文件夾。

using SportsStore.Domain.Entities;
using System.Collections.Generic;

namespace SportsStore.WebUI.Models
{
    public class ProductsListViewModel
    {
        public IEnumerable<Product> Products { get; set; }
        public PagingInfo PagingInfo { get; set; }
    }
}

現在,可以更新 ProductController 類中的 List 方法,以便使用這個 ProductsListViewModel 類,給視圖提供在頁面上顯示的產品細節和分頁細節,修改後代碼如下:

using SportsStore.Domain.Abstract;
using SportsStore.WebUI.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace SportsStore.WebUI.Controllers
{
    public class ProductController : Controller
    {
        private IProductRepository repository;
        public int PageSize = 4;

        public ProductController(IProductRepository productRepository)
        {
            this.repository = productRepository;
        }

        public ViewResult List(int page = 1)
        {
            ProductsListViewModel model = new ProductsListViewModel
            {
                Products = repository.Products.OrderBy(p => p.ProductID).Skip((page - 1) * PageSize).Take(PageSize),
                PagingInfo = new PagingInfo
                {
                    CurrentPage = page,
                    ItemsPerPage = PageSize,
                    TotalItems = repository.Products.Count()
                }
            };
            return View(model);
        }
    }
}

這些修改將一個 ProductsListViewModel 對象作為模型數據傳遞給了視圖。

 

此時,視圖期望的是一個 Product 對象的序列,因此需要更新 List.cshtml ,以處理這個新視圖模型類型,修改後代碼如下:

@model SportsStore.WebUI.Models.ProductsListViewModel

@{
    ViewBag.Title = "Products";
}

@foreach (var p in Model.Products) { 
<div class="item">
    <h3>@p.Name</h3>
    @p.Description
    <h4>@p.Price.ToString("c")</h4>
</div>
}

 

這個例子修改了 @model 指示符,以告訴 Razor,現在正在使用一個不同的數據類型。也需要更新 foreach 迴圈,以使數據源是模型數據的 Products 屬性。

 

(4)顯示頁面鏈接

現在已經做到好了再 List 視圖上添加頁面鏈接的所有準備。前面已經創建了含有分頁信息的視圖模型,更新了控制器以使這個信息能夠傳遞給視圖,並修改了 @model 指示符以匹配新的視圖模型類。剩下的事是在視圖中調用這個 HTML 輔助器方法,修改視圖文件如下:

@model SportsStore.WebUI.Models.ProductsListViewModel

@{
    ViewBag.Title = "Products";
}

@foreach (var p in Model.Products)
{ 
    <div class="item">
        <h3>@p.Name</h3>
        @p.Description
        <h4>@p.Price.ToString("c")</h4>
    </div>
}

<div class="pager">
    @Html.PageLinks(Model.PagingInfo, x => Url.Action("List", new { page = x }));
</div>

運行該應用程式,可以看到已經添加了頁面鏈接,如下圖所示。這個鏈接的樣式仍然是很基本。重要的是這個鏈接能把客戶從一個頁面帶到另一個頁面,並瀏覽正在銷售的產品。

5.2 改進 URL

頁面鏈接雖然可以其作用,但它們使用的仍然是查詢字元串,以便將分頁信息伺服器,代碼如下:

http://localhost/?page=2

一個更好的方法是專門創建一種遵循可組合 URL 模式的方案。“可組合 URL ”是一種對用戶有意義的方式,其形式如下:

http://localhost/Page2

幸運的是,MVC 很容易修改 URL 方案,因為它使用了 ASP.NET 的路由特性。所要做的知識吧一條新路由添加到 Global.asax.cs 中的 RegisterRoutes 方法,如下所示:

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

namespace SportsStore.WebUI
{
    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
                name: null,
                url: "Page{page}",
                defaults: new { controller = "Product", action = "List" }
                );
routes.MapRoute( name:
"Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Product", action = "List", id = UrlParameter.Optional } ); } } }

重要的是吧這條路由加在 Default 路由之前。路由是按它們列出的順序進行處理的,這裡需要這條新路由優先於已經存在的那條。

這是唯一需要對產品分頁的 URL 方案進行修改的地方。MVC 框架與路由功能是密切集成的。因此這樣的修改將自動反映在 Url.Action 方法的處理結果中。如果運行這個應用程式,並導航到一個頁面,將會看到這個新的 URL 方案在其作用。

 

6.設置內容樣式

前面已經建立了大量的基礎結構,而且應用程式也開始真正地集合在一起了,但並未把註意力放到其外觀上。即使這本書不是一本關於 Web 設計或 CSS 的書,但SportsStore 應用程式設計也會因為太糟糕的格式而破壞它的技術強度。本節將一些常規的事情。

本文打算實現一個帶有頭部的經典式兩列佈局。

 

6.1 定義佈局中的公用內容

【MVC 4】2.使用 Razor 中曾解釋了 Razor 佈局是如何工作和運用的。 當為 Product 控制器創建 List.cshtml 視圖時,曾要求用戶選中 “使用一個佈局” 覆選框,但該文本框保留為空。這便使用了預設佈局 _Layout.cshtml,可以在 SportsStore.WebUI 項目的 Views/Shared 文件夾中找到它。打開這個文件並修改如下:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    	   

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 說起那個無限級分類,相信很多人都知道是什麼東西,也曾經做過。我也相信,大家用得最多的實現方式就是做一個遞歸。 最近我也要做一個帶無限級分類的菜單,但是我又不想用遞歸來做,所以我需要用其他方式來實現,那就是迭代了。 首先,我需要定義一個實體模型,這舉一個省市無限級的例子: class Loaction ...
  • Asp.net的優勢就在於快速構建應用,而對於一些最基礎數據的增刪改以及分頁事件或者樣式的設定可以通過在父類中寫上虛方法來供子類調用,如果子類需要在模板的基礎上衍生變化或者索性不要父類的方法的話,則只需要重寫父類的方法即可。 實驗代碼如下: 首先是模板類的抽象,繼承自Page類; 1 public ...
  • 我們知道,在C#中,string是引用類型的。為空情況有以下三種: 1、 string str1="":會定義指針(棧),併在記憶體里劃一塊值為空的存儲空間(堆),指針指向這個空間。 2、String str2=String.Empty:同上。但是這是個靜態方法,不會反覆的重覆申請記憶體,要優於1中的方 ...
  • 可空值類型,正如字面意義上的,是可以為NULL的值類型。 這個東西存在的意義可以解決比如資料庫的的Int可以為NUll的情況,使得處理資料庫數據更簡單。 實際上可空值類型就是Nullable<T>這個泛型值類型,而C#有一種更簡單的語法糖是int?這種用法: 可空值類型的更多玩法 在大多數時候用C# ...
  • Hello! 歡迎新老朋友來到這裡,這裡隨時恭候你的大駕。 接下來說說三層架構↓↓↓↓↓↓ 三層架構分為:表現層(UI(User Interface))、業務邏輯層(BLL(Business Logic Layer))、數據訪問層(DAL(Data Access Layer))再加上實體類庫(Mod ...
  • 1、解析簡單Json字元串 2、從Json字元串中解析Json數組 持續更新中,敬請期待... ...
  • 日常開發的絕大多數系統中,都涉及到管理用戶的登錄和授權問題。登錄功能(Authentication),針對於所有用戶都開放;而授權(Authorization),則對於某種用戶角色才開放。 在asp.net mvc中,微軟雖然已經幫助開發者構建了ASP.NET Identity這樣強大的驗證授權框架 ...
  • 一、開發環境 操作系統:Win10 編譯器:VS2013 .Net版本:.net framework4.5 二、涉及程式集 Spring.Core.dll:1.3.1 Common.Logging.dll 三、開發過程 1.項目結構 2.編寫Product.cs namespace SpringNe... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...