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

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

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

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

2 每一個 ASP.NET 組件框架的優勢html

框架java

效率jquery

Control數據庫

UIapi

實時服務器

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 框架。 而是要各取所需。

相關文章
相關標籤/搜索