葡萄城控件技术团队 阅读(21) 评论(0)

今天来为大家介绍如何在 ASP.NET MVC 中集成 AngularJS 的最后一部分内容。

调试路由表 - HTML 缓存清除

就在我以为示例应用程序完成之后,我意识到,我必须提供两个版本的路由表:一个运行在调试模式的应用程序下和一个运行在发布模式的应用程序下。在调试模式下,JavaScript 文件在未使用压缩功能的情况下会被下载。如果想要调试并在 JavaScript 控制器中设置断点,这是必须的。事实上,路由表的产生版本也出现了一些挑战,由于产生路由代码使用的是 JavaScript 捆绑,但是在 Visual Studio 下,捆绑无法一步一步执行调试,所以我无法调试这些代码。我不得不将一些 console.log 命令和一些 JavaScript 语句警报一起开发并测试来生成路由表。

两个路由版本都包含的事情是:支持 HTML 文件的缓存,就像捆绑和 JavaScript,你还需要提供一个附属在 HTML Angular 视图上的序列号。在调试和生成路由代码两种情况下,嵌入版本号将会从 applicationConfigurationProvder 中推出并附属在缓存的 HTML 路径中。

// CodeProjectRouting-debug.js

angular.module("codeProject").config(
['$routeProvider', '$locationProvider', 'applicationConfigurationProvider',

    function ($routeProvider, $locationProvider, applicationConfigurationProvider) {

    this.getApplicationVersion = function () {
        var applicationVersion = applicationConfigurationProvider.getVersion();
        return applicationVersion;
    }

    var baseSiteUrlPath = $("base").first().attr("href");
   
    $routeProvider.when('/:section/:tree',
    {
        templateUrl: function (rp) { return baseSiteUrlPath + 'views/' + 
                     rp.section + '/' + rp.tree + '.html?v=' + this.getApplicationVersion(); },

        resolve: {

            load: ['$q', '$rootScope', '$location', function ($q, $rootScope, $location) {

                var path = $location.path().split("/");
                var directory = path[1];
                var controllerName = path[2];
               
                var controllerToLoad = "Views/" + directory + "/" + 
                    controllerName + "Controller.js?v=" + this.getApplicationVersion();

                var deferred = $q.defer();

                require([controllerToLoad], function () {
                    $rootScope.$apply(function () {
                        deferred.resolve();
                    });
                });

                return deferred.promise;
              
            }]
        }

    });

    $routeProvider.when('/:section/:tree/:id',
    {
        templateUrl: function (rp) { return baseSiteUrlPath + 'views/' + 
                     rp.section + '/' + rp.tree + '.html?v=' + this.getApplicationVersion(); },

        resolve: {

            load: ['$q', '$rootScope', '$location', function ($q, $rootScope, $location) {

                var path = $location.path().split("/");
                var directory = path[1];
                var controllerName = path[2];

                var controllerToLoad = "Views/" + directory + "/" + controllerName + 
                                       "Controller.js?v=" + this.getApplicationVersion();

                var deferred = $q.defer();

                require([controllerToLoad], function () {
                    $rootScope.$apply(function () {
                        deferred.resolve();
                    });
                });

                return deferred.promise;

            }]
        }

    });

    $routeProvider.when('/',
    {

        templateUrl: function (rp) { return baseSiteUrlPath + 'views/Home/Index.html?v=' + 
                                     this.getApplicationVersion(); },

        resolve: {

            load: ['$q', '$rootScope', '$location', function ($q, $rootScope, $location) {

                var controllerToLoad = "Views/Home/IndexController.js?v=" + 
                                        this.getApplicationVersion();

                var deferred = $q.defer();

                require([controllerToLoad], function () {
                    $rootScope.$apply(function () {
                        deferred.resolve();
                    });
                });

                return deferred.promise;

            }]
        }

    });

    $locationProvider.html5Mode(true);  

}]);

 

测试浏览器缓存

当开发一个 Web 应用程序时,一件你想要做的事情是:测试所有浏览器的缓存和缓存清除功能。你将会想要确保你的应用内容被正确下载并缓存,这些内容会在页面请求之后出现。

你将会对你的内容做很多改变,来重建你的应用,以确保清除缓存和内容被再次下载时新版本号的问题能够解决。

为了测试这一切,我在发布模式下通过 Chrome 浏览器来运行应用,并点击 F12 来打开网络标签。在这里,你可以看见下载你的应用花费了多少时间和来自于服务器的内容,或者是浏览器的缓存。你甚至可以看到捆绑包的下载情况。

最终,你点击你的应用程序的所有页面,你会发现,所有的内容是从浏览器缓存来了,这是单页应用的美丽之处。你的所有内容都会以获取更大的缓存响应时间为结束,唯一要做的点击 web 服务器来从呈现在页面中的 RESTful Web API 来返回 JSON 格式的数据。

 

其它有趣的点 

其它实例应用中有趣的点,还包括执行在服务器端的 .NET 库。对于数据的有效性输入,应用在业务处理中使用了 FluentValidation 库。 

FluentValidation 是 .NET 的一个使用流畅的界面和 lambda 表达式建立验证规则的小型验证库。

当试图创建示例应用程序的客户时,客户代码和公司名称为必填项。示例应用程序的业务层管理有效性,使用了 FluentValidation 库验证。通过将一个密集的客户对象传入到 CreateCustomer 方法中,对象上的属性可以通过设置的 FluentValidation 表达式的业务规则被验证。如果该业务对象验证失败,业务层可以从验证库返回错误的集合,并发送错误收集结果到客户端,以便浏览器端错误信息的呈现。

public Customer CreateCustomer(Customer customer, out TransactionalInformation transaction)
{
     transaction = new TransactionalInformation();

     try
     {
         CustomerBusinessRules customerBusinessRules = new CustomerBusinessRules();
         ValidationResult results = customerBusinessRules.Validate(customer);

         bool validationSucceeded = results.IsValid;
         IList<ValidationFailure> failures = results.Errors;

         if (validationSucceeded == false)
         {
             transaction = ValidationErrors.PopulateValidationErrors(failures);
             return customer;
         }

         _customerDataService.CreateSession();
         _customerDataService.BeginTransaction();
         _customerDataService.CreateCustomer(customer);
         _customerDataService.CommitTransaction(true);

         transaction.ReturnStatus = true;
         transaction.ReturnMessage.Add("Customer successfully created.");

    }
    catch (Exception ex)
    {
         string errorMessage = ex.Message;
         transaction.ReturnMessage.Add(errorMessage);
         transaction.ReturnStatus = false;
    }
    finally
    {
        _customerDataService.CloseSession();
    }

    return customer;
}

下面是定义了客户对象的业务规则类,使用 FluentValidation 库,定义一组 lambda 表达式并创建业务规则和每个验证相关的错误信息。该 FluentValidation 库使用了一组不同的 lambda 表达式来验证业务对象或实体。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using FluentValidation;
using CodeProject.Business.Entities;
using System.Configuration;
using CodeProject.Interfaces;

namespace CodeProject.Business
{
    public class CustomerBusinessRules : AbstractValidator<Customer>
    {

        public CustomerBusinessRules()
        {       
            RuleFor(c => c.CompanyName).NotEmpty().WithMessage("Company Name is required.");
            RuleFor(c => c.CustomerCode).NotEmpty().WithMessage("Customer Code is required.");   
        }

    }

}

在示例应用程序中另一个值得注意的点,是使用 Ninject 库的依赖注入的实现。当 Ninject从NuGet 安装时,一个配置文件 NinjectWebCommon.cs 就会为你创建。在这里,你可以告诉 Ninject 库当应用的某些部分被执行时,要创建哪些对象,比如在 Web API 服务中。在下面的 RegisterServices ,我告诉 Ninject 分配客户数据​​服务和产品数据服务到他们各自实现的接口中。这就告诉了 Ninject 去哪儿加载匹配的 dll 引用。

namespace CodeProject.Portal.App_Start
{
    using System;
    using System.Web;

    using Microsoft.Web.Infrastructure.DynamicModuleHelper;

    using Ninject;
    using Ninject.Web.Common;

    public static class NinjectWebCommon 
    {
        private static readonly Bootstrapper bootstrapper = new Bootstrapper();

        /// <summary>
        /// Starts the application
        /// </summary>
        public static void Start() 
        {
            DynamicModuleUtility.RegisterModule(typeof(OnePerRequestHttpModule));
            DynamicModuleUtility.RegisterModule(typeof(NinjectHttpModule));
            bootstrapper.Initialize(CreateKernel);
        }
        
        /// <summary>
        /// Stops the application.
        /// </summary>
        public static void Stop()
        {
            bootstrapper.ShutDown();
        }
        
        /// <summary>
        /// Creates the kernel that will manage your application.
        /// </summary>
        /// <returns>The created kernel.</returns>
        private static IKernel CreateKernel()
        {
            var kernel = new StandardKernel();
            try
            {
                kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
                kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();

                RegisterServices(kernel);
                System.Web.Http.GlobalConfiguration.Configuration.DependencyResolver = 
                                new Ninject.Web.WebApi.NinjectDependencyResolver(kernel);
                return kernel;
            }
            catch
            {
                kernel.Dispose();
                throw;
            }
        }

        /// <summary>
        /// Load your modules or register your services here!
        /// </summary>
        /// <param name="kernel">The kernel.</param>
        private static void RegisterServices(IKernel kernel)
        {
            kernel.Bind<CodeProject.Interfaces.ICustomerDataService>().
                                    To<CodeProject.Data.EntityFramework.CustomerDataService>();
            kernel.Bind<CodeProject.Interfaces.IProductDataService>().
                                    To<CodeProject.Data.EntityFramework.ProductDataService>();

        }        
    }
}

使用 Ninject 数据注解[注入],你可以告诉 Ninject 库何时何地实例化你的对象。在下面的网页 API 服务,客户数据​​服务就是由 Ninject 创建的。由于客户业务服务依赖于客户数据的​​服务来访问数据,客户数据​​服务应该被注入客户业务服务的构造函数中。所有这一切都是通过创建客户数据​​的服务接口,然后简单地实现了客户数据​​服务接口来完成的。依赖注入是功能强大的,因为它创造应用代码彼此分离的耦合度低的应用层。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using CodeProject.Portal.Models;
using CodeProject.Business.Entities;
using CodeProject.Business;
using CodeProject.Interfaces;
using Ninject;

namespace CodeProject.Portal.WebApiControllers
{
    [RoutePrefix("api/CustomerService")]
    public class CustomerServiceController : ApiController
    {

        [Inject]
        public ICustomerDataService _customerDataService { get; set; }

        /// <summary>
        /// Create Customer
        /// </summary>
        /// <param name="request"></param>
        /// <param name="customerViewModel"></param>
        /// <returns></returns>
        [Route("CreateCustomer")]     
        [HttpPost]
        public HttpResponseMessage CreateCustomer(HttpRequestMessage request, 
                                   [FromBody] CustomerViewModel customerViewModel)
        {
            TransactionalInformation transaction;

            Customer customer = new Customer();
            customer.CompanyName = customerViewModel.CompanyName;
            customer.ContactName = customerViewModel.ContactName;
            customer.ContactTitle = customerViewModel.ContactTitle;
            customer.CustomerCode = customerViewModel.CustomerCode;
            customer.Address = customerViewModel.Address;
            customer.City = customerViewModel.City;
            customer.Region = customerViewModel.Region;
            customer.PostalCode = customerViewModel.PostalCode;
            customer.Country = customerViewModel.Country;
            customer.PhoneNumber = customerViewModel.PhoneNumber;
            customer.MobileNumber = customerViewModel.MobileNumber;

            CustomerBusinessService customerBusinessService = 
                                    new CustomerBusinessService(_customerDataService);

            customerBusinessService.CreateCustomer(customer, out transaction);
            if (transaction.ReturnStatus == false)
            {                
                customerViewModel.ReturnStatus = false;
                customerViewModel.ReturnMessage = transaction.ReturnMessage;
                customerViewModel.ValidationErrors = transaction.ValidationErrors;

                var responseError = Request.CreateResponse<CustomerViewModel>
                                    (HttpStatusCode.BadRequest, customerViewModel);
                return responseError;
              
            }

            customerViewModel.CustomerID = customer.CustomerID;
            customerViewModel.ReturnStatus = true;
            customerViewModel.ReturnMessage = transaction.ReturnMessage;

            var response = Request.CreateResponse<CustomerViewModel>
                           (HttpStatusCode.OK, customerViewModel);
            return response;

        }

 

结论

在 ASP.NET MVC 和 ASP.NET 捆绑中集成 AngularJS 似乎是一个开始时看起来像挑战的尝试。在试验和失败的每次迭代中,这个挑战变得逐渐变得不那么难。我只是想使所有这些集成起来工作,我不会停止努力。

你可以争论在 ASP.NET 中使用捆绑和缩功能和在 Grunt 与 Gulp 部分使用流行的压缩工具,其各自的优点。如果你是一个无需学习另外技术和工具并且喜欢点击按钮来发布你的 Visual Studio 的微软开发人员,你很可能会想使用 ASP.NET 捆绑功能。我发现这个功能确实是我想要的,它只是花费了我很长的时间来弄清楚如何将它与 AngularJS 集成。

在这些天里,有很多技术可以来写。我以后的一些文章中可能包括 AngularJS 2 和 MEAN 的其余部分,包括 Node.js 的,Express 和 MongoDB。

还有一些包含在最新发布的 Visual Studio 2015 中的一些使用 Apache Cordov 开发的移动应用。这种先进的 HTML 混合的移动应用框架很可能可以和 Apache Cordov 一起工作使用。据说 Ionic 能够使用 HTML 和 AngularJS ,并且可以很容易的建立大规模交互式的移动应用。敬请期待!