使用ASP.NET Web API和Handlebars的Web模板

目錄

介紹

目標聽衆

期待什麼

示例代碼概述

總覽

Handlebars和模板

使用代碼

起步

第1步


GitHub下載

介紹

Web應用程序的開發趨勢不時發生變化。幾年前我們用來構建的應用程序體系結構現在已經開始或已經過時且效率較低。您將要閱讀的文章適用於相同的變化趨勢。

本文爲集成Web應用程序的三個不同但至關重要的部分提供了啓示。即客戶端層,服務端層和服務層。將客戶端和服務端代碼區域稱爲層可能是錯誤的,但是爲了命名約定的統一,讓我們將它們視爲多層系統的特定層,服務端位於其中,然後是服務,然後是客戶端(如果您願意,您甚至可以改變排序,那完全可以)。

目標聽衆

我將假設您已經熟悉使用Web API以及Handlebars。如果您處理的大型應用程序可能達到企業規模,那就太好了,因爲這樣您就可以更輕鬆地將我嘗試在示例代碼中實現的關注點分離聯繫起來。

期待什麼

本文絕不嘗試提出某種新形式的Web應用程序開發模式。Web模板已經存在很長時間了,只是我們處理模板中內容生成的方式發生了變化。我們曾經在不同位置將服務器端變量和嵌入式代碼放在HTML標記中,以實現與模板的數據綁定。即使在今天,我們中的許多人還是會喜歡並嚴重依賴動態代碼生成技術,例如ASP.NET MVC中的Razor View,這是可以理解的,因爲它完美地解決了內容生成和視圖模型綁定的問題。但是,如果您是像我這樣的頭腦簡單的開發人員,並且不太願意將太多內容混淆在一起,那麼您將瞭解維護一個將我們正確分離所有內容的應用程序是多麼容易。而且,我一直認爲在模板代碼中放入過多的領域邏輯是一個壞主意。

本文旨在提供一個使用JavaScriptWeb APIWeb模板代碼實現,以便希望對這些應用程序中的事物如何運行有基本瞭解的開發人員可以從此處獲得參考。

如果您願意進行Google的快速搜索,則可以在Internet上輕鬆找到其他此類代碼示例,但是最好參考多種做同一方法的引用,因爲我們每個人都有不同的要求。

示例代碼概述

本文中的代碼用於示例購物車佈局。在此應用程序中,我們基本上必須在預定義的網頁設計中更新信息。爲此,最好使用HTML模板,因爲我們只需要將輸入數據與模板結合起來並顯示已編譯的HTML結果。

總覽

我試圖使事情保持簡單,我的重點主要放在Web API服務調用和已編譯模板的代碼生成以及正確分離所有內容上。您將找不到用於數據庫CRUD操作的任何代碼,爲此,我創建了一個非常簡單的數據映射器以使用內存中對象。您可以隨意編寫數據映射器的代碼,以根據需要在附加的示例代碼中支持其他提供程序。

此實現的優點在於,將特定類型的數據映射器注入到Web API控制器中,以便我們可以輕鬆地在開發數據和測試數據之間切換。這種方法使我們很容易將UI與單獨的測試模塊集成在一起。我們需要做的就是使用另一個Web.config文件來使用另一個數據映射器。然後,映射器可以從諸如內存對象“XML或測試數據庫之類的任何源返回數據。

本文的篇幅將有些長,因此我將請您忍受,您將發現如何正確構造Web應用程序框架,並且在編寫摘要時可以減少編寫新功能的代碼。

首先,讓我們簡要了解一下Handlebars模板引擎,以及有關所有模板的介紹。我假設您已經熟悉ASP.NET應用程序和Web API,並且對模板有一些基本的瞭解。如果您不是,那麼我建議您先進一步瞭解它們,然後再繼續閱讀本文。

Handlebars和模板

模板作爲結構實踐已經使用了很長時間。模板只是表示輸出藍圖或線框,其中有一些標記被替換爲輸入數據中包含的實際值。然後將模板與數據合併以生成所需的輸出。

對於Handlebars,我將僅使用Wikipedia上給出的定義,因爲它已經非常清楚了,我認爲不需要重新發明另一個定義。

引用:

Handlebars是一個語義Web模板系統,由Yehuda Katz2010年啓動。Handlebars.jsMustache的超集,除了Handlebars模板外,還可以呈現Mustache模板。雖然Mustache​​一種無邏輯的模板語言,但是Handlebars增加了可擴展性和最小的邏輯,例如#if,#unless,#with#each幫助器。

使用代碼

起步

在開始理解客戶端代碼之前,我們應該首先了解服務器代碼中的情況。該示例代碼具有一個單獨的數據層,其中包含用於從您決定使用的任何數據源獲取數據的類。我已經創建了一個接口IMapper,並將其放在另一個層中,該層在所有地方都可以共享,以便我們可以管理應用程序不同部分之間的依賴項注入。

1

我們將從在Visual Studio 2012中創建一個空的ASP.NET Web窗體應用程序開始(您可以從此處下載一個)。這個空的應用程序沒有任何與導航或身份驗證相關的預生成代碼,而這正是我們此示例應用程序所需要的。

這是項目結構的快照。我將逐一解釋其中的所有模塊:

添加一個新的.aspx文件,並將其命名爲Default.aspx。另外,添加一個母版頁並將其命名Master.master。到目前爲止,我們不需要在母版頁上添加任何代碼,稍後我們將添加對所需腳本和樣式表的引用。

將我們剛剛添加的Default.aspx文件中的代碼替換爲以下內容:

%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" 
   Inherits="TestCart.Default" MasterPageFile="~/Master.Master" %>

<asp:Content ID="index" runat="server" ContentPlaceHolderID="MainPlaceHolder">
    <div id="divGrid">
    </div>
    <script>      
    </script>
</asp:Content>

默認頁面將包含所有可用產品的表格。

添加另一個aspx頁面並將其命名爲Product.aspx。在此示例應用程序中,我們將只有兩個aspx頁面,它們足以傳達對使用HandlebarsASP.NET中進行模板製作的理解。將以下代碼添加到Product.aspx頁。

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Product.aspx.cs" 

    Inherits="TestCart.Product"  MasterPageFile="~/Master.Master" %>

<asp:Content ID="product" runat="server" ContentPlaceHolderID="MainPlaceHolder">
    <div id="divProduct">
    </div>
    <script>        
    </script>
</asp:Content>

在進一步深入客戶端之前,讓我們首先了解服務器代碼結構。客戶端只需調用Web Api即可獲取數據,然後將數據與模板進行合併。現在,我們可以遵循以下兩種方法之一:

  1. 第一個是將領域邏輯直接寫入Web Api類,初始化Global.asax文件中的數據訪問模塊,並將其綁定到會話。Web Api類將在會話中使用該數據訪問模塊來獲取數據。這種方法有很多缺點,例如服務層內部不應有任何業務邏輯(或者應該是最低限度),並且我們還將服務與數據訪問模塊緊密結合在一起,這使得集成測試模塊或其他生產數據提供程序非常困難。
  2. 第二個是當我們將適當的數據訪問模塊依賴項注入Web Api而不在其中放置任何域邏輯代碼時。

現在很明顯,你們已經找出了哪種方法更好,所以讓我們開始實施第二種方法。

添加一個新的類庫項目並將其命名Data。在Data項目中添加一個類,併爲其命名Data。(請參閱上面的解決方案資源管理器圖像)。

Data類上,我們將首先照顧我們的內存產品存儲庫。該存儲庫將用於將靜態產品對象存儲在內存中。爲此,請將以下代碼添加到Data.cs文件中:

#region Static Data

/// <summary>
/// Product repository class that contains the static product information
/// </summary>
public class ProductRepository
{
    private List<Product> _products;

    /// <summary>
    /// Get all products
    /// </summary>
    public List<Product> Products { get { return _products; } }

    public ProductRepository()
    {
        Product product;
        _products = new List<Product>();

        product = new Product(1, "iPhone 6", 38999, "iphone.jpg",
            "Apple iPhone 6 exudes elegance and excellence at its best. With iOS 8,
            the world’s most advanced mobile operating system, and a larger and
            superior 4.7 inches high definition Retina display screen,
            this device surpasses any expectations you might have with a mobile phone. ",
            "This new generation device comes with improved Wi-Fi speed to enhance
            your surfing experience. Its sleek body and seamless design along with
            a perfect unison between its software and hardware system gives you
            marvellous navigation results. Just double tap the home screen
            and the entire screen shifts down to your thumb for easy operation.
            In terms of security, this iPhone is a class apart from its predecessors
            as it allows you finger print lock. The multitude of features in
            Apple iPhone 6 makes it powerful, efficient, smooth and super easy to use.
            With this phone in your hands, you can manage your world with
            just a single touch!",
            true);
        product.Reviews = new List<Review>();
        product.Attributes = new List<ProductAttribute>();

        product.Reviews.Add(new Review("John", "Very good phone!!", 4));
        product.Attributes.Add(new ProductAttribute("Brand", "Apple"));

        _products.Add(product);
    }

    /// <summary>
    /// Get product by id
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    public Product GetById(Int32 id)
    {
        return _products.Find(p => p.Id == id);
    }
}

#endregion Static Data

上面是添加內存對象的非常醜陋的方法,但這將對本示例應用程序有用。ProductRepository類構造函數中,我們將產品信息添加到Products列表中。隨附的代碼將更多產品添加到列表中。視圖模型類ProductReviewsAttributes被包含在共享模塊中。

可以直接從只讀Products屬性訪問產品列表。有一種方法GetById可以使用整數類型Id在產品列表中搜索產品,並在找到後返回product對象。

現在,我們需要添加抽象和具體的映射器類以獲取數據。在此之前,我們需要在解決方案中爲共享數據添加另一個邏輯層。添加一個新的類庫項目並將其命名Shared。在共享層添加三個類和它們命名爲ModelsSharedInterfacesTypes

讓我們在Models類中爲視圖模型類添加如下代碼:

/// <summary>
/// Product class
/// </summary>
public class Product
{
    public Int32 Id { get; set; }
    public String Name { get; set; }
    public Decimal Price { get; set; }
    public String Image { get; set; }
    public String SmallDescription { get; set; }
    public String LargeDescription { get; set; }
    public List<Review> Reviews { get; set; }
    public List<ProductAttribute> Attributes { get; set; }
    public Boolean InStock { get; set; }

    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="name"></param>
    /// <param name="image"></param>
    /// <param name="smallDescription"></param>
    /// <param name="largeDescription"></param>
    /// <param name="inStock"></param>
    public Product(Int32 id, String name, Decimal price, String image,
                   String smallDescription,
                   String largeDescription, Boolean inStock)
    {
        Id = id;
        Name = name;
        Price = price;
        Image = image;
        SmallDescription = smallDescription;
        LargeDescription = largeDescription;
        InStock = inStock;
    }
}

/// <summary>
/// Product review class
/// </summary>
public class Review
{
    public String ReviewedBy { get; set; }
    public String ReviewText { get; set; }
    public Int32 Rating { get; set; }

    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="reviewedBy"></param>
    /// <param name="reviewText"></param>
    /// <param name="rating"></param>
    public Review(String reviewedBy, String reviewText, Int32 rating)
    {
        ReviewedBy = reviewedBy;
        ReviewText = reviewText;
        Rating = rating;
    }
}

/// <summary>
/// Product attribute class
/// </summary>
public class ProductAttribute
{
    public String Name { get; set; }
    public String Value { get; set; }

    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="name"></param>
    /// <param name="value"></param>
    public ProductAttribute(String name, String value)
    {
        Name = name;
        Value = value;
    }
}

上面的代碼是不言自明的,我們只是添加帶有public屬性的簡單類來保存產品數據。這些視圖模型將返回到JavaScript代碼,然後與我們的模板合併。

我們將需要在應用程序之間共享接口協定,以執行依賴項注入。因此,將它們放在一個共同的地方很有意義。將以下代碼添加到SharedInterfaces類:

/// <summary>
/// Mapper interface
/// </summary>
public interface IMapper
{
    Object Get(DataType type);
    void SetId(Int32 id);
}

/// <summary>
/// Product Mapper interface
/// </summary>
public interface IProductMapper
{
    List<Product> GetAllProducts();
    Product GetProductById(Int32 id);
}

IMapper是所有數據映射器的接口。Get方法將根據類型返回數據。SetId方法是設置我們需要的記錄的IDDataType是一個enum,它將指導Get方法獲取我們所需的特定信息。

還有另一個特定於產品的IProductMapper接口。IMapper將注入到IProductMapper的構造函數。然後,IProductMapper方法實現將在IMapper內部調用方法以執行Crud操作。GetAllProducts將獲得所有可用的產品,並且GetProductById根據提供的ID返回單個產品。當我們使用數據庫時,具體的產品映射器方法實現將需要包裝在事務範圍內。

共享中只剩下添加DataType enum實現:

/// <summary>
/// Data type enum
/// </summary>
public enum DataType
{
    AllProducts,
    IndividualProduct
}

到目前爲止,我們只需要兩種類型;AllProductsIndividualProducts。當前的映射器實現需要處理所有類型,以返回所請求數據的類型。您可以根據需要隨意添加更多內容。

有了共享代碼後,現在我們可以開始實現具體的映射器類了。我們將有三個映射器類:AbstractMapperInMemoryMapperProductMapper

AbstractMapper將包含所有不同的具體映射器通用的代碼。將以下代碼添加到Data.cs類中,以實現此目的:

#region Mappers

/// <summary>
/// Abstract data mapper.
/// </summary>
public abstract class AbstractMapper : IMapper
{
    protected Int32 _id;
    public abstract Object Get(DataType type);
    public void SetId(Int32 id)
    {
        _id = id;
    }
}

在上面的代碼中,我們將Get方法聲明爲abstract,以便繼承abstract映射器的具體類可以執行該實現。SetId方法只是設置受保護_id變量的值。

InMemoryMapper將通過實例化ProductRepository類從我們將創建的內存數據中獲取數據。以下代碼適用於此映射器類:

/// <summary>
/// Product mapper to get data from the objects in the memory.
/// </summary>
public class InMemoryMapper : AbstractMapper
{
    /// <summary>
    /// Get the data
    /// </summary>
    /// <returns></returns>
    public override Object Get(DataType type)
    {
        Object retVal = null;

        switch (type)
        {
            case DataType.AllProducts:
                retVal = new ProductRepository().Products;
                break;

            case DataType.IndividualProduct:
                retVal = new ProductRepository().GetById(_id);
                break;
        }

        return retVal;
    }
}

以上是Get方法的實現。根據請求的數據類型,代碼初始化ProductRepository的新實例,然後獲取數據。需要注意的一件事是,您可以將內存數據設置爲static避免每次都創建新對象。這是我留給你們練習的一項練習。

ProductMapper是產品的特定映射器。它將使用IMapper接口執行不同的代碼指令以獲取用戶所需的數據。將以下代碼添加到Data.cs文件:

/// <summary>
/// Concrete mapper class
/// </summary>
public class ProductMapper : IProductMapper
{
    private IMapper _mapper = null;

    public IMapper Mapper
    {
        get { return _mapper; }
        set { _mapper = value; }
    }

    public ProductMapper(IMapper mapper)
    {
        _mapper = mapper;
    }

    public List<Product> GetAllProducts()
    {
        return _mapper.Get(DataType.AllProducts) as List<Product>;
    }

    public Product GetProductById(Int32 id)
    {
        _mapper.SetId(id);
        return _mapper.Get(DataType.IndividualProduct) as Product;
    }
}

如果您熟悉最簡單的依賴項注入的工作方式,那麼上面的代碼將很簡單。如果您不熟悉,那麼我強烈建議您閱讀一下。

我們正在做的只是將IMapper接口的特定實現注入到ProductMapper構造函數中。ProductMapper只需要知道IMapper的成員,而不是實現的類型。然後,我們將在Web Api控制器中注入ProductMapper實現。

我們已經創建了所有必需的類來獲取產品數據,現在是時候進行Web Api了。添加一個新的Web Api控制器並將其命名ProductController。這一產品將帶有空GET/PUT/POST/DELETE方法。我們只需要從服務器獲取數據的GET方法。將以下代碼添加到ProductController構造函數中,該構造函數接受IProductMapper類型的mapper 作爲參數。我們還需要添加一個private字段來保存GET方法的映射器引用和修改後的簽名:

public class ProductController : ApiController
{
    private readonly IProductMapper _mapper;

    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="mapper">Mapper dependency</param>
    public ProductController(IProductMapper mapper)
    {
        _mapper = mapper;
    }

    // GET api/<controller>
    public List<Shared.Product> Get()
    {
        return _mapper.GetAllProducts();
    }

    // GET api/<controller>/5
    public Shared.Product Get(int id)
    {
        return _mapper.GetProductById(id);
    }

    // POST api/<controller>
    public void Post([FromBody]string value)
    {
    }

    // PUT api/<controller>/5
    public void Put(int id, [FromBody]string value)
    {
    }

    // DELETE api/<controller>/5
    public void Delete(int id)
    {
    }
}

Api控制器類將使用private _mapper調用具體映射器類的IMapper類型實現。這些方法無需擔心正在使用哪種類型的映射器,因此UI完全不知道並且已從數據提供者中取消插入。這使得將定製測試與我們的應用程序集成非常容易。

現在出現了一個問題,我們將如何在Web Api控制器類中實際注入映射器。我們將通過創建IDependencyResolver接口的實現來做到這一點。IDependencyResolver用於解析Web Api服務依賴關係,我們可以根據請求的服務類型返回服務。在下面的類中對此進行了說明。添加一個新的文件夾App_Code和一個類MyDependencyResolver,並將此代碼添加到類文件中:

public class MyDependencyResolver : IDependencyResolver
{
    private static readonly IProductMapper Mapper =
            new ProductMapper(DataHelper.CreateMapper(WebConfigurationManager.AppSettings
            ["MapperType"].ToString()));

    public Object GetService(Type serviceType)
    {
        return serviceType == typeof(ProductController) ?
                              new ProductController(Mapper) : null;
    }

    public IEnumerable<Object> GetServices(Type serviceType)
    {
        return new List<Object>();
    }

    public IDependencyScope BeginScope()
    {
        return this;
    }

    public void Dispose()
    {

    }
}

GetService方法實現將用於根據請求的服務類型返回適當的Api控制器。當請求的類型爲ProductController時,我們將根據存儲在配置文件中的映射器類型注入適當的映射器依賴項,從而返回ProductController類的新實例。

映射器類型的值需要存儲在web.config文件中:

<appSettings>
    <add key="MapperType" value="InMemoryMapper" />
</appSettings>

現在,我們需要使用應用程序配置連接依賴解析器,可以在Global.asax文件中完成此操作,如下所示:

public class Global : System.Web.HttpApplication
{
    protected void Application_Start(object sender, EventArgs e)
    {
        RouteTable.Routes.MapHttpRoute(
                    name: "DefaultApi",
                    routeTemplate: "api/{controller}/{id}",
                    defaults: new { id = System.Web.Http.RouteParameter.Optional }
                    );
        GlobalConfiguration.Configuration.DependencyResolver = new MyDependencyResolver();
    }
}

哦,當我們將Web Api控制器顯式添加到Web窗體應用程序時,我們還需要將api控制器路由模板與路由表進行映射。

我們仍然缺少重要的代碼,這些代碼將用於使用String配置中存儲的值來創建映射器類型實例。將此代碼添加到Data.cs文件中:

#region Helper

public static class DataHelper
{
    /// <summary>
    /// Creates the mapper object from the given type.
    /// </summary>
    /// <param name="type"></param>
    /// <returns></returns>
    public static IMapper CreateMapper(String type)
    {
        return Activator.CreateInstance(Type.GetType
                         (String.Format("Data.{0}", type))) as IMapper;
    }
}

#endregion Helper

上面,我們使用反射創建映射器和類型,然後在返回對象之前將其類型轉換爲IMapper然後,該mapper對象將被傳遞到具體的產品映射器類構造函數中。

這一切都與服務器代碼有關,現在我們可以自由地繼續討論客戶端代碼。您必須已經添加了母版頁和aspx頁,現在我們必須添加支持的JavaScript代碼。爲此,我們應該在JavaScript中創建一個應用程序結構,該結構應可從任何地方輕鬆訪問。爲此,我將要附加一個名爲App窗口的對象。從我們的App對象,我們可以做不同的事情,例如使用Handlebars模板,事件綁定等調用Web Api

我將添加與不同任務關聯的單獨的JavaScript文件,以使將來的代碼更改更加容易。要將App對象加載到窗口中,我將使用一個簡單的加載器函數。同樣,我將爲每個模塊使用單獨的加載器功能。

Content\Scripts文件夾中添加一個新的JavaScript文件,並將其命名爲App.js

;
(function (w, undefined)
{
    function loadApp()
    {
        var app =
            {
                'Events': new w['AppEventsLoader'](),
                'Service': new w['AppServiceLoader'](),
                'Constants': new w['AppConstantsLoader'](),
                'Templates': new w['AppTemplatesLoader']()
            };

        w['App'] = app;

        //There is no going back now!!
        if (w['LoadApp']) w['LoadApp'] = undefined;
        if (w['AppEventLoader']) w['AppEventLoader'] = undefined;
        if (w['AppServiceLoader']) w['AppServiceLoader'] = undefined;
        if (w['AppConstantsLoader']) w['AppConstantsLoader'] = undefined;
        if (w['AppTemplatesLoader']) w['AppTemplatesLoader'] = undefined;
    }
    
    w['LoadApp'] = loadApp;
})(window);

加載程序完成其工作後,由於不再使用它們,我們將其從內存中刪除。在這一點上,您必須考慮如何調用LoadApp函數,當我們到達下面的母版頁實現時,您會發現這一點。

讓我們添加App對象的不同模塊。添加一個新的JavaScript文件,並將其命名爲Service.js。我們將使用它通過一個入口點來調用Web Api

;
(function (w, $)
{
    var serviceLoader = function ()
    {
        this.Call = function (type, args)
        {
            var url = w.App.Constants.ServiceMap[type];

            if (!args) return $.getJSON(url);
            else return $.getJSON(url, args);
        };
    };
    
    w['AppServiceLoader'] = serviceLoader;
})(window, jQuery);

在上面的代碼中,type用於確定Web ApiURL。如果args不是null,那麼我們將通過服務調用將其發送。我已將服務類型和模板url的元數據信息存儲在代碼中,該代碼將在App初始化時加載。Constants.js用於此目的:

;
(function (w)
{
    var constantsLoader = function ()
    {
        this.ServiceMap =
        {
            'GetAllProducts': '/api/Product/',
            'GetProduct': '/api/Product/'
        };

        this.TemplateMap =
        {
            'ProductGrid': 'Content/Templates/ProductGrid.html',
            'ProductItem': 'Content/Templates/ProductItem.html'
        };
    };

    w['AppConstantsLoader'] = constantsLoader;

})(window);

最後一個JavaScript模塊將用於編譯Handlebars模板。現在,在嘗試實現代碼之前,我們應該確保如何將所有內容放在一起。所以就這樣。我們需要:

  1. 輸入數據
  2. handlebars模板,它只是HTML字符串和標記在這裏和那裏的{{value}}形式,和
  3. 最後,我們需要將兩者結合起來以獲取最終的HTML,並將其顯示給用戶。這意味着輸出HTML將始終基於用戶請求的信息。

例如,如果我們以Json的形式輸入數據如下:

var data = {'FirstName': 'Mark', 'LastName': 'Twain'};

我們有以下handlebars模板:

<div id='placeholder'></div>
<script id="my-template" type="text/x-handlebars-template">
    <div>First Name: {{FirstName}}</div>
    <div>Last Name: {{LastName}}</div>
</script>

然後我們將編寫以下JavaScript代碼以將模板與數據結合起來:

var myTemplate = Handlebars.compile($('#my-template').html());
var myHtml = myTemplate(data);
$('#placeholder').html(myHtml);

我們還可以將json對象數組與模板綁定,爲此,我們需要使用以下示例中給出的{{#each}}令牌:

var contact = {'Name': 'Jason', 'Phones':[{'Number': 111111}, {'Number': 222222}]};

<script id="my-template" type="text/x-handlebars-template">
    <div>Name: {{Name}}</div>
    Phone Numbers: <br />
    {{#each Phones}}
        <div>Last Name: {{Number}}</div>
    {{/each}}     
</script>

就這麼簡單。但是,我們不會在示例應用程序中編寫如此簡單的代碼。我們將在需要時將模板加載到瀏覽器緩存中,然後將使用輸入數據獲取輸出HTML

添加一個新的JavaScript文件並將其命名爲Templates.js 並將以下內容粘貼到其中:

;
(function (w, $, h)
{
    var templatesLoader = function ()
    {
        this.GetTemplateHtmlAjax = function (name, jsonData, secondCallback)
        {
            var source;
            var template;
            var html;

            $.ajax({
                url: App.Constants.TemplateMap[name],
                cache: true  
            }).then(function (data)
            {
                source = data;
                template = h.compile(source);
                html = template(jsonData);
                secondCallback(html);
            });
        }        
    };

    w['AppTemplatesLoader'] = templatesLoader;
})(window, jQuery, Handlebars);

GetTemplateHtmlAjax函數接受template namejsonData並和callback作爲參數。模板名稱用於解析模板文件路徑,將提供jsonData作爲已加載和已編譯模板的源,並且secondCallback將在傳遞輸出HTML的同時調用該函數。

因此,當我們想將輸入數據與模板綁定時,只需調用上面的GetTemplateHtmlAjax代碼,並將HTML編寫代碼放在回調函數中,一旦獲得HTML響應,該函數便會執行。

我們的小型客戶端框架幾乎已完成,我們現在要做的就是定義模板HTML並將所有內容放在一起。將兩個HTML文件添加到Templates文件夾(請參見上面的解決方案資源管理器圖像),並將它們命名爲ProductGrid.htmlProductItem.html

ProductGrid.html

<div>
    {{#each Products}}
        <div class="ProductGridItem">
            <div>
                <div style="display:block;overflow:hidden;margin:0 auto;">
                    <a href="Product.aspx?id={{Id}}">
                    <img src="../../Content/Images/{{Image}}" /></a>
                </div>
            </div>
            <hr />
            <div><a href="Product.aspx?id={{Id}}"><b>{{Name}}</b></a></div>            
            <hr />
            <div style="text-align:center;"><b>Rs. {{Price}}</b></div>    
            <hr />      
            <div><a class="AddToCartGridButton" href="#">Add To Cart</a></div>  
        </div>
    {{/each}}
</div>

ProductItem.html

<div style="overflow:hidden;">
    <div class="ProductItemLeftDiv">
        <img src="../../Content/Images/{{Image}}" />
    </div>
    <div class="ProductItemRightDiv">
        <div><h2>{{Name}}</h2></div>
        <hr />
        <b>Price</b> {{Price}}
        <hr />
        {{SmallDescription}}
        <hr />
    </div>
</div>
<div id="divLargeDescription">
    <h3>Description</h3>
    <hr />
    {{LargeDescription}}
</div>
<div id="divAttributes">
    <h3>Attributes</h3>
    <hr />
    <table>
        {{#each Attributes}}
            <tr>
                <td><b>{{Name}}</b></td>
                <td>{{Value}}</td>
            </tr>    
        {{/each}}
    </table>
</div>
<div id="divReviews ">
    <h3>Reviews</h3>
    <hr />
    {{#each Reviews}}
        <span class="ReviewedBySpan"><b>{{ReviewedBy}}</b></span>
        <span class="RatingSpan">Rating: {{Rating}}</span>
        <div>{{ReviewText}}</div>
    {{/each}}
</div>

我們將需要HandlebarsjQuery庫文件。儘管jQuery不是強制性的,但我們大多數人最終還是會在某種程度上使用jQuery,以避免爲各種瀏覽器處理每種代碼方案。

下載最新的HandlebarsjQuery庫腳本並將其引用添加到母版頁中(或者,您可以使用cdn鏈接,因爲它們可能已經被瀏覽器緩存了)。我們還需要添加所有JavaScript文件的引用,並添加一個單獨的腳本塊來初始化我們的客戶端App對象。

<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="Master.master.cs" 
    Inherits="TestCart.Master" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>

    <link href="Content/Style/Style.css" rel="stylesheet"/>

    <script src="Content/Scripts/jquery-1.11.3.min.js"></script>
    <script src="Content/Scripts/handlebars-v4.0.2.js"></script>
    <script src="Content/Scripts/App.js"></script>
    <script src="Content/Scripts/Constants.js"></script>
    <script src="Content/Scripts/Events.js"></script>
    <script src="Content/Scripts/Service.js"></script>
    <script src="Content/Scripts/Templates.js"></script>

    <script>
        $(document).ready(function ()
        {
            if(!window.App) window.LoadApp();
        });
    </script>

    <asp:ContentPlaceHolder ID="head" runat="server">
    </asp:ContentPlaceHolder>

</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:ContentPlaceHolder ID="MainPlaceHolder" runat="server">
        
        </asp:ContentPlaceHolder>
    </div>
    </form>    
</body>
</html>

在上面的代碼中,如果瀏覽器內存中尚不存在App對象,我們將調用LoadApp函數來創建該對象。

將以下腳本塊添加到Default.aspx以加載產品表格:

<script>
        $(document).ready(function ()
        {
            var app = window.App;
            if (app)
            {
                app.Service.Call('GetAllProducts').then(function (data)
                {
                    $divGrid = $('#divGrid');

                    //Our callback
                    function setHtml(html)
                    {
                        $divGrid.html(html);
                    }

                    app.Templates.GetTemplateHtmlAjax('ProductGrid'
                    , { 'Products': data }, setHtml.bind(this));
                })
            }
        });
    </script>

上面的代碼應該很容易理解。我們正在調用GetAllProducts Web Api服務,並在成功回調中,正在從Templates模塊調用GetTemplateHtmlAjax函數以獲取輸出html字符串。該setHtml是一個回調函數,它作爲參數傳遞給GetTemplateHtmlAjax,並在HTML響應後調用。Web Api服務的路徑將從我們編碼爲Constants.js文件的元數據信息中解析。

這裏要注意的一件事是,我們在setHtml函數上使用bind 來保留函數範圍。如果您不知道這意味着什麼,那麼您應該瞭解有關JavaScript範圍的更多信息,因爲這不在本文的討論範圍之內(無雙關!)。

同樣,將以下腳本塊添加到Product.aspx文件中:

<script>
    $(document).ready(function ()
    {
        var app = window.App;
        if (app)
        {
            app.Service.Call('GetProduct', { 'Id': getParameterByName('id') }).then
            (function (data)
            {
                $divProduct = $('#divProduct');

                //Our callback
                function setHtml(html)
                {
                    $divProduct.html(html);
                }

                app.Templates.GetTemplateHtmlAjax('ProductItem', data, setHtml.bind(this));
            })
        }

        //To read id from the url
        //http://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript
        function getParameterByName(name)
        {
            name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
            var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
                results = regex.exec(location.search);
            return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
        }
    });
</script>

getParameterByName是否可以讀取url中任何查詢字符串參數的值。我們正在從網址中讀取id的值,然後將其作爲參數傳遞給Web api服務調用。

就是這樣,現在您所需要做的就是下載示例代碼並運行(如果尚未運行),並將此處給出的所有步驟與示例代碼相關聯,以更好地瞭解所有工作方式。我一般不會對HandlebarsWeb Api進行詳細介紹,因爲我的目的是提供一個工作樣本,您可以在該樣本上建立代碼,或者可以從不同的角度更好地瞭解實現時如何設計應用程序HTML模板。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章