使用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模板。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值