博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
asp.net web api 2.2 基础框架(带例子)
阅读量:6566 次
发布时间:2019-06-24

本文共 33316 字,大约阅读时间需要 111 分钟。

链接:

简介

这个是我自己编写的asp.net web api 2.2的基础框架,使用了Entity Framework 6.2(beta)作为ORM。

该模板主要采用了 Unit of Work 和 Repository 模式,使用autofac进行控制反转(ioc)。

记录Log采用的是NLog。

 

结构

项目列表如下图:

该启动模板为多层结构,其结构如下图:

 

开发流程

1. 创建model

在LegacyApplication.Models项目里建立相应的文件夹作为子模块,然后创建model,例如Nationality.cs:

using System.ComponentModel.DataAnnotations.Schema;using System.Data.Entity.Infrastructure.Annotations;using LegacyApplication.Shared.Features.Base;namespace LegacyApplication.Models.HumanResources{    public class Nationality : EntityBase    {        public string Name { get; set; }    }    public class NationalityConfiguration : EntityBaseConfiguration
{ public NationalityConfiguration() { ToTable("hr.Nationality"); Property(x => x.Name).IsRequired().HasMaxLength(50); Property(x => x.Name).HasMaxLength(50).HasColumnAnnotation( IndexAnnotation.AnnotationName, new IndexAnnotation(new IndexAttribute { IsUnique = true })); } }}
View Code

 

所建立的model需要使用EntityBase作为基类,EntityBase有几个业务字段,包括CreateUser,CreateTime,UpdateUser,UpdateTime,LastAction。EntityBase代码如下:

using System;namespace LegacyApplication.Shared.Features.Base{    public class EntityBase : IEntityBase    {        public EntityBase(string userName = "匿名")        {            CreateTime = UpdateTime = DateTime.Now;            LastAction = "创建";            CreateUser = UpdateUser = userName;        }        public int Id { get; set; }        public DateTime CreateTime { get; set; }        public DateTime UpdateTime { get; set; }        public string CreateUser { get; set; }        public string UpdateUser { get; set; }        public string LastAction { get; set; }        public int Order { get; set; }    }}
View Code

 

model需要使用Fluent Api来配置数据库的映射属性等,按约定使用Model名+Configuration作为fluent api的类的名字,并需要继承EntityBaseConfiguration<T>这个类,这个类对EntityBase的几个属性进行了映射配置,其代码如下:

using System.Data.Entity.ModelConfiguration;namespace LegacyApplication.Shared.Features.Base{    public class EntityBaseConfiguration
: EntityTypeConfiguration
where T : EntityBase { public EntityBaseConfiguration() { HasKey(e => e.Id); Property(x => x.CreateTime).IsRequired(); Property(x => x.UpdateTime).IsRequired(); Property(x => x.CreateUser).IsRequired().HasMaxLength(50); Property(x => x.UpdateUser).IsRequired().HasMaxLength(50); Property(x => x.LastAction).IsRequired().HasMaxLength(50); } }}
View Code

 

1.1 自成树形的Model

自成树形的model是指自己和自己成主外键关系的Model(表),例如菜单表或者部门表的设计有时候是这样的,下面以部门为例:

using System.Collections.Generic;using LegacyApplication.Shared.Features.Tree;namespace LegacyApplication.Models.HumanResources{    public class Department : TreeEntityBase
{ public string Name { get; set; } public ICollection
Employees { get; set; } } public class DepartmentConfiguration : TreeEntityBaseConfiguration
{ public DepartmentConfiguration() { ToTable("hr.Department"); Property(x => x.Name).IsRequired().HasMaxLength(100); } }}
View Code

 

与普通的Model不同的是,它需要继承的是TreeEntityBase<T>这个基类,TreeEntityBase<T>的代码如下:

using System.Collections.Generic;using LegacyApplication.Shared.Features.Base;namespace LegacyApplication.Shared.Features.Tree{    public class TreeEntityBase
: EntityBase, ITreeEntity
where T: TreeEntityBase
{ public int? ParentId { get; set; } public string AncestorIds { get; set; } public bool IsAbstract { get; set; } public int Level => AncestorIds?.Split('-').Length ?? 0; public T Parent { get; set; } public ICollection
Children { get; set; } }}
View Code

其中ParentId,Parent,Children这几个属性是树形关系相关的属性,AncestorIds定义为所有祖先Id层级别连接到一起的一个字符串,需要自己实现。然后Level属性是通过AncestorIds这个属性自动获取该Model在树形结构里面的层级。

该Model的fluent api配置类需要继承的是TreeEntityBaseConfiguration<T>这个类,代码如下:

using System.Collections.Generic;using LegacyApplication.Shared.Features.Base;namespace LegacyApplication.Shared.Features.Tree{    public class TreeEntityBaseConfiguration
: EntityBaseConfiguration
where T : TreeEntityBase
{ public TreeEntityBaseConfiguration() { Property(x => x.AncestorIds).HasMaxLength(200); Ignore(x => x.Level); HasOptional(x => x.Parent).WithMany(x => x.Children).HasForeignKey(x => x.ParentId).WillCascadeOnDelete(false); } }}
View Code

针对树形结构的model,我还做了几个简单的Extension Methods,代码如下:

using System;using System.Collections.Generic;using System.Linq;namespace LegacyApplication.Shared.Features.Tree{    public static class TreeExtensions    {        ///         /// 把树形结构数据的集合转化成单一根结点的树形结构数据        ///         /// 
树形结构实体
/// 树形结构实体的集合 ///
树形结构实体的根结点
public static TreeEntityBase
ToSingleRoot
(this IEnumerable
> items) where T : TreeEntityBase
{ var all = items.ToList(); if (!all.Any()) { return null; } var top = all.Where(x => x.ParentId == null).ToList(); if (top.Count > 1) { throw new Exception("树的根节点数大于1个"); } if (top.Count == 0) { throw new Exception("未能找到树的根节点"); } TreeEntityBase
root = top.Single(); Action
> findChildren = null; findChildren = current => { var children = all.Where(x => x.ParentId == current.Id).ToList(); foreach (var child in children) { findChildren(child); } current.Children = children as ICollection
; }; findChildren(root); return root; } ///
/// 把树形结构数据的集合转化成多个根结点的树形结构数据 /// ///
树形结构实体
///
树形结构实体的集合 ///
多个树形结构实体根结点的集合
public static List
> ToMultipleRoots
(this IEnumerable
> items) where T : TreeEntityBase
{ List
> roots; var all = items.ToList(); if (!all.Any()) { return null; } var top = all.Where(x => x.ParentId == null).ToList(); if (top.Any()) { roots = top; } else { throw new Exception("未能找到树的根节点"); } Action
> findChildren = null; findChildren = current => { var children = all.Where(x => x.ParentId == current.Id).ToList(); foreach (var child in children) { findChildren(child); } current.Children = children as ICollection
; }; roots.ForEach(findChildren); return roots; } ///
/// 作为父节点, 取得树形结构实体的祖先ID串 /// ///
树形结构实体
///
父节点实体 ///
public static string GetAncestorIdsAsParent
(this T parent) where T : TreeEntityBase
{ return string.IsNullOrEmpty(parent.AncestorIds) ? parent.Id.ToString() : (parent.AncestorIds + "-" + parent.Id); } }}
View Code

 

2. 把Model加入到DbContext里面

建立完Model后,需要把Model加入到Context里面,下面是CoreContext的代码:

using System;using System.Data.Entity;using System.Data.Entity.ModelConfiguration.Conventions;using System.Diagnostics;using System.Reflection;using LegacyApplication.Database.Infrastructure;using LegacyApplication.Models.Core;using LegacyApplication.Models.HumanResources;using LegacyApplication.Models.Work;using LegacyApplication.Shared.Configurations;namespace LegacyApplication.Database.Context{    public class CoreContext : DbContext, IUnitOfWork    {        public CoreContext() : base(AppSettings.DefaultConnection)        {            //System.Data.Entity.Database.SetInitializer
(null);#if DEBUG Database.Log = Console.Write; Database.Log = message => Trace.WriteLine(message);#endif } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Conventions.Remove
(); modelBuilder.Conventions.Remove
(); //去掉默认开启的级联删除 modelBuilder.Configurations.AddFromAssembly(Assembly.GetAssembly(typeof(UploadedFile))); } //Core public DbSet
UploadedFiles { get; set; } //Work public DbSet
InternalMails { get; set; } public DbSet
InternalMailTos { get; set; } public DbSet
InternalMailAttachments { get; set; } public DbSet
Todos { get; set; } public DbSet
Schedules { get; set; } //HR public DbSet
Departments { get; set; } public DbSet
Employees { get; set; } public DbSet
JobPostLevels { get; set; } public DbSet
JobPosts { get; set; } public DbSet
AdministrativeLevels { get; set; } public DbSet
TitleLevels { get; set; } public DbSet
Nationalitys { get; set; } }}
View Code

其中“modelBuilder.Configurations.AddFromAssembly(Assembly.GetAssembly(typeof(UploadedFile)));” 会把UploadFile所在的Assembly(也就是LegacyApplication.Models这个项目)里面所有的fluent api配置类(EntityTypeConfiguration的派生类)全部加载进来。

这里说一下CoreContext,由于它派生与DbContext,而DbContext本身就实现了Unit of Work 模式,所以我做Unit of work模式的时候,就不考虑重新建立一个新类作为Unit of work了,我从DbContext抽取了几个方法,提炼出了IUnitofWork接口,代码如下:

using System;using System.Threading;using System.Threading.Tasks;namespace LegacyApplication.Database.Infrastructure{    public interface IUnitOfWork: IDisposable    {        int SaveChanges();        Task
SaveChangesAsync(CancellationToken cancellationToken); Task
SaveChangesAsync(); }}
View Code

用的时候IUnitOfWork就是CoreContext的化身。

 

3.建立Repository

我理解的Repository(百货)里面应该具有各种小粒度的逻辑方法,以便复用,通常Repository里面要包含各种单笔和多笔的CRUD方法。

此外,我在我的模板里做了约定,不在Repository里面进行任何的提交保存等动作。

下面我们来建立一个Repository,就用Nationality为例,在LegacyApplication.Repositories里面相应的文件夹建立NationalityRepository类:

using LegacyApplication.Database.Infrastructure;using LegacyApplication.Models.HumanResources;namespace LegacyApplication.Repositories.HumanResources{    public interface INationalityRepository : IEntityBaseRepository
{ } public class NationalityRepository : EntityBaseRepository
, INationalityRepository { public NationalityRepository(IUnitOfWork unitOfWork) : base(unitOfWork) { } }}
View Code

代码很简单,但是它已经包含了常见的10多种CRUD方法,因为它继承于EntityBaseRepository这个泛型类,这个类的代码如下:

using System;using System.Collections.Generic;using System.Data.Entity;using System.Data.Entity.Infrastructure;using System.Linq;using System.Linq.Expressions;using System.Threading.Tasks;using LegacyApplication.Database.Context;using LegacyApplication.Shared.Features.Base;namespace LegacyApplication.Database.Infrastructure{    public class EntityBaseRepository
: IEntityBaseRepository
where T : class, IEntityBase, new() { #region Properties protected CoreContext Context { get; } public EntityBaseRepository(IUnitOfWork unitOfWork) { Context = unitOfWork as CoreContext; } #endregion public virtual IQueryable
All => Context.Set
(); public virtual IQueryable
AllIncluding(params Expression
>[] includeProperties) { IQueryable
query = Context.Set
(); foreach (var includeProperty in includeProperties) { query = query.Include(includeProperty); } return query; } public virtual int Count() { return Context.Set
().Count(); } public async Task
CountAsync() { return await Context.Set
().CountAsync(); } public T GetSingle(int id) { return Context.Set
().FirstOrDefault(x => x.Id == id); } public async Task
GetSingleAsync(int id) { return await Context.Set
().FirstOrDefaultAsync(x => x.Id == id); } public T GetSingle(Expression
> predicate) { return Context.Set
().FirstOrDefault(predicate); } public async Task
GetSingleAsync(Expression
> predicate) { return await Context.Set
().FirstOrDefaultAsync(predicate); } public T GetSingle(Expression
> predicate, params Expression
>[] includeProperties) { IQueryable
query = Context.Set
(); foreach (var includeProperty in includeProperties) { query = query.Include(includeProperty); } return query.Where(predicate).FirstOrDefault(); } public async Task
GetSingleAsync(Expression
> predicate, params Expression
>[] includeProperties) { IQueryable
query = Context.Set
(); foreach (var includeProperty in includeProperties) { query = query.Include(includeProperty); } return await query.Where(predicate).FirstOrDefaultAsync(); } public virtual IQueryable
FindBy(Expression
> predicate) { return Context.Set
().Where(predicate); } public virtual void Add(T entity) { DbEntityEntry dbEntityEntry = Context.Entry
(entity); Context.Set
().Add(entity); } public virtual void Update(T entity) { DbEntityEntry
dbEntityEntry = Context.Entry
(entity); dbEntityEntry.Property(x => x.Id).IsModified = false; dbEntityEntry.State = EntityState.Modified; dbEntityEntry.Property(x => x.CreateUser).IsModified = false; dbEntityEntry.Property(x => x.CreateTime).IsModified = false; } public virtual void Delete(T entity) { DbEntityEntry dbEntityEntry = Context.Entry
(entity); dbEntityEntry.State = EntityState.Deleted; } public virtual void AddRange(IEnumerable
entities) { Context.Set
().AddRange(entities); } public virtual void DeleteRange(IEnumerable
entities) { foreach (var entity in entities) { DbEntityEntry dbEntityEntry = Context.Entry
(entity); dbEntityEntry.State = EntityState.Deleted; } } public virtual void DeleteWhere(Expression
> predicate) { IEnumerable
entities = Context.Set
().Where(predicate); foreach (var entity in entities) { Context.Entry
(entity).State = EntityState.Deleted; } } public void Attach(T entity) { Context.Set
().Attach(entity); } public void AttachRange(IEnumerable
entities) { foreach (var entity in entities) { Attach(entity); } } public void Detach(T entity) { Context.Entry
(entity).State = EntityState.Detached; } public void DetachRange(IEnumerable
entities) { foreach (var entity in entities) { Detach(entity); } } public void AttachAsModified(T entity) { Attach(entity); Update(entity); } }}
View Code

我相信这个泛型类你们都应该能看明白,如果不明白可以@我。通过继承这个类,所有的Repository都具有了常见的方法,并且写的代码很少。

但是为什么自己建立的Repository不直接继承与EntityBaseRepository,而是中间非得插一层接口呢?因为我的Repository可能还需要其他的自定义方法,这些自定义方法需要提取到这个接口里面以便使用。

 

3.1 对Repository进行注册

在LegacyApplication.Web项目里App_Start/MyConfigurations/AutofacWebapiConfig.cs里面对Repository进行ioc注册,我使用的是:

using System.Reflection;using System.Web.Http;using Autofac;using Autofac.Integration.WebApi;using LegacyApplication.Database.Context;using LegacyApplication.Database.Infrastructure;using LegacyApplication.Repositories.Core;using LegacyApplication.Repositories.HumanResources;using LegacyApplication.Repositories.Work;using LegacyApplication.Services.Core;using LegacyApplication.Services.Work;namespace LegacyStandalone.Web.MyConfigurations{    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());                        //builder.RegisterType
() // .As
() // .InstancePerRequest(); builder.RegisterType
().As
().InstancePerRequest(); //Services builder.RegisterType
().As
().InstancePerRequest(); builder.RegisterType
().As
().InstancePerRequest(); //Core builder.RegisterType
().As
().InstancePerRequest(); //Work builder.RegisterType
().As
().InstancePerRequest(); builder.RegisterType
().As
().InstancePerRequest(); builder.RegisterType
().As
().InstancePerRequest(); builder.RegisterType
().As
().InstancePerRequest(); builder.RegisterType
().As
().InstancePerRequest(); //HR builder.RegisterType
().As
().InstancePerRequest(); builder.RegisterType
().As
().InstancePerRequest(); builder.RegisterType
().As
().InstancePerRequest(); builder.RegisterType
().As
().InstancePerRequest(); builder.RegisterType
().As
().InstancePerRequest(); builder.RegisterType
().As
().InstancePerRequest(); builder.RegisterType
().As
().InstancePerRequest(); Container = builder.Build(); return Container; } }}
View Code

在里面我们也可以看见我把CoreContext注册为IUnitOfWork。

 

4.建立ViewModel

ViewModel是最终和前台打交道的一层。所有的Model都是转化成ViewModel之后再传送到前台,所有前台提交过来的对象数据,大多是作为ViewModel传进来的。

下面举一个例子:

using System.ComponentModel.DataAnnotations;using LegacyApplication.Shared.Features.Base;namespace LegacyApplication.ViewModels.HumanResources{    public class NationalityViewModel : EntityBase    {        [Display(Name = "名称")]        [Required(ErrorMessage = "{0}是必填项")]        [StringLength(50, ErrorMessage = "{0}的长度不可超过{1}")]        public string Name { get; set; }    }}
View Code

同样,它要继承EntityBase类。

同时,ViewModel里面应该加上属性验证的注解,例如DisplayName,StringLength,Range等等等等,加上注解的属性在ViewModel从前台传进来的时候会进行验证(详见Controller部分)。

 

4.1注册ViewModel和Model之间的映射

由于ViewModel和Model之间经常需要转化,如果手写代码的话,那就太多了。所以我这里采用了一个主流的.net库叫。

因为映射有两个方法,所以每对需要注册两次,分别在DomainToViewModelMappingProfile.cs和ViewModelToDomainMappingProfile.cs里面:

using System.Linq;using AutoMapper;using LegacyApplication.Models.Core;using LegacyApplication.Models.HumanResources;using LegacyApplication.Models.Work;using LegacyApplication.ViewModels.Core;using LegacyApplication.ViewModels.HumanResources;using LegacyStandalone.Web.Models;using Microsoft.AspNet.Identity.EntityFramework;using LegacyApplication.ViewModels.Work;namespace LegacyStandalone.Web.MyConfigurations.Mapping{    public class DomainToViewModelMappingProfile : Profile    {        public override string ProfileName => "DomainToViewModelMappings";        public DomainToViewModelMappingProfile()        {            CreateMap
(); CreateMap
(); CreateMap
(); CreateMap
(); CreateMap
(); CreateMap
(); CreateMap
(); CreateMap
() .ForMember(dest => dest.AttachmentCount, opt => opt.MapFrom(ori => ori.Attachments.Count)) .ForMember(dest => dest.HasAttachments, opt => opt.MapFrom(ori => ori.Attachments.Any())) .ForMember(dest => dest.ToCount, opt => opt.MapFrom(ori => ori.Tos.Count)) .ForMember(dest => dest.AnyoneRead, opt => opt.MapFrom(ori => ori.Tos.Any(y => y.HasRead))) .ForMember(dest => dest.AllRead, opt => opt.MapFrom(ori => ori.Tos.All(y => y.HasRead))); CreateMap
(); CreateMap
(); CreateMap
() .ForMember(dest => dest.Parent, opt => opt.Ignore()) .ForMember(dest => dest.Children, opt => opt.Ignore()); CreateMap
(); CreateMap
(); CreateMap
(); CreateMap
(); CreateMap
(); CreateMap
(); } }}
View Code
using AutoMapper;using LegacyApplication.Models.Core;using LegacyApplication.Models.HumanResources;using LegacyApplication.Models.Work;using LegacyApplication.ViewModels.Core;using LegacyApplication.ViewModels.HumanResources;using LegacyStandalone.Web.Models;using Microsoft.AspNet.Identity.EntityFramework;using LegacyApplication.ViewModels.Work;namespace LegacyStandalone.Web.MyConfigurations.Mapping{    public class ViewModelToDomainMappingProfile : Profile    {        public override string ProfileName => "ViewModelToDomainMappings";        public ViewModelToDomainMappingProfile()        {            CreateMap
(); CreateMap
(); CreateMap
(); CreateMap
(); CreateMap
(); CreateMap
(); CreateMap
(); CreateMap
(); CreateMap
(); CreateMap
() .ForMember(dest => dest.Parent, opt => opt.Ignore()) .ForMember(dest => dest.Children, opt => opt.Ignore()); CreateMap
(); CreateMap
(); CreateMap
(); CreateMap
(); CreateMap
(); CreateMap
(); } }}
View Code

高级功能还是要参考AutoMapper的文档。

 

5.建立Controller

先上个例子:

using System.Collections.Generic;using System.Data.Entity;using System.Threading.Tasks;using System.Web.Http;using AutoMapper;using LegacyApplication.Database.Infrastructure;using LegacyApplication.Models.HumanResources;using LegacyApplication.Repositories.HumanResources;using LegacyApplication.ViewModels.HumanResources;using LegacyStandalone.Web.Controllers.Bases;using LegacyApplication.Services.Core;namespace LegacyStandalone.Web.Controllers.HumanResources{    [RoutePrefix("api/Nationality")]    public class NationalityController : ApiControllerBase    {        private readonly INationalityRepository _nationalityRepository;        public NationalityController(            INationalityRepository nationalityRepository,            ICommonService commonService,            IUnitOfWork unitOfWork) : base(commonService, unitOfWork)        {            _nationalityRepository = nationalityRepository;        }        public async Task
> Get() { var models = await _nationalityRepository.All.ToListAsync(); var viewModels = Mapper.Map
, IEnumerable
>(models); return viewModels; } public async Task
GetOne(int id) { var model = await _nationalityRepository.GetSingleAsync(id); if (model != null) { var viewModel = Mapper.Map
(model); return Ok(viewModel); } return NotFound(); } public async Task
Post([FromBody]NationalityViewModel viewModel) { if (!ModelState.IsValid) { return BadRequest(ModelState); } var newModel = Mapper.Map
(viewModel); newModel.CreateUser = newModel.UpdateUser = User.Identity.Name; _nationalityRepository.Add(newModel); await UnitOfWork.SaveChangesAsync(); return RedirectToRoute("", new { controller = "Nationality", id = newModel.Id }); } public async Task
Put(int id, [FromBody]NationalityViewModel viewModel) { if (!ModelState.IsValid) { return BadRequest(ModelState); } viewModel.UpdateUser = User.Identity.Name; viewModel.UpdateTime = Now; viewModel.LastAction = "更新"; var model = Mapper.Map
(viewModel); _nationalityRepository.AttachAsModified(model); await UnitOfWork.SaveChangesAsync(); return Ok(viewModel); } public async Task
Delete(int id) { var model = await _nationalityRepository.GetSingleAsync(id); if (model == null) { return NotFound(); } _nationalityRepository.Delete(model); await UnitOfWork.SaveChangesAsync(); return Ok(); } }}
View Code

这是比较标准的Controller,里面包含一个多笔查询,一个单笔查询和CUD方法。

所有的Repository,Service等都是通过依赖注入弄进来的。

所有的Controller需要继承ApiControllerBase,所有Controller公用的方法、属性(property)等都应该放在ApiControllerBase里面,其代码如下:

namespace LegacyStandalone.Web.Controllers.Bases{    public abstract class ApiControllerBase : ApiController    {        protected readonly ICommonService CommonService;        protected readonly IUnitOfWork UnitOfWork;        protected readonly IDepartmentRepository DepartmentRepository;        protected readonly IUploadedFileRepository UploadedFileRepository;        protected ApiControllerBase(            ICommonService commonService,            IUnitOfWork untOfWork)        {            CommonService = commonService;            UnitOfWork = untOfWork;            DepartmentRepository = commonService.DepartmentRepository;            UploadedFileRepository = commonService.UploadedFileRepository;        }        #region Current Information        protected DateTime Now => DateTime.Now;        protected string UserName => User.Identity.Name;        protected ApplicationUserManager UserManager => Request.GetOwinContext().GetUserManager
(); [NonAction] protected async Task
GetMeAsync() { var me = await UserManager.FindByNameAsync(UserName); return me; } [NonAction] protected async Task
GetMyDepartmentEvenNull() { var department = await DepartmentRepository.GetSingleAsync(x => x.Employees.Any(y => y.No == UserName)); return department; } [NonAction] protected async Task
GetMyDepartmentNotNull() { var department = await GetMyDepartmentEvenNull(); if (department == null) { throw new Exception("您不属于任何单位/部门"); } return department; } #endregion #region Upload [NonAction] public virtual async Task
Upload() { var root = GetUploadDirectory(DateTime.Now.ToString("yyyyMM")); var result = await UploadFiles(root); return Ok(result); } [NonAction] public virtual async Task
GetFileAsync(int fileId) { var model = await UploadedFileRepository.GetSingleAsync(x => x.Id == fileId); if (model != null) { return new FileActionResult(model); } return null; } [NonAction] public virtual IHttpActionResult GetFileByPath(string path) { return new FileActionResult(path); } [NonAction] protected string GetUploadDirectory(params string[] subDirectories) {#if DEBUG var root = HttpContext.Current.Server.MapPath("~/App_Data/Upload");#else var root = AppSettings.UploadDirectory;#endif if (subDirectories != null && subDirectories.Length > 0) { foreach (var t in subDirectories) { root = Path.Combine(root, t); } } if (!Directory.Exists(root)) { Directory.CreateDirectory(root); } return root; } [NonAction] protected async Task
> UploadFiles(string root) { var list = await UploadFilesAsync(root); var models = Mapper.Map
, List
>(list).ToList(); foreach (var model in models) { UploadedFileRepository.Add(model); } await UnitOfWork.SaveChangesAsync(); return models; } [NonAction] private async Task
> UploadFilesAsync(string root) { if (!Request.Content.IsMimeMultipartContent()) { throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); } var provider = new MultipartFormDataStreamProvider(root); var count = HttpContext.Current.Request.Files.Count; var files = new List
(count); for (var i = 0; i < count; i++) { files.Add(HttpContext.Current.Request.Files[i]); } await Request.Content.ReadAsMultipartAsync(provider); var list = new List
(); var now = DateTime.Now; foreach (var file in provider.FileData) { var temp = file.Headers.ContentDisposition.FileName; var length = temp.Length; var lastSlashIndex = temp.LastIndexOf(@"\", StringComparison.Ordinal); var fileName = temp.Substring(lastSlashIndex + 2, length - lastSlashIndex - 3); var fileInfo = files.SingleOrDefault(x => x.FileName == fileName); long size = 0; if (fileInfo != null) { size = fileInfo.ContentLength; } var newFile = new UploadedFileViewModel { FileName = fileName, Path = file.LocalFileName, Size = size, Deleted = false }; var userName = string.IsNullOrEmpty(User.Identity?.Name) ? "anonymous" : User.Identity.Name; newFile.CreateUser = newFile.UpdateUser = userName; newFile.CreateTime = newFile.UpdateTime = now; newFile.LastAction = "上传"; list.Add(newFile); } return list; } #endregion protected override void Dispose(bool disposing) { base.Dispose(disposing); UserManager?.Dispose(); UnitOfWork?.Dispose(); } } #region Upload Model internal class FileActionResult : IHttpActionResult { private readonly bool _isInline = false; private readonly string _contentType; public FileActionResult(UploadedFile fileModel, string contentType, bool isInline = false) { UploadedFile = fileModel; _contentType = contentType; _isInline = isInline; } public FileActionResult(UploadedFile fileModel) { UploadedFile = fileModel; } public FileActionResult(string path) { UploadedFile = new UploadedFile { Path = path }; } private UploadedFile UploadedFile { get; set; } public Task
ExecuteAsync(CancellationToken cancellationToken) { FileStream file; try { file = File.OpenRead(UploadedFile.Path); } catch (DirectoryNotFoundException) { return Task.FromResult(new HttpResponseMessage(HttpStatusCode.NotFound)); } catch (FileNotFoundException) { return Task.FromResult(new HttpResponseMessage(HttpStatusCode.NotFound)); } var response = new HttpResponseMessage { Content = new StreamContent(file) }; var name = UploadedFile.FileName ?? file.Name; var last = name.LastIndexOf("\\", StringComparison.Ordinal); if (last > -1) { var length = name.Length - last - 1; name = name.Substring(last + 1, length); } if (!string.IsNullOrEmpty(_contentType)) { response.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(_contentType); } response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue(_isInline ? DispositionTypeNames.Inline : DispositionTypeNames.Attachment) { FileName = HttpUtility.UrlEncode(name, Encoding.UTF8) }; return Task.FromResult(response); } } #endregion}
View Code

这个基类里面可以有很多东西,目前,它可以获取当前用户名,当前时间,当前用户(ApplicationUser),当前登陆人的部门,文件上传下载等。

这个基类保证的通用方法的可扩展性和复用性,其他例如EntityBase,EntityBaseRepository等等也都是这个道理。

注意,前面在Repository里面讲过,我们不在Repository里面做提交动作。

所以所有的提交动作都在Controller里面进行,通常所有挂起的更改只需要一次提交即可,毕竟Unit of Work模式。

 

5.1获取枚举的Controller

所有的枚举都应该放在LegacyApplication.Shared/ByModule/xxx模块/Enums下。

然后前台通过访问"api/Shared"(SharedController.cs)获取该模块下(或者整个项目)所有的枚举。

using System;using System.Collections.Generic;using System.Linq;using System.Web.Http;namespace LegacyStandalone.Web.Controllers.Bases{    [RoutePrefix("api/Shared")]    public class SharedController : ApiController    {        [HttpGet]        [Route("Enums/{moduleName?}")]        public IHttpActionResult GetEnums(string moduleName = null)        {            var exp = AppDomain.CurrentDomain.GetAssemblies()                .SelectMany(t => t.GetTypes())                .Where(t => t.IsEnum);            if (!string.IsNullOrEmpty(moduleName))            {                exp = exp.Where(x => x.Namespace == $"LegacyApplication.Shared.ByModule.{moduleName}.Enums");            }            var enumTypes = exp;            var result = new Dictionary
>(); foreach (var enumType in enumTypes) { result[enumType.Name] = Enum.GetValues(enumType).Cast
().ToDictionary(e => Enum.GetName(enumType, e), e => e); } return Ok(result); } [HttpGet] [Route("EnumsList/{moduleName?}")] public IHttpActionResult GetEnumsList(string moduleName = null) { var exp = AppDomain.CurrentDomain.GetAssemblies() .SelectMany(t => t.GetTypes()) .Where(t => t.IsEnum); if (!string.IsNullOrEmpty(moduleName)) { exp = exp.Where(x => x.Namespace == $"LegacyApplication.Shared.ByModule.{moduleName}.Enums"); } var enumTypes = exp; var result = new Dictionary
>>(); foreach (var e in enumTypes) { var names = Enum.GetNames(e); var values = Enum.GetValues(e).Cast
().ToArray(); var count = names.Count(); var list = new List
>(count); for (var i = 0; i < count; i++) { list.Add(new KeyValuePair
(names[i], values[i])); } result.Add(e.Name, list); } return Ok(result); } }}
View Code

 

6.建立Services

注意Controller里面的CommonService就处在Service层。并不是所有的Model/Repository都有相应的Service层。

通常我在如下情况会建立Service:

a.需要写与数据库操作无关的可复用逻辑方法。

b.需要写多个Repository参与的可复用的逻辑方法或引用。

我的CommonService就是b这个类型,其代码如下:

using LegacyApplication.Repositories.Core;using LegacyApplication.Repositories.HumanResources;using System;using System.Collections.Generic;using System.Text;namespace LegacyApplication.Services.Core{    public interface ICommonService    {        IUploadedFileRepository UploadedFileRepository { get; }        IDepartmentRepository DepartmentRepository { get; }    }    public class CommonService : ICommonService    {        public IUploadedFileRepository UploadedFileRepository { get; }        public IDepartmentRepository DepartmentRepository { get; }        public CommonService(            IUploadedFileRepository uploadedFileRepository,            IDepartmentRepository departmentRepository)        {            UploadedFileRepository = uploadedFileRepository;        }    }}
View Code

因为我每个Controller都需要注入这几个Repository,所以如果不写service的话,每个Controller的Constructor都需要多几行代码,所以我把他们封装进了一个Service,然后注入这个Service就行。

Service也需要进行IOC注册。

 

7.其他

a.使用自行实现的异常处理和异常记录类:

GlobalConfiguration.Configuration.Services.Add(typeof(IExceptionLogger), new MyExceptionLogger());            GlobalConfiguration.Configuration.Services.Replace(typeof(IExceptionHandler), new MyExceptionHandler());
View Code

b.启用了Cors

c.所有的Controller默认是需要验证的

d.采用Token Bearer验证方式

e.默认建立一个用户,在DatabaseInitializer.cs里面可以看见用户名密码。

f.EF采用Code First,需要手动进行迁移。(我认为这样最好)

g.内置把汉字转为拼音首字母的工具,PinyinTools

h.所有上传文件的Model需要实现IFileEntity接口,参考代码中的例子。

i.所有后台翻页返回的结果应该是使用PaginatedItemsViewModel。

 

里面有很多例子,请参考。

 

注意:项目启动后显示错误页,因为我把Home页去掉了。请访问/Help页查看API列表。

过些日子可以考虑加入Swagger。

下面是我的关于ASP.NET Core Web API相关技术的公众号--草根专栏:

转载地址:http://flcjo.baihongyu.com/

你可能感兴趣的文章
【java】java自带的java.util.logging.Logger日志功能
查看>>
12.2. mcelog - Decode kernel machine check log on x86 machines
查看>>
WF4.0实战(七):请假流程(带驳回操作)
查看>>
[转]微服务(Microservice)那点事
查看>>
自动换行的draw2d标签
查看>>
Db4o结合Linq、Lambda表达式的简单示例
查看>>
25.2. String
查看>>
Mac环境下用Java(Sikuli+Robot)实现页游自动化
查看>>
胡乱读书去也
查看>>
C# QRCode 二维码
查看>>
36.11. Example
查看>>
飞得更高:(三)人不好招啊
查看>>
初识未来趋势:Java与Kotlin;EclipsE与IntelliJ
查看>>
php在没用xdebug等调试工具的情况下如何让调试内容优雅地展现出来?--php数组格式化...
查看>>
30分钟搞定后台登录界面(103个后台PSD源文件、素材网站)
查看>>
配置 L2 Population - 每天5分钟玩转 OpenStack(114)
查看>>
python接口自动化测试(三)-requests.post()
查看>>
第 5 章 Spring Boot
查看>>
谈谈一些有趣的CSS题目(五)-- 单行居中,两行居左,超过两行省略
查看>>
css ul li checkbox 格式
查看>>