利用同一 ASP.NET 的多個代碼框架


2012 年,Microsoft 推出了兩個添加到 ASP.NET 工具包的新框架:Web API 和 SignalR。 這兩個框架為開發環境帶來獨特的開發方式,每個框架都有自身的獨特之處:

  • Web API 為開發人員提供了類似 MVC 的體驗,以交付針對機器解釋的內容。 沒有用戶界面,並且事務以 RESTful 的方式出現。 內容類型經過協商后,基於提交到 Web API 端點的 HTTP 標頭,Web API 就可以將內容自動格式化為 JSON 或 XML。
  • SignalR 是來自 Microsoft 的新型“實時 Web”交付模型。 此技術打開了客戶端 - 服務器通信通道,支持進行從服務器到客戶端的即時豐富通信。 由於是通過服務器調用客戶端來實現內容交互,SignalR 中的內容交付模型顛覆了我們的正常預期。

Web 窗體和 MVC 之間以及 Web API 和 MVC 之間的利弊權衡如 2 所示。

2 每個 ASP.NET 組件框架的優點

框架

效率

Control

UI

實時

Web 表單

 

 

MVC

 

 

Web API

 

 

SignalR

 

 

 

工作效率與允許您快速開發和交付解決方案的功能相關。 控制是可影響通過網絡向連接用戶傳輸的比特的程度。 UI 指示是否可以使用該框架來交付完整的 UI。 最后,“實時”表明框架能夠在多大程度上及時顯示即時更新的內容。

現在,在 2013 年,當我打開 Visual Studio 並試圖啟動一個 ASP.NET 項目時,我看到的是如 3 和 4 所示的對話框。

 

 
3 Visual Studio 2012 中的新建 Web 項目

 

 
4 Visual Studio 2012 中的新建項目模板對話框

在這些窗口中有一些棘手的問題。 我應從什么類型的項目開始呢? 我應使用什么模板才能最快獲得解決方案呢? 如果我想要添加每個模板的一些組件,將會怎樣? 我可以構建一個帶有一些服務器控件和一個 Web API 的移動應用程序嗎?

我只能選擇一種方法嗎?

我只能選擇一種方法嗎? 簡短的答案是否定的,您並非只能選擇其中一種框架來構建 Web 應用程序。 現在已有一些技術允許您將 Web 窗體和 MVC 結合在一起使用。與顯示的對話框窗口不同,Web API 和 SignalR 可以作為功能輕松添加到 Web 應用程序中。 請記住,所有 ASP.NET 內容都是通過一系列 HttpHandlers 和 HttpModules 呈現的。 只要引用了正確的處理程序和模塊,就可以使用任何一種框架構來建解決方案。

這是“同一 ASP.NET”概念的核心:不要只選擇這些框架中的一個,應使用最符合您的需求的部分構建解決方案。 您有很多選擇,不要局限於其中的一種。

我們來具體看看這是怎么實現的。為此,我將創建一個小型的 Web 應用程序,其中包含統一布局、一個搜索屏幕和一個產品列表的創建屏幕。 搜索屏幕將由 Web 窗體和 Web API 支持,並顯示來自 SignalR 的實時更新。 創建屏幕將由 MVC 模板自動生成。 通過使用第三方控件庫和面向 ASP.NET AJAX 的 Telerik RadControls,我還將確保 Web 窗體具有精美的外觀。 這些控件的試用版可從 bit.ly/15o2Oab 獲得。

設置“示例項目”和“共享布局”

我只需要使用 3 中所示的對話框創建一個項目就可以開始了。 雖然我可以選擇一個空的或 Web 窗體應用程序,可以選擇的最完備解決方案則是 MVC 應用程序。 以 MVC 項目開始是很好的選擇,因為您從 Visual Studio 獲得了所有的工具,可幫助您完成配置模型、視圖和控制器的過程,並能夠將 Web 窗體對象添加到項目文件結構中的任何位置。 通過更改 .csproj 文件中的一些 XML 內容,可將 MVC 工具添加回現有 Web 應用程序。 此過程可通過安裝名為 AddMvc3ToWebForms 的 NuGet 包自動完成。

若要配置在這個項目中使用的 Telerik 控件,我需要在 Web.config 中進行一些更改,以添加通常會在標准 Telerik RadControls 項目中配置的 HttpHandlers 和 HttpModules。 首先,我將添加幾行來定義 Telerik AJAX 控件 UI 皮膚:

 
          <add key="Telerik.Skin" value="WebBlue" />
</appSettings>
        

接下來,添加 Telerik 標簽前綴:

 
          <add tagPrefix="telerik" namespace="Telerik.Web.UI" assembly="Telerik.Web.UI" />
</controls>
        

我將為 Telerik 控件的 Web.config Http­Handlers 添加最少的內容:

 
          <add path="Telerik.Web.UI.WebResource.axd" type="Telerik.Web.UI.WebResource"
    verb="*" validate="false" />
</httpHandlers>
        

最后,我將添加到 Telerik 控件的 Web.config Handlers:

 
          <system.WebServer>
  <validation validateIntegratedModeConfiguration="false" />
  <handlers>
    <remove name="Telerik_Web_UI_WebResource_axd" />
    <add name="Telerik_Web_UI_WebResource_axd"
      path="Telerik.Web.UI.WebResource.axd"
      type="Telerik.Web.UI.WebResource" verb="*" preCondition="integratedMode" />
        

現在,我要為這個項目創建一個布局頁,所以我將在“視圖” | “共享”文件夾中創建一個 Web 窗體 site.master 頁。 對於此站點布局,我要將標准的徽標和菜單添加到所有頁。 我將通過簡單地將圖像拖到布局上來添加一個徽標圖像。 接下來,為了將一個主要的級聯菜單添加到布局,我將從控件工具箱把 RadMenu 拖到圖像正下方的設計器上。 從設計器圖面,通過右鍵單擊菜單控件並選擇“編輯項目”以得到 5 所示的窗口,我可以快速構建菜單。

 

 
5 Telerik RadMenu 配置窗口

我要重點關注的兩個菜單項位於“產品”下:“搜索”和“新建”。 對於每個項目,我已對 NavigateUrl 屬性和文本進行如下設置:

 
          <telerik:RadMenuItem Text="Products">
  <Items>
    <telerik:RadMenuItem Text="Search" NavigateUrl="~/Product/Search" />
    <telerik:RadMenuItem Text="New" NavigateUrl="~/Product/New" />
  </Items>
</telerik:RadMenuItem>
        

菜單配置好以后,我現在遇到了新問題:我使用 Web 窗體定義布局,但需要承載 MVC 內容。 這不是一個簡單的問題,但它可以解決。

彌合鴻溝 — 將 MVC 配置為使用 Web 窗體母版頁

像大多數人一樣,我喜歡讓事情變得簡單。 我來分享一下我為這個介於 Web 窗體和 MVC 之間的項目定義的布局。 Matt Hawley 設計了一項技術(有完善的文檔),演示了如何結合使用 Web 窗體母版頁和基於 MVC Razor 的視圖 (bit.ly/ehVY3H)。 我將在這個項目中使用該技術。 為了創建這樣一個橋梁,我將配置一個引用母版頁的簡單 Web 窗體視圖,稱為 RazorView.aspx:

1.     
2.              <%@ Page Language="C#" AutoEventWireup="true"
3.      MasterPageFile="~/Views/Shared/Site.Master"
4.      Inherits="System.Web.Mvc.ViewPage<dynamic>" %>
5.    <%@ Import Namespace="System.Web.Mvc" %>
6.    <asp:Content id="bodyContent" runat="server" 
7.      ContentPlaceHolderID="body">
8.    <% Html.RenderPartial((string)ViewBag._ViewName); %>
9.    </asp:Content>
10.          

為了讓我的 MVC 控制器使用此視圖,並使其基於 Razor 的視圖得到執行,我需要對每個控制器進行擴展,以正確路由視圖內容。 這通過一個擴展方法來實現,該方法通過 RazorView.aspx 對模型、ViewData 和 TempData 重新進行路由,如 6 所示。

6 通過 Web 窗體母版頁重新路由 MVC 視圖的 RazorView 擴展方法

1.     
2.              public static ViewResult RazorView(this Controller controller,
3.      string viewName = nullobject model = null)
4.    {
5.      if (model != null)
6.        controller.ViewData.Model = model;
7.      controller.ViewBag._ViewName = !string.IsNullOrEmpty(viewName)
8.        ?
9.              viewName
10.      : controller.RouteData.GetRequiredString("action");
11.    return new ViewResult
12.    {
13.      ViewName = "RazorView",
14.      ViewData = controller.ViewData,
15.      TempData = controller.TempData
16.    };
17.  }
18.          

構建此方法后,我就可以通過母版頁輕松路由所有 MVC 操作。 下一個步驟是設置 ProductsController 以便能夠創建產品。

MVC 和創建產品屏幕

此解決方案的 MVC 部分遵循相當標准的 MVC 方法。 在我的項目的“模型”文件夾,我定義了一個簡單的模型對象,稱為 BoardGame,如 7 所示。

7 BoardGame 對象

1.     
2.              public class BoardGame
3.    {
4.      public int Id { get; set; }
5.      public string Name { get; set; }
6.      [DisplayFormat(DataFormatString="$0.00")]
7.      public decimal Price { get; set; }
8.      [Display(Name="Number of items in stock"), Range(0,10000)]
9.      public int NumInStock { get; set; }
10.  }
11.          

接下來,我使用 Visual Studio 中標准的 MVC 工具創建一個空的 ProductController。 我將添加“視圖”|“產品”文件夾,然后右鍵單擊“產品”文件夾,再從“添加”菜單選擇“視圖”。 此視圖將支持新 BoardGame 的創建,所以我將使用 8 中所示的選項創建。

 

 
8 創建新建視圖

由於使用了 MVC 工具和模板,我不需要進行任何更改。 創建的視圖帶有標簽和驗證,並可以使用我的母版頁。  9 顯示如何在 ProductController 中定義“新建”操作。

9 通過 RazorView ProductController 路由

1.     
2.              public ActionResult New()
3.    {
4.      return this.RazorView();
5.    }
6.    [HttpPost]
7.    public ActionResult New(BoardGame newGame)
8.    {
9.      if (!ModelState.IsValid)
10.    {
11.      return this.RazorView();
12.    }
13.    newGame.Id = _Products.Count + 1;
14.    _Products.Add(newGame);
15.    return Redirect("~/Product/Search");
16.  }
17.          

MVC 開發人員應熟悉此語法,唯一的變化是返回一個 RazorView,而不是視圖。 _Products 對象是一個此控制器中所定義的虛產品的靜態只讀集合,而不是使用此示例中的數據庫:

1.     
2.              public static readonly List<BoardGame> _Products = 
3.      new List<BoardGame>()
4.    {
5.      new BoardGame() {Id=1, Name="Chess", Price=9.99M},
6.      new BoardGame() {Id=2, Name="Checkers", Price=7.99M},
7.      new BoardGame() {Id=3, Name="Battleship", Price=8.99M},
8.      new BoardGame() {Id=4, Name="Backgammon", Price= 12.99M}
9.    };
10.          

配置基於 Web 窗體的搜索頁

我希望用戶訪問產品搜索頁面的 URL 有別於 Web 窗體的 URL,能夠便於用戶搜索。 隨着 ASP.NET 2012.2 的發布,現在可以輕松完成這一配置。 只需打開 App_Start/ RouteConfig.cs 文件,並調用 EnableFriendlyUrls 以啟動此功能:

1.     
2.              public static void RegisterRoutes(
3.        RouteCollection routes)
4.      {
5.        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
6.        routes.EnableFriendlyUrls();
7.        routes.MapRoute(
8.          name: "Default",
9.          url: "{controller}/{action}/{id}",
10.        defaults: new { controller = "Home", action =
11.          "Index", id = UrlParameter.Optional }
12.      );
13.    }
14.          

添加這一行后,ASP.NET 將把 /Product/Search 請求路由到位於 /Product/Search.aspx 的物理文件

接下來,我要配置一個顯示目前產品及其庫存水平的網格的搜索頁面。 我將在我的項目中創建一個“產品”文件夾並向其添加一個名為 Search.aspx 的新 Web 窗體。 在此文件中,我將去掉除 @Page 指令之外的所有標記,並將 MasterPageFile 設置為前面定義的 Site.Master 文件。 為了顯示我的結果,我將選擇 Telerik RadGrid,這樣我就可以快速配置並顯示結果數據:

1.     
2.              <%@ Page Language="C#" AutoEventWireup="true"
3.      CodeBehind="Search.aspx.cs"
4.      Inherits="MvcApplication1.Product.Search"
5.      MasterPageFile="~/Views/Shared/Site.Master" %>
6.    <asp:Content runat="server" id="main" ContentPlaceHolderID="body">
7.      <telerik:RadGrid ID="searchProducts" runat="server" width="500"
8.        AllowFilteringByColumn="True" CellSpacing="0" GridLines="None"
9.        AllowSorting="True">
10.          

網格將自動生成綁定到其上的列,並提供排序和篩選功能。 不過,我希望提高這一過程的動態性。 我想在客戶端實現數據的交付和管理。 在此模型中,數據可以在 Web 窗體中無服務器端代碼的情況下被發送並綁定。 為此,我將添加一個負責交付並執行數據操作的 Web API。

向組合中添加 Web API

使用標准的“項目” | “新增”菜單,我將一個名為 ProductController 的 Web API 控制器添加到我的項目中名為“api”的文件夾。 這有助於我清楚了解 MVC 控制器和 API 控制器之間的差別。 此 API 將完成一項工作 — 以 JSON 格式交付網格數據並支持 OData 查詢。 要在 Web API 中完成這一點,我將編寫一個 Get 方法並為其添加 Queryable 屬性:

1.     
2.              [Queryable]
3.    public IQueryable<dynamic> Get(ODataQueryOptions options)
4.    {
5.      return Controllers.ProductController._Products.Select(b => new
6.      {
7.        Id = b.Id,
8.        Name = b.Name,
9.        NumInStock = b.NumInStock,
10.      Price = b.Price.ToString("$0.00")
11.    }).AsQueryable();
12.  }
13.          

此代碼經過少許格式化處理之后,返回靜態列表中的 BoardGame 對象集合。 通過使用 [Queryable] 修飾該方法並返回可查詢的集合,Web API 框架會自動接手處理 OData 篩選和排序命令。 該方法還需要使用輸入參數 ODataQueryOptions 進行配置,以便處理網格提交的篩選數據。

如果要在 Search.aspx 上配置網格以使用此新 API,我需要向頁面標記添加一些客戶端設置。 在此網格控件中,我使用 ClientSettings 元素和 DataBinding 設置定義客戶端數據綁定。 DataBinding 設置列出了 API 的位置、響應格式類型和要查詢的控制器名稱,以及 OData 查詢格式。 通過這些設置和要在網格中顯示的列的定義,我可以運行該項目,並看到綁定到 _Products 虛數據列表中數據的網格,如 10 所示。

10 網格的完整格式源

1.     
2.              <telerik:RadGrid ID="searchProducts" runat="server" width="500"
3.      AllowFilteringByColumn="True" CellSpacing="0" GridLines="None"
4.      AllowSorting="True" AutoGenerateColumns="false"
5.      >
6.        <ClientSettings AllowColumnsReorder="True"
7.          ReorderColumnsOnClient="True"
8.          ClientEvents-OnGridCreated="GridCreated">
9.          <Scrolling AllowScroll="True" UseStaticHeaders="True"></Scrolling>
10.        <DataBinding Location="/api" ResponseType="JSON">
11.          <DataService TableName="Product" Type="OData"  />
12.        </DataBinding>
13.      </ClientSettings>
14.      <MasterTableView ClientDataKeyNames="Id" DataKeyNames="Id">
15.        <Columns>
16.          <telerik:GridBoundColumn DataField="Id" HeaderStyle-Width="0"
17.            ItemStyle-Width="0"></telerik:GridBoundColumn>
18.          <telerik:GridBoundColumn DataField="Name" HeaderText="Name"
19.            HeaderStyle-Width="150" ItemStyle-Width="150">
20.            </telerik:GridBoundColumn>
21.          <telerik:GridBoundColumn ItemStyle-CssClass="gridPrice"
22.            DataField="Price"
23.            HeaderText="Price" ItemStyle-HorizontalAlign="Right">
24.            </telerik:GridBoundColumn>
25.          <telerik:GridBoundColumn DataField="NumInStock"
26.            ItemStyle-CssClass="numInStock"
27.            HeaderText="# in Stock"></telerik:GridBoundColumn>
28.        </Columns>
29.      </MasterTableView>
30.    </telerik:RadGrid>
31.          

使用實時數據激活網格

最后一個步驟是隨着產品出貨和進貨實時顯示庫存水平變化的功能。 我將添加一個 SignalR hub 以傳輸更新信息並在搜索網格上顯示新值。 要將 SignalR 添加到我的項目,我需要發出以下兩個 NuGet 命令:

 
          Install-Package -pre Microsoft.AspNet.SignalR.SystemWeb
Install-Package -pre Microsoft.AspNet.SignalR.JS
        

這些命令將在 IIS Web 服務器中安裝要承載的 ASP.NET 服務器組件,並為 Web 窗體啟動客戶端 JavaScript 庫。

SignalR 服務器端組件被稱為 Hub,我將定義我自己的 Hub,方法是在我的 Web 項目中的 Hubs 文件夾添加一個名為 StockHub 的類。 StockHub 需從 Microsoft.AspNet.SignalR.Hub 類繼承而得。 我定義了一個靜態的 System.Timers.Timer,使應用程序能夠模擬庫存水平的變化。 模擬方式是每隔 2 秒(觸發定時器 Elapsed 事件處理程序),我會隨機設置一個隨機選擇產品的庫存水平。 一旦設置了產品庫存水平,通過在客戶端執行一個名為 setNewStockLevel 的方法,我將通知所有連接的客戶端,如 11 中所示。

11 SignalR Hub 服務器端組件

1.     
2.              public class StockHub : Hub
3.    {
4.      public static readonly Timer _Timer = new Timer();
5.      private static readonly Random _Rdm = new Random();
6.      static StockHub()
7.      {
8.        _Timer.Interval = 2000;
9.        _Timer.Elapsed += _Timer_Elapsed;
10.      _Timer.Start();
11.    }
12.    static void _Timer_Elapsed(object sender, ElapsedEventArgs e)
13.    {
14.      var products = ProductController._Products;
15.      var p = products.Skip(_Rdm.Next(0, products.Count())).First();
16.      var newStockLevel = p.NumInStock + 
17.        _Rdm.Next(-1 * p.NumInStock, 100);
18.      p.NumInStock = newStockLevel;
19.      var hub = GlobalHost.ConnectionManager.GetHubContext<StockHub>();
20.      hub.Clients.All.setNewStockLevel(p.Id, newStockLevel);
21.    }
22.  }
23.          

為使此 Hub 的數據可從服務器訪問,我需要向 RouteConfig 添加一行,表明 Hub 的存在。 通過在 RouteConfig 的 RegisterRoutes 方法中調用 routes.MapHubs,我完成了 SignalR 服務器端的配置。

接下來,網格需要偵聽這些來自服務器的事件。 為此,我需要添加一些從 NuGet 安裝的 SignalR 客戶端庫和 MapHubs 命令生成的代碼的 JavaScript 引用。 SignalR 服務使用 12 中顯示的代碼連接並公開客戶端上的 setNewStockLevel 方法。

12 用於激活網格的 SignalR 客戶端代碼

1.     
2.              <script src="/Scripts/jquery.signalR-1.0.0-rc2.min.js"></script>
3.    <script src="/signalr/hubs"></script>
4.    <script type="text/javascript">
5.      var grid;
6.      $().ready(function() {
7.          var stockWatcher = $.connection.stockHub;
8.          stockWatcher.client.setNewStockLevel = function(id, newValue) {
9.            var row = GetRow(id);
10.          var orgColor = row.css("background-color");
11.          row.find(".
12.            numInStock").animate({
13.            backgroundColor: "#FFEFD5"
14.          }, 1000, "swing", function () {
15.            row.find(".
16.            numInStock").html(newValue).animate({
17.              backgroundColor: orgColor
18.            }, 1000)
19.          });
20.        };
21.        $.connection.hub.start();
22.    })
23.  </script>
24.          

在 jQuery 就緒事件處理程序中,我使用 $.connection.stockHub 語法建立了對 StockHub 的引用,名為 stockWatcher。 然后為 stockWatcher 的客戶端屬性定義了 setNewStockLevel 方法。 此方法使用其他一些 JavaScript 幫助器方法遍歷網格,找到相應產品的行,並使用 jQuery UI 提供的絢麗動畫更改庫存水平,如 13 所示。

 

 
13 Web API 生成並由 SignalR 維護的網格搜索界面

總結

至此,我演示了如何構建一個 ASP.NET MVC 項目並添加 Web 窗體布局、第三方 AJAX 控件及相應的 Web 窗體。 我使用 MVC 工具生成用戶界面,並使用 Web API 和 SignalR 激活內容。 此項目利用各組件的最佳功能,使用來自所有四個 ASP.NET 框架的功能,提供了一個一致的界面。 您也來試試吧。 對於您以后的項目,您將不必局限於一種 ASP.NET 框架。 而是要各取所需。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM