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(, ).WithMessage("First Name must be between 1 - 100 characters"); RuleFor(customer => customer.LastName).NotEmpty()
.Length(, ).WithMessage("Last Name must be between 1 - 100 characters"); RuleFor(customer => customer.IdentityCard).NotEmpty()
.Length(, ).WithMessage("Identity Card must be between 1 - 50 characters"); RuleFor(customer => customer.DateOfBirth).NotNull()
.LessThan(DateTime.Now.AddYears(-))
.WithMessage("Customer must be at least 16 years old."); RuleFor(customer => customer.Mobile).NotEmpty().Matches(@"^\d{10}$")
.Length().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[], tokensValues[]);
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 }
);
}
}
}

待续~

对一个前端使用AngularJS后端使用ASP.NET Web API项目的理解(2)的更多相关文章

  1. 对一个前端使用AngularJS后端使用ASP.NET Web API项目的理解(4)

    chsakell分享了一个前端使用AngularJS,后端使用ASP.NET Web API的项目. 源码: https://github.com/chsakell/spa-webapi-angula ...

  2. 对一个前端使用AngularJS后端使用ASP.NET Web API项目的理解(3)

    chsakell分享了一个前端使用AngularJS,后端使用ASP.NET Web API的项目. 源码: https://github.com/chsakell/spa-webapi-angula ...

  3. 对一个前端使用AngularJS后端使用ASP.NET Web API项目的理解(1)

    chsakell分享了一个前端使用AngularJS,后端使用ASP.NET Web API的项目. 源码: https://github.com/chsakell/spa-webapi-angula ...

  4. 购物车Demo,前端使用AngularJS,后端使用ASP.NET Web API(3)--Idetity,OWIN前后端验证

    原文:购物车Demo,前端使用AngularJS,后端使用ASP.NET Web API(3)--Idetity,OWIN前后端验证 chsakell分享了前端使用AngularJS,后端使用ASP. ...

  5. 购物车Demo,前端使用AngularJS,后端使用ASP.NET Web API(2)--前端,以及前后端Session

    原文:购物车Demo,前端使用AngularJS,后端使用ASP.NET Web API(2)--前端,以及前后端Session chsakell分享了前端使用AngularJS,后端使用ASP.NE ...

  6. 购物车Demo,前端使用AngularJS,后端使用ASP.NET Web API(1)--后端

    原文:购物车Demo,前端使用AngularJS,后端使用ASP.NET Web API(1)--后端 chsakell分享了前端使用AngularJS,后端使用ASP.NET Web API的购物车 ...

  7. 对一个前端AngularJS,后端OData,ASP.NET Web API案例的理解

    依然chsakell,他写了一篇前端AngularJS,后端OData,ASP.NET Web API的Demo,关于OData在ASP.NET Web API中的正删改查没有什么特别之处,但在前端调 ...

  8. 在ASP.NET Web API项目中使用Hangfire实现后台任务处理

    当前项目中有这样一个需求:由前端用户的一个操作,需要触发到不同设备的消息推送.由于推送这个具体功能,我们采用了第三方的服务.而这个服务调用有时候可能会有延时,为此,我们希望将消息推送与用户前端操作实现 ...

  9. 如何创建一个Asp .Net Web Api项目

    1.点击文件=>新建=>项目 2.创建一个Asp .NET Web项目 3.选择Empty,然后选中下面的MVC和Web Api,也可以直接选择Web Api选项,注意将身份验证设置为无身 ...

随机推荐

  1. JVM 垃圾回收算法及案例分析

    一. 在说垃圾回收算法之前,先谈谈JVM怎样确定哪些对象是“垃圾”. 1.引用计数器算法: 引用计数器算法是给每个对象设置一个计数器,当有地方引用这个对象的时候,计数器+1,当引用失效的时候,计数器- ...

  2. .NetCore Linux环境下安装InfluxDB以及配置设置

    Linux下安装 确定需要安装的版本,我的linux是干净的,所以我需要先安装wget yum -y install wget 下载安装 wget https://dl.influxdata.com/ ...

  3. linux文本编码格式转化 字幕处理

    在处理字幕的时候,linux的编码格式转换很烦. 步骤: 用python先判断 其编码,再用iconv 转编码,再用awk处理格式. file不能判断吗?file有时不准. 1.python判断编码 ...

  4. HDU - 1525

    题意:给你两个数,a,b,有两个人轮流进行一次操作, 每次操作可以将大的数减去k倍的小的数,最后不能操作的人输了,问你谁赢了. 思路:我们可以用辗转相除法求出对于每一个状态可以改变几次,这样问题就变成 ...

  5. kebab HDU2883

    题意:现在有n个人要烤肉,有m个烤肉架,然后给出每个人的烤肉开始时间si,结束时间ei,以及要烤肉的串数num,还有拷一串的时间ti,然后问你能不能满足所有人的要求. 为3572的进阶题 每个人为一个 ...

  6. 007.MySQL-Keepalived搭配脚本01

    vim /etc/keepalived/check_MySQL.sh #!/bin/bash MYSQL=/usr/bin/mysql MYSQL_HOST=localhost MYSQL_USER= ...

  7. JavaScript 对象Array,Map,Set使用

    for(int i = 0 :i < 3 ;i++ ){ //[重点说三遍] 在说明每个对象的用法之前,首先说明 JavaScript 对象的使用一定要注意浏览器的兼容性问题!尤其是IE的版本! ...

  8. [同步脚本]mysql-elasticsearch同步

    公司项目搜索部分用的elasticsearch,那么这两个之间的数据同步就是一个问题. 网上找了几个包,但都有各自的缺点,最后决定还是自己写一个脚本,大致思路如下: 1.在死循环中不断的select指 ...

  9. normalizr实践使用(个人总结,仅供参考)

    # normalizr实践使用 原数据 (自编数据,本数据仅供参考) var aaaObj ={ "id" : "0000000000000000000000000000 ...

  10. 简单的Python 火车抢票程序

    当你想查询一下火车票信息的时候,你还在上12306官网吗?或是打开你手机里的APP?下面让我们来用Python写一个命令行版的火车票查看器, 只要在命令行敲一行命令就能获得你想要的火车票信息!如果你刚 ...