zoukankan      html  css  js  c++  java
  • 对一个前端使用AngularJS后端使用ASP.NET Web API项目的理解(2)

    chsakell分享了一个前端使用AngularJS,后端使用ASP.NET Web API的项目。

    源码: https://github.com/chsakell/spa-webapi-angularjs
    文章:http://chsakell.com/2015/08/23/building-single-page-applications-using-web-api-and-angularjs-free-e-book/

    这里记录下对此项目的理解。分为如下几篇:

    ● 对一个前端使用AngularJS后端使用ASP.NET Web API项目的理解(1)--领域、Repository、Service

    ● 对一个前端使用AngularJS后端使用ASP.NET Web API项目的理解(2)--依赖倒置、Bundling、视图模型验证、视图模型和领域模型映射、自定义handler

    ● 对一个前端使用AngularJS后端使用ASP.NET Web API项目的理解(3)--主页面布局

    ● 对一个前端使用AngularJS后端使用ASP.NET Web API项目的理解(4)--Movie增改查以及上传图片

    依赖倒置

    我们注意到经常是把接口注入到构造函数中,然后调用接口方法,如何最终让接口的实现类执行相应的方法呢?这时候就应该请出Autofac了。通过NuGet安装:Autofac ASP.NET Web API 2.2 Integration

    在HomeCinema.Web中创建有关Autofac的配置类。

    namespace HomeCinema.Web.App_Start
    {
        public class AutofacWebapiConfig
        {
            public static IContainer Container;
            public static void Initialize(HttpConfiguration config)
            {
                Initialize(config, RegisterServices(new ContainerBuilder()));
            }
    
            public static void Initialize(HttpConfiguration config, IContainer container)
            {
                config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
            }
    
            private static IContainer RegisterServices(ContainerBuilder builder)
            {
                builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
    
                // EF HomeCinemaContext
                builder.RegisterType<HomeCinemaContext>()
                       .As<DbContext>()
                       .InstancePerRequest();
    
                builder.RegisterType<DbFactory>()
                    .As<IDbFactory>()
                    .InstancePerRequest();
    
                builder.RegisterType<UnitOfWork>()
                    .As<IUnitOfWork>()
                    .InstancePerRequest();
    
                builder.RegisterGeneric(typeof(EntityBaseRepository<>))
                       .As(typeof(IEntityBaseRepository<>))
                       .InstancePerRequest();
    
                ...
    
                Container = builder.Build();
    
                return Container;
            }
        }
    }

    再创建一个用来初始化Autofac。

    namespace HomeCinema.Web.App_Start
    {
        public class Bootstrapper
        {
            public static void Run()
            {
                // Configure Autofac
                AutofacWebapiConfig.Initialize(GlobalConfiguration.Configuration);
                ...
            }
        }
    }

    还需要在全局运行以上的静态方法Run.

    public class Global : HttpApplication
    {
        void Application_Start(object sender, EventArgs e)
        {
            var config = GlobalConfiguration.Configuration;
    
            ...
            Bootstrapper.Run();
            ...
            GlobalConfiguration.Configuration.EnsureInitialized();
            ...
        }
    }

    配置Bundling

    在ASP.NET MVC中提供了一种管理js,css文件的方法叫做Bunling,首先提供一个静态方法为BundleCollection集合添加元素。如下:

    namespace HomeCinema.Web.App_Start
    {
        public class BundleConfig
        {
            public static void RegisterBundles(BundleCollection bundles)
            {
                bundles.Add(new ScriptBundle("~/bundles/modernizr").Include(
                    "~/Scripts/Vendors/modernizr.js"));
    
                bundles.Add(new ScriptBundle("~/bundles/vendors").Include(
                    "~/Scripts/Vendors/jquery.js",
                    "~/Scripts/Vendors/bootstrap.js",
                    "~/Scripts/Vendors/toastr.js",
                    "~/Scripts/Vendors/jquery.raty.js",
                    "~/Scripts/Vendors/respond.src.js",
                    "~/Scripts/Vendors/angular.js",
                    "~/Scripts/Vendors/angular-route.js",
                    "~/Scripts/Vendors/angular-cookies.js",
                    "~/Scripts/Vendors/angular-validator.js",
                    "~/Scripts/Vendors/angular-base64.js",
                    "~/Scripts/Vendors/angular-file-upload.js",
                    "~/Scripts/Vendors/angucomplete-alt.min.js",
                    "~/Scripts/Vendors/ui-bootstrap-tpls-0.13.1.js",
                    "~/Scripts/Vendors/underscore.js",
                    "~/Scripts/Vendors/raphael.js",
                    "~/Scripts/Vendors/morris.js",
                    "~/Scripts/Vendors/jquery.fancybox.js",
                    "~/Scripts/Vendors/jquery.fancybox-media.js",
                    "~/Scripts/Vendors/loading-bar.js"
                    ));
    
                bundles.Add(new ScriptBundle("~/bundles/spa").Include(
                    "~/Scripts/spa/modules/common.core.js",
                    "~/Scripts/spa/modules/common.ui.js",
                    "~/Scripts/spa/app.js",
                    "~/Scripts/spa/services/apiService.js",
                    "~/Scripts/spa/services/notificationService.js",
                    "~/Scripts/spa/services/membershipService.js",
                    "~/Scripts/spa/services/fileUploadService.js",
                    "~/Scripts/spa/layout/topBar.directive.js",
                    "~/Scripts/spa/layout/sideBar.directive.js",
                    "~/Scripts/spa/layout/customPager.directive.js",
                    "~/Scripts/spa/directives/rating.directive.js",
                    "~/Scripts/spa/directives/availableMovie.directive.js",
                    "~/Scripts/spa/account/loginCtrl.js",
                    "~/Scripts/spa/account/registerCtrl.js",
                    "~/Scripts/spa/home/rootCtrl.js",
                    "~/Scripts/spa/home/indexCtrl.js",
                    "~/Scripts/spa/customers/customersCtrl.js",
                    "~/Scripts/spa/customers/customersRegCtrl.js",
                    "~/Scripts/spa/customers/customerEditCtrl.js",
                    "~/Scripts/spa/movies/moviesCtrl.js",
                    "~/Scripts/spa/movies/movieAddCtrl.js",
                    "~/Scripts/spa/movies/movieDetailsCtrl.js",
                    "~/Scripts/spa/movies/movieEditCtrl.js",
                    "~/Scripts/spa/controllers/rentalCtrl.js",
                    "~/Scripts/spa/rental/rentMovieCtrl.js",
                    "~/Scripts/spa/rental/rentStatsCtrl.js"
                    ));
    
                bundles.Add(new StyleBundle("~/Content/css").Include(
                    "~/content/css/site.css",
                    "~/content/css/bootstrap.css",
                    "~/content/css/bootstrap-theme.css",
                     "~/content/css/font-awesome.css",
                    "~/content/css/morris.css",
                    "~/content/css/toastr.css",
                    "~/content/css/jquery.fancybox.css",
                    "~/content/css/loading-bar.css"));
    
                BundleTable.EnableOptimizations = false;
            }
        }
    }

    在全局中启用Bundling。

    public class Global : HttpApplication
        {
            void Application_Start(object sender, EventArgs e)
            {
                ...
                BundleConfig.RegisterBundles(BundleTable.Bundles);
            }
        }

    在ASP.NET MVC的视图页按如下调用Bundle中的css或js文件。

    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/modernizr")
    @Scripts.Render("~/bundles/vendors")
    @Scripts.Render("~/bundles/spa")

    Styles.Render方法或Scripts.Render位于"Microsoft Asp.Net Web Optimization"组件的"System.Web.Optimization"命名空间内,先通过NuGet安装:Microsoft Asp.Net Web Optimization

    然后在Views文件夹的web.config中把"System.Web.Optimization"命名空间配置进去。

    <pages pageBaseType="System.Web.Mvc.WebViewPage">
      <namespaces>
        <add namespace="System.Web.Mvc" />
        <add namespace="System.Web.Mvc.Ajax" />
        <add namespace="System.Web.Mvc.Html" />
        <add namespace="System.Web.Routing" />
        <add namespace="HomeCinema.Web" />
        <add namespace="System.Web.Optimization" />
      </namespaces>
    </pages>

    视图模型的验证

    首先,通过NuGet安装:FluentValidation

    拿Customer的视图模型来说:

    namespace HomeCinema.Web.Models
    {
        [Bind(Exclude = "UniqueKey")]
        public class CustomerViewModel : IValidatableObject
        {
            public int ID { get; set; }
            public string FirstName { get; set; }
            public string LastName { get; set; }
            public string Email { get; set; }
            public string IdentityCard { get; set; }
            public Guid UniqueKey { get; set; }
            public DateTime DateOfBirth { get; set; }
            public string Mobile { get; set; }
            public DateTime RegistrationDate { get; set; }
    
            public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
            {
                var validator = new CustomerViewModelValidator();
                var result = validator.Validate(this);
                return result.Errors.Select(item => new ValidationResult(item.ErrorMessage, new[] { item.PropertyName }));
            }
        }
    }

    通过IValidatableObject的接口方法Validate,我们为CustomerViewModel定义了一个验证类CustomerViewModelValidator:

    namespace HomeCinema.Web.Infrastructure.Validators
    {
        public class CustomerViewModelValidator : AbstractValidator<CustomerViewModel>
        {
            public CustomerViewModelValidator()
            {
                RuleFor(customer => customer.FirstName).NotEmpty()
                    .Length(1, 100).WithMessage("First Name must be between 1 - 100 characters");
    
                RuleFor(customer => customer.LastName).NotEmpty()
                    .Length(1, 100).WithMessage("Last Name must be between 1 - 100 characters");
    
                RuleFor(customer => customer.IdentityCard).NotEmpty()
                    .Length(1, 100).WithMessage("Identity Card must be between 1 - 50 characters");
    
                RuleFor(customer => customer.DateOfBirth).NotNull()
                    .LessThan(DateTime.Now.AddYears(-16))
                    .WithMessage("Customer must be at least 16 years old.");
    
                RuleFor(customer => customer.Mobile).NotEmpty().Matches(@"^d{10}$")
                    .Length(10).WithMessage("Mobile phone must have 10 digits");
    
                RuleFor(customer => customer.Email).NotEmpty().EmailAddress()
                    .WithMessage("Enter a valid Email address");
    
            }
        }
    }

    以上的RuleFor方法等就是FluentValidation组件的Fluent API。

    视图模型和领域模型的映射

    首先,通过NuGet安装:Automapper

    继承AutoMapper的Profile类,用来把领域模型映射到视图模型。

    namespace HomeCinema.Web.Mappings
    {
        public class DomainToViewModelMappingProfile : Profile
        {
            public override string ProfileName
            {
                get { return "DomainToViewModelMappings"; }
            }
    
            protected override void Configure()
            {
                Mapper.CreateMap<Movie, MovieViewModel>()
                    .ForMember(vm => vm.Genre, map => map.MapFrom(m => m.Genre.Name))
                    .ForMember(vm => vm.GenreId, map => map.MapFrom(m => m.Genre.ID))
                    .ForMember(vm => vm.IsAvailable, map => map.MapFrom(m => m.Stocks.Any(s => s.IsAvailable)))
                    .ForMember(vm => vm.NumberOfStocks, map => map.MapFrom(m => m.Stocks.Count))
                    .ForMember(vm => vm.Image, map => map.MapFrom(m => string.IsNullOrEmpty(m.Image) == true ? "unknown.jpg" : m.Image));
    
                Mapper.CreateMap<Genre, GenreViewModel>()
                    .ForMember(vm => vm.NumberOfMovies, map => map.MapFrom(g => g.Movies.Count()));
                // code omitted
                Mapper.CreateMap<Customer, CustomerViewModel>();
    
                Mapper.CreateMap<Stock, StockViewModel>();
    
                Mapper.CreateMap<Rental, RentalViewModel>();
            }
        }
    }

    再写一个继承AutoMapper的Profile类,用来把视图模型映射到领域模型。

    namespace HomeCinema.Web.Mappings
    {
        public class ViewModelToDomainMappingProfile : Profile
        {
            public override string ProfileName
            {
                get { return "ViewModelToDomainMappings"; }
            }
    
            protected override void Configure()
            {
                Mapper.CreateMap<MovieViewModel, Movie>()
                    //.ForMember(m => m.Image, map => map.Ignore())
                    .ForMember(m => m.Genre, map => map.Ignore());
            }
        }
    }

    接着定义一个有关AutoMapper的配置类:

    namespace HomeCinema.Web.Mappings
    {
        public class AutoMapperConfiguration
        {
            public static void Configure()
            {
                Mapper.Initialize(x =>
                {
                    x.AddProfile<DomainToViewModelMappingProfile>();
                });
            }
        }
    }

    封装一个类调用AutoMapper的配置:

    namespace HomeCinema.Web.App_Start
    {
        public class Bootstrapper
        {
            public static void Run()
            {
                // Configure Autofac
                AutofacWebapiConfig.Initialize(GlobalConfiguration.Configuration);
                //Configure AutoMapper
                AutoMapperConfiguration.Configure();
            }
        }
    }

    最后,在全局文件中运行Run静态方法,略去。

    自定义HttpMessageHandler

    在System.Net.Http命名空间中定义了一个抽象类自定义HttpMessageHandler,其中定义了一个SendAsync方法,用来接收请求,返回响应,以异步的方式:

    protected internal abstract Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);

    HttpMessageHandler还有一个继承类DelegatingHandler,这里,就来继承DelegatingHandler,实现自定义handler。

    namespace HomeCinema.Web.MessageHandlers
    {
        public class HomeCinemaAuthHandler : DelegatingHandler
        {
            IEnumerable<string> authHeaderValues = null;
            protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
            {
                try
                {
                    request.Headers.TryGetValues("Authorization",out authHeaderValues);
                    if(authHeaderValues == null)
                        return base.SendAsync(request, cancellationToken); // cross fingers
    
                    var tokens = authHeaderValues.FirstOrDefault();
                    tokens = tokens.Replace("Basic","").Trim();
                    if (!string.IsNullOrEmpty(tokens))
                    {
                        byte[] data = Convert.FromBase64String(tokens);
                        string decodedString = Encoding.UTF8.GetString(data);
                        string[] tokensValues = decodedString.Split(':');
    
                        //扩展方法GetMembershipService
                        var membershipService = request.GetMembershipService();
    
                        var membershipCtx = membershipService.ValidateUser(tokensValues[0], tokensValues[1]);
                        if (membershipCtx.User != null)
                        {
                            IPrincipal principal = membershipCtx.Principal;
                            Thread.CurrentPrincipal = principal;
                            HttpContext.Current.User = principal;
                        }
                        else // Unauthorized access - wrong crededentials
                        {
                            var response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
                            var tsc = new TaskCompletionSource<HttpResponseMessage>();
                            tsc.SetResult(response);
                            return tsc.Task;
                        }
                    }
                    else
                    {
                        var response = new HttpResponseMessage(HttpStatusCode.Forbidden);
                        var tsc = new TaskCompletionSource<HttpResponseMessage>();
                        tsc.SetResult(response);
                        return tsc.Task;
                    }
                    return base.SendAsync(request, cancellationToken);
                }
                catch
                {
                    var response = new HttpResponseMessage(HttpStatusCode.Forbidden);
                    var tsc = new TaskCompletionSource<HttpResponseMessage>();
                    tsc.SetResult(response);
                    return tsc.Task;
                }
            }
        }
    }

    以上,request.GetMembershipService()方法使基于HttpRequestMessage的扩展方法,用来从依赖倒置中获取某个接口。

    namespace HomeCinema.Web.Infrastructure.Extensions
    {
        public static class RequestMessageExtensions
        {
            internal static IMembershipService GetMembershipService(this HttpRequestMessage request)
            {
                return request.GetService<IMembershipService>();
            }
    
            internal static IEntityBaseRepository<T> GetDataRepository<T>(this HttpRequestMessage request) where T : class, IEntityBase, new()
            {
                return request.GetService<IEntityBaseRepository<T>>();
            }
    
            private static TService GetService<TService>(this HttpRequestMessage request)
            {
                IDependencyScope dependencyScope = request.GetDependencyScope();
                TService service = (TService)dependencyScope.GetService(typeof(TService));
    
                return service;
            }
        }
    }

    最后,在WebApi.config中配置。

    namespace HomeCinema.Web
    {
        public static class WebApiConfig
        {
            public static void Register(HttpConfiguration config)
            {
                // Web API configuration and services
                config.MessageHandlers.Add(new HomeCinemaAuthHandler());
    
                // Web API routes
                config.MapHttpAttributeRoutes();
    
                config.Routes.MapHttpRoute(
                    name: "DefaultApi",
                    routeTemplate: "api/{controller}/{id}",
                    defaults: new { id = RouteParameter.Optional }
                );
            }
        }
    }

    待续~

  • 相关阅读:
    (Lineup the Dominoes筛子)三维状压
    Halloween Costumes 玄学题
    jQuery之动画
    javascript之位置
    javascript之事件
    jQuery之DOM
    jQuery之选择器
    jQuery简介
    javascript之Bom简介
    javascript之DOM操作
  • 原文地址:https://www.cnblogs.com/darrenji/p/4945703.html
Copyright © 2011-2022 走看看