链接:https://github.com/solenovex/asp.net-web-api-2.2-starter-template

简介

这个是我自己编写的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<Nationality>
{
public NationalityConfiguration()
{
ToTable("hr.Nationality");
Property(x => x.Name).IsRequired().HasMaxLength();
Property(x => x.Name).HasMaxLength().HasColumnAnnotation(
IndexAnnotation.AnnotationName,
new IndexAnnotation(new IndexAttribute { IsUnique = true }));
}
}
}

所建立的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; }
}
}

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

using System.Data.Entity.ModelConfiguration;

namespace LegacyApplication.Shared.Features.Base
{
public class EntityBaseConfiguration<T> : EntityTypeConfiguration<T> 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();
Property(x => x.UpdateUser).IsRequired().HasMaxLength();
Property(x => x.LastAction).IsRequired().HasMaxLength();
}
}
}

1.1 自成树形的Model

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

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

 

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

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

其中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<T> : EntityBaseConfiguration<T> where T : TreeEntityBase<T>
{
public TreeEntityBaseConfiguration()
{
Property(x => x.AncestorIds).HasMaxLength();
Ignore(x => x.Level); HasOptional(x => x.Parent).WithMany(x => x.Children).HasForeignKey(x => x.ParentId).WillCascadeOnDelete(false);
}
}
}

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

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

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<CoreContext>(null);
#if DEBUG
Database.Log = Console.Write;
Database.Log = message => Trace.WriteLine(message);
#endif
} protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>(); //去掉默认开启的级联删除 modelBuilder.Configurations.AddFromAssembly(Assembly.GetAssembly(typeof(UploadedFile)));
} //Core
public DbSet<UploadedFile> UploadedFiles { get; set; } //Work
public DbSet<InternalMail> InternalMails { get; set; }
public DbSet<InternalMailTo> InternalMailTos { get; set; }
public DbSet<InternalMailAttachment> InternalMailAttachments { get; set; }
public DbSet<Todo> Todos { get; set; }
public DbSet<Schedule> Schedules { get; set; } //HR
public DbSet<Department> Departments { get; set; }
public DbSet<Employee> Employees { get; set; }
public DbSet<JobPostLevel> JobPostLevels { get; set; }
public DbSet<JobPost> JobPosts { get; set; }
public DbSet<AdministrativeLevel> AdministrativeLevels { get; set; }
public DbSet<TitleLevel> TitleLevels { get; set; }
public DbSet<Nationality> Nationalitys { get; set; } }
}

其中“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<int> SaveChangesAsync(CancellationToken cancellationToken);
Task<int> SaveChangesAsync();
}
}

用的时候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<Nationality>
{
} public class NationalityRepository : EntityBaseRepository<Nationality>, INationalityRepository
{
public NationalityRepository(IUnitOfWork unitOfWork) : base(unitOfWork)
{
}
}
}

代码很简单,但是它已经包含了常见的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<T> : IEntityBaseRepository<T>
where T : class, IEntityBase, new()
{
#region Properties
protected CoreContext Context { get; } public EntityBaseRepository(IUnitOfWork unitOfWork)
{
Context = unitOfWork as CoreContext;
}
#endregion public virtual IQueryable<T> All => Context.Set<T>(); public virtual IQueryable<T> AllIncluding(params Expression<Func<T, object>>[] includeProperties)
{
IQueryable<T> query = Context.Set<T>();
foreach (var includeProperty in includeProperties)
{
query = query.Include(includeProperty);
}
return query;
} public virtual int Count()
{
return Context.Set<T>().Count();
} public async Task<int> CountAsync()
{
return await Context.Set<T>().CountAsync();
} public T GetSingle(int id)
{
return Context.Set<T>().FirstOrDefault(x => x.Id == id);
} public async Task<T> GetSingleAsync(int id)
{
return await Context.Set<T>().FirstOrDefaultAsync(x => x.Id == id);
} public T GetSingle(Expression<Func<T, bool>> predicate)
{
return Context.Set<T>().FirstOrDefault(predicate);
} public async Task<T> GetSingleAsync(Expression<Func<T, bool>> predicate)
{
return await Context.Set<T>().FirstOrDefaultAsync(predicate);
} public T GetSingle(Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[] includeProperties)
{
IQueryable<T> query = Context.Set<T>();
foreach (var includeProperty in includeProperties)
{
query = query.Include(includeProperty);
}
return query.Where(predicate).FirstOrDefault();
} public async Task<T> GetSingleAsync(Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[] includeProperties)
{
IQueryable<T> query = Context.Set<T>();
foreach (var includeProperty in includeProperties)
{
query = query.Include(includeProperty);
} return await query.Where(predicate).FirstOrDefaultAsync();
} public virtual IQueryable<T> FindBy(Expression<Func<T, bool>> predicate)
{
return Context.Set<T>().Where(predicate);
} public virtual void Add(T entity)
{
DbEntityEntry dbEntityEntry = Context.Entry<T>(entity);
Context.Set<T>().Add(entity);
} public virtual void Update(T entity)
{
DbEntityEntry<T> dbEntityEntry = Context.Entry<T>(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<T>(entity);
dbEntityEntry.State = EntityState.Deleted;
} public virtual void AddRange(IEnumerable<T> entities)
{
Context.Set<T>().AddRange(entities);
} public virtual void DeleteRange(IEnumerable<T> entities)
{
foreach (var entity in entities)
{
DbEntityEntry dbEntityEntry = Context.Entry<T>(entity);
dbEntityEntry.State = EntityState.Deleted;
}
} public virtual void DeleteWhere(Expression<Func<T, bool>> predicate)
{
IEnumerable<T> entities = Context.Set<T>().Where(predicate); foreach (var entity in entities)
{
Context.Entry<T>(entity).State = EntityState.Deleted;
}
}
public void Attach(T entity)
{
Context.Set<T>().Attach(entity);
} public void AttachRange(IEnumerable<T> entities)
{
foreach (var entity in entities)
{
Attach(entity);
}
} public void Detach(T entity)
{
Context.Entry<T>(entity).State = EntityState.Detached;
} public void DetachRange(IEnumerable<T> entities)
{
foreach (var entity in entities)
{
Detach(entity);
}
} public void AttachAsModified(T entity)
{
Attach(entity);
Update(entity);
} }
}

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

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

3.1 对Repository进行注册

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

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<CoreContext>()
// .As<DbContext>()
// .InstancePerRequest(); builder.RegisterType<CoreContext>().As<IUnitOfWork>().InstancePerRequest(); //Services
builder.RegisterType<CommonService>().As<ICommonService>().InstancePerRequest();
builder.RegisterType<InternalMailService>().As<IInternalMailService>().InstancePerRequest(); //Core
builder.RegisterType<UploadedFileRepository>().As<IUploadedFileRepository>().InstancePerRequest(); //Work
builder.RegisterType<InternalMailRepository>().As<IInternalMailRepository>().InstancePerRequest();
builder.RegisterType<InternalMailToRepository>().As<IInternalMailToRepository>().InstancePerRequest();
builder.RegisterType<InternalMailAttachmentRepository>().As<IInternalMailAttachmentRepository>().InstancePerRequest();
builder.RegisterType<TodoRepository>().As<ITodoRepository>().InstancePerRequest();
builder.RegisterType<ScheduleRepository>().As<IScheduleRepository>().InstancePerRequest(); //HR
builder.RegisterType<DepartmentRepository>().As<IDepartmentRepository>().InstancePerRequest();
builder.RegisterType<EmployeeRepository>().As<IEmployeeRepository>().InstancePerRequest();
builder.RegisterType<JobPostLevelRepository>().As<IJobPostLevelRepository>().InstancePerRequest();
builder.RegisterType<JobPostRepository>().As<IJobPostRepository>().InstancePerRequest();
builder.RegisterType<AdministrativeLevelRepository>().As<IAdministrativeLevelRepository>().InstancePerRequest();
builder.RegisterType<TitleLevelRepository>().As<ITitleLevelRepository>().InstancePerRequest();
builder.RegisterType<NationalityRepository>().As<INationalityRepository>().InstancePerRequest(); Container = builder.Build(); return Container;
}
}
}

在里面我们也可以看见我把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(, ErrorMessage = "{0}的长度不可超过{1}")]
public string Name { get; set; }
}
}

同样,它要继承EntityBase类。

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

4.1注册ViewModel和Model之间的映射

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

因为映射有两个方法,所以每对需要注册两次,分别在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<ApplicationUser, UserViewModel>();
CreateMap<IdentityRole, RoleViewModel>();
CreateMap<IdentityUserRole, RoleViewModel>(); CreateMap<UploadedFile, UploadedFileViewModel>(); CreateMap<InternalMail, InternalMailViewModel>();
CreateMap<InternalMailTo, InternalMailToViewModel>();
CreateMap<InternalMailAttachment, InternalMailAttachmentViewModel>();
CreateMap<InternalMail, SentMailViewModel>()
.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<Todo, TodoViewModel>();
CreateMap<Schedule, ScheduleViewModel>(); CreateMap<Department, DepartmentViewModel>()
.ForMember(dest => dest.Parent, opt => opt.Ignore())
.ForMember(dest => dest.Children, opt => opt.Ignore()); CreateMap<Employee, EmployeeViewModel>();
CreateMap<JobPostLevel, JobPostLevelViewModel>();
CreateMap<JobPost, JobPostViewModel>();
CreateMap<AdministrativeLevel, AdministrativeLevelViewModel>();
CreateMap<TitleLevel, TitleLevelViewModel>();
CreateMap<Nationality, NationalityViewModel>(); }
}
}
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<UserViewModel, ApplicationUser>();
CreateMap<RoleViewModel, IdentityRole>();
CreateMap<RoleViewModel, IdentityUserRole>(); CreateMap<UploadedFileViewModel, UploadedFile>(); CreateMap<InternalMailViewModel, InternalMail>();
CreateMap<InternalMailToViewModel, InternalMailTo>();
CreateMap<InternalMailAttachmentViewModel, InternalMailAttachment>();
CreateMap<TodoViewModel, Todo>();
CreateMap<ScheduleViewModel, Schedule>(); CreateMap<DepartmentViewModel, Department>()
.ForMember(dest => dest.Parent, opt => opt.Ignore())
.ForMember(dest => dest.Children, opt => opt.Ignore());
CreateMap<EmployeeViewModel, Employee>();
CreateMap<JobPostLevelViewModel, JobPostLevel>();
CreateMap<JobPostViewModel, JobPost>();
CreateMap<AdministrativeLevelViewModel, AdministrativeLevel>();
CreateMap<TitleLevelViewModel, TitleLevel>();
CreateMap<NationalityViewModel, Nationality>(); }
}
}

高级功能还是要参考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<IEnumerable<NationalityViewModel>> Get()
{
var models = await _nationalityRepository.All.ToListAsync();
var viewModels = Mapper.Map<IEnumerable<Nationality>, IEnumerable<NationalityViewModel>>(models);
return viewModels;
} public async Task<IHttpActionResult> GetOne(int id)
{
var model = await _nationalityRepository.GetSingleAsync(id);
if (model != null)
{
var viewModel = Mapper.Map<Nationality, NationalityViewModel>(model);
return Ok(viewModel);
}
return NotFound();
} public async Task<IHttpActionResult> Post([FromBody]NationalityViewModel viewModel)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var newModel = Mapper.Map<NationalityViewModel, Nationality>(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<IHttpActionResult> 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<NationalityViewModel, Nationality>(viewModel); _nationalityRepository.AttachAsModified(model); await UnitOfWork.SaveChangesAsync(); return Ok(viewModel);
} public async Task<IHttpActionResult> Delete(int id)
{
var model = await _nationalityRepository.GetSingleAsync(id);
if (model == null)
{
return NotFound();
}
_nationalityRepository.Delete(model);
await UnitOfWork.SaveChangesAsync();
return Ok();
}
}
}

这是比较标准的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<ApplicationUserManager>(); [NonAction]
protected async Task<ApplicationUser> GetMeAsync()
{
var me = await UserManager.FindByNameAsync(UserName);
return me;
} [NonAction]
protected async Task<Department> GetMyDepartmentEvenNull()
{
var department = await DepartmentRepository.GetSingleAsync(x => x.Employees.Any(y => y.No == UserName));
return department;
} [NonAction]
protected async Task<Department> GetMyDepartmentNotNull()
{
var department = await GetMyDepartmentEvenNull();
if (department == null)
{
throw new Exception("您不属于任何单位/部门");
}
return department;
} #endregion #region Upload [NonAction]
public virtual async Task<IHttpActionResult> Upload()
{
var root = GetUploadDirectory(DateTime.Now.ToString("yyyyMM"));
var result = await UploadFiles(root);
return Ok(result);
} [NonAction]
public virtual async Task<IHttpActionResult> 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 > )
{
foreach (var t in subDirectories)
{
root = Path.Combine(root, t);
}
}
if (!Directory.Exists(root))
{
Directory.CreateDirectory(root);
}
return root;
} [NonAction]
protected async Task<List<UploadedFile>> UploadFiles(string root)
{
var list = await UploadFilesAsync(root);
var models = Mapper.Map<List<UploadedFileViewModel>, List<UploadedFile>>(list).ToList();
foreach (var model in models)
{
UploadedFileRepository.Add(model);
}
await UnitOfWork.SaveChangesAsync();
return models;
} [NonAction]
private async Task<List<UploadedFileViewModel>> 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<HttpPostedFile>(count);
for (var i = ; i < count; i++)
{
files.Add(HttpContext.Current.Request.Files[i]);
}
await Request.Content.ReadAsMultipartAsync(provider);
var list = new List<UploadedFileViewModel>();
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 + , length - lastSlashIndex - );
var fileInfo = files.SingleOrDefault(x => x.FileName == fileName);
long size = ;
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<HttpResponseMessage> 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 > -)
{
var length = name.Length - last - ;
name = name.Substring(last + , 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
}

这个基类里面可以有很多东西,目前,它可以获取当前用户名,当前时间,当前用户(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<string, Dictionary<string, int>>();
foreach (var enumType in enumTypes)
{
result[enumType.Name] = Enum.GetValues(enumType).Cast<int>().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<string, List<KeyValuePair<string, int>>>();
foreach (var e in enumTypes)
{
var names = Enum.GetNames(e);
var values = Enum.GetValues(e).Cast<int>().ToArray();
var count = names.Count();
var list = new List<KeyValuePair<string, int>>(count);
for (var i = ; i < count; i++)
{
list.Add(new KeyValuePair<string, int> (names[i], values[i]));
}
result.Add(e.Name, list);
}
return Ok(result);
}
}
}

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;
}
}
}

因为我每个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());

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 web api 2.2 基础框架(带例子)的更多相关文章

  1. 基于SpringBoot的Web API快速开发基础框架

    其实还是很因为懒,才会有这个案例项目的产生,每次开启一个终端的小服务都要整理一次框架,造成重复的.不必要的.缺乏创造性的劳动,SO,本着可以用.用着简单的原则上传代码到Github,希望有需要的朋友直 ...

  2. 【ASP.NET Web API教程】2.3 与实体框架一起使用Web API

    原文:[ASP.NET Web API教程]2.3 与实体框架一起使用Web API 2.3 Using Web API with Entity Framework 2.3 与实体框架一起使用Web ...

  3. ASP.NET Web API 开篇示例介绍

    ASP.NET Web API 开篇示例介绍 ASP.NET Web API 对于我这个初学者来说ASP.NET Web API这个框架很陌生又熟悉着. 陌生的是ASP.NET Web API是一个全 ...

  4. Asp.net web api 知多少

    本系列主要翻译自<ASP.NET MVC Interview Questions and Answers >- By Shailendra Chauhan,想看英文原版的可访问http:/ ...

  5. 使用 OWIN 作为 ASP.NET Web API 的宿主

    使用 OWIN 作为 ASP.NET Web API 的宿主 ASP.NET Web API 是一种框架,用于轻松构建可以访问多种客户端(包括浏览器和移动 设备)的 HTTP 服务. ASP.NET ...

  6. 【转】WCF和ASP.NET Web API在应用上的选择

    文章出处:http://www.cnblogs.com/shanyou/archive/2012/09/26/2704814.html 在最近发布的Visual Studio 2012及.NET 4. ...

  7. ASP.NET Web API下的HttpController激活:程序集的解析

    ASP.NET Web API下的HttpController激活:程序集的解析 HttpController的激活是由处于消息处理管道尾端的HttpRoutingDispatcher来完成的,具体来 ...

  8. WCF和ASP.NET Web API在应用上的选择

    小分享:我有几张阿里云优惠券,用券购买或者升级阿里云相应产品最多可以优惠五折!领券地址:https://promotion.aliyun.com/ntms/act/ambassador/shareto ...

  9. [转载]WCF和ASP.NET Web API在应用上的选择

    http://www.cnblogs.com/shanyou/archive/2012/09/26/2704814.html http://msdn.microsoft.com/en-us/libra ...

随机推荐

  1. Java的类型转换

    Java的类型转换 在适当的时候,我们会想要将一种数据类型自动转换成另一种,比如把int转化成float类型.Java有隐藏式的自动转换,可以自动转换成想要的类型,但是强制的自动转换的话,.需要将希望 ...

  2. 本地配置DNS服务器(MAC版)

    作为一个前端开发者,会遇到使用cookie的情况,常见的如:登录,权限控制,视频播放,图形验证码等,这时候本地开发者在PC上会使用修改hosts的方式添加指向本地的域名,来获取cookie的同域名.如 ...

  3. css 找到隐藏元素个数

    <form>   <input type="hidden" name="email" />   <input type=" ...

  4. HK2框架的简单自实现kunJ

    kunJ kunJ框架,是基于HK2框架的一个自实现注入框架,功能比较简单,重在探索依赖注入的实现原理. 实现细节 自定义3个注解,Access,Inject,Service 在Service中实现对 ...

  5. element ui datePicker 设置当前日期之前的日期不可选

    pickerOptions0: { disabledDate(time) { return time.getTime() < Date.now() - 8.64e7 } },

  6. JAVA HashMap的实现原理

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt359 1. HashMap的数据结构 数据结构中有数组和链表来实现对数据的存 ...

  7. 第3阶段——内核启动分析之make menuconfig内核配置(2)

    目标: 分析make menuconfig内核配置过程 在上1小结中(内核编译试验)讲到了3种不同的配置: (1)通过make menuconfig 直接从头到尾配置.config文件 (2) 通过m ...

  8. string和double之间的相互转换(C++)

    很多人都写过这个标题的文章,但本文要解决的是确保负数的string和double也可以进行转换. 代码如下: string转double double stringToDouble(string nu ...

  9. JavaScript 的使用基础总结①

    JavaScript 使用   JavaScript 是一种轻量级的编程语言,JavaScript 是可插入 HTML 页面的编程代码,JavaScript 插入 HTML 页面后,可由所有的现代浏览 ...

  10. 团队作业1——团队展示&博客作业查重系统

    团队展示: 1.队名:六个核桃 2.队员学号: 王婧(201421123065).柯怡芳(201421123067组长).陈艺菡(201421123068). 钱惠(201421123071).尼玛( ...