asp.net web api 2.2 基础框架(带例子)
链接: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 基础框架(带例子)的更多相关文章
- 基于SpringBoot的Web API快速开发基础框架
其实还是很因为懒,才会有这个案例项目的产生,每次开启一个终端的小服务都要整理一次框架,造成重复的.不必要的.缺乏创造性的劳动,SO,本着可以用.用着简单的原则上传代码到Github,希望有需要的朋友直 ...
- 【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 ...
- ASP.NET Web API 开篇示例介绍
ASP.NET Web API 开篇示例介绍 ASP.NET Web API 对于我这个初学者来说ASP.NET Web API这个框架很陌生又熟悉着. 陌生的是ASP.NET Web API是一个全 ...
- Asp.net web api 知多少
本系列主要翻译自<ASP.NET MVC Interview Questions and Answers >- By Shailendra Chauhan,想看英文原版的可访问http:/ ...
- 使用 OWIN 作为 ASP.NET Web API 的宿主
使用 OWIN 作为 ASP.NET Web API 的宿主 ASP.NET Web API 是一种框架,用于轻松构建可以访问多种客户端(包括浏览器和移动 设备)的 HTTP 服务. ASP.NET ...
- 【转】WCF和ASP.NET Web API在应用上的选择
文章出处:http://www.cnblogs.com/shanyou/archive/2012/09/26/2704814.html 在最近发布的Visual Studio 2012及.NET 4. ...
- ASP.NET Web API下的HttpController激活:程序集的解析
ASP.NET Web API下的HttpController激活:程序集的解析 HttpController的激活是由处于消息处理管道尾端的HttpRoutingDispatcher来完成的,具体来 ...
- WCF和ASP.NET Web API在应用上的选择
小分享:我有几张阿里云优惠券,用券购买或者升级阿里云相应产品最多可以优惠五折!领券地址:https://promotion.aliyun.com/ntms/act/ambassador/shareto ...
- [转载]WCF和ASP.NET Web API在应用上的选择
http://www.cnblogs.com/shanyou/archive/2012/09/26/2704814.html http://msdn.microsoft.com/en-us/libra ...
随机推荐
- Java的类型转换
Java的类型转换 在适当的时候,我们会想要将一种数据类型自动转换成另一种,比如把int转化成float类型.Java有隐藏式的自动转换,可以自动转换成想要的类型,但是强制的自动转换的话,.需要将希望 ...
- 本地配置DNS服务器(MAC版)
作为一个前端开发者,会遇到使用cookie的情况,常见的如:登录,权限控制,视频播放,图形验证码等,这时候本地开发者在PC上会使用修改hosts的方式添加指向本地的域名,来获取cookie的同域名.如 ...
- css 找到隐藏元素个数
<form> <input type="hidden" name="email" /> <input type=" ...
- HK2框架的简单自实现kunJ
kunJ kunJ框架,是基于HK2框架的一个自实现注入框架,功能比较简单,重在探索依赖注入的实现原理. 实现细节 自定义3个注解,Access,Inject,Service 在Service中实现对 ...
- element ui datePicker 设置当前日期之前的日期不可选
pickerOptions0: { disabledDate(time) { return time.getTime() < Date.now() - 8.64e7 } },
- JAVA HashMap的实现原理
详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt359 1. HashMap的数据结构 数据结构中有数组和链表来实现对数据的存 ...
- 第3阶段——内核启动分析之make menuconfig内核配置(2)
目标: 分析make menuconfig内核配置过程 在上1小结中(内核编译试验)讲到了3种不同的配置: (1)通过make menuconfig 直接从头到尾配置.config文件 (2) 通过m ...
- string和double之间的相互转换(C++)
很多人都写过这个标题的文章,但本文要解决的是确保负数的string和double也可以进行转换. 代码如下: string转double double stringToDouble(string nu ...
- JavaScript 的使用基础总结①
JavaScript 使用 JavaScript 是一种轻量级的编程语言,JavaScript 是可插入 HTML 页面的编程代码,JavaScript 插入 HTML 页面后,可由所有的现代浏览 ...
- 团队作业1——团队展示&博客作业查重系统
团队展示: 1.队名:六个核桃 2.队员学号: 王婧(201421123065).柯怡芳(201421123067组长).陈艺菡(201421123068). 钱惠(201421123071).尼玛( ...