WebApiHelper.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. using Autofac;
  2. using Autofac.Extensions.DependencyInjection;
  3. using Microsoft.AspNetCore.Builder;
  4. using Microsoft.AspNetCore.Hosting;
  5. using Microsoft.AspNetCore.Http;
  6. using Microsoft.AspNetCore.Http.Features;
  7. using Microsoft.AspNetCore.Mvc.ApplicationParts;
  8. using Microsoft.AspNetCore.Mvc.Controllers;
  9. using Microsoft.AspNetCore.Server.Kestrel.Core;
  10. using Microsoft.Extensions.DependencyInjection;
  11. using Microsoft.Extensions.DependencyInjection.Extensions;
  12. using Microsoft.Extensions.FileProviders;
  13. using Microsoft.Extensions.Hosting;
  14. using Serilog;
  15. using Microsoft.OpenApi.Models;
  16. using Newtonsoft.Json.Converters;
  17. using Newtonsoft.Json.Serialization;
  18. using Microsoft.Extensions.Logging;
  19. using Serilog.Extensions.Logging;
  20. using System.Diagnostics;
  21. using System.Reflection;
  22. using DW5S.Service;
  23. using DW5S.Repostory;
  24. using Serilog.Core;
  25. using Microsoft.EntityFrameworkCore;
  26. using DW5S.Entity;
  27. namespace DW5S.WebApi
  28. {
  29. /// <summary>
  30. ///
  31. /// </summary>
  32. public static class WebApiHelper
  33. {
  34. private static CancellationTokenSource _cts;
  35. /// <summary>
  36. /// 启动AspNetCore WebAPI
  37. /// (如果Controller所在程序集未被代码使用,且入口Exe被发布为自包含程序,会找不到Controller)
  38. /// </summary>
  39. /// <param name="_localPort">本地端口</param>
  40. /// <param name="controllerXmlName">Controller所在程序集XML描述文档名称,默认使用入口程序生成的xml</param>
  41. /// <param name="dtoXmlName">DTO所在程序集XML描述文档,默认使用入口程序生成的xml</param>
  42. /// <param name="staticDir">要启用的静态目录预览及文件下载的目录(已经包含upload、download、logs三个目录)</param>
  43. /// <param name="dllKey">使用DI注入时程序集标识</param>
  44. /// <exception cref="Exception"></exception>
  45. public static void Start(int _localPort, string dtoXmlName = null, string[] staticDir = null, string controllerXmlName = null, string dllKey = "DW5S")
  46. {
  47. BaseController.EnsureAssemblyLoaded();
  48. _cts = new CancellationTokenSource();
  49. if (controllerXmlName == null)
  50. controllerXmlName = $"{AppDomain.CurrentDomain.FriendlyName}.xml";
  51. if (dtoXmlName == null)
  52. dtoXmlName = $"{AppDomain.CurrentDomain.FriendlyName}.xml";
  53. List<string> listDir = new List<string>();
  54. if (staticDir != null)
  55. {
  56. listDir.AddRange(staticDir.Select(p => p.ToLower()).Distinct());
  57. }
  58. if (!listDir.Contains("upload"))
  59. {
  60. listDir.Add("upload");
  61. }
  62. if (!listDir.Contains("download"))
  63. {
  64. listDir.Add("download");
  65. }
  66. if (!listDir.Contains("logs"))
  67. {
  68. listDir.Add("logs");
  69. }
  70. staticDir = listDir.ToArray();
  71. var assemblies = AppDomain.CurrentDomain.GetAllAssemblies(dllKey);
  72. assemblies = assemblies.Take(assemblies.Length - 1).ToArray();
  73. if (assemblies == null)
  74. {
  75. throw new Exception($"未扫描到包含{dllKey}字符串的程序集");
  76. }
  77. foreach (var item in assemblies)
  78. {
  79. Console.WriteLine($"已加载DI注入程序集[{item.FullName}]");
  80. }
  81. var controllerAssemblies = assemblies.Where(p => p.GetTypes().Any(q =>
  82. {
  83. if (q.Name.EndsWith("Controller") && q.IsSubclassOf(typeof(BaseController)))
  84. return true;
  85. return false;
  86. })).ToList();
  87. if (controllerAssemblies == null || !controllerAssemblies.Any())
  88. {
  89. throw new Exception("未找到Controller所在的程序集");
  90. }
  91. var builder = WebApplication.CreateBuilder();
  92. builder.Services.AddRouting(t => t.LowercaseUrls = true);//全部路由默认显示小写
  93. #region 请求大小限制200MB及Http2支持
  94. builder.WebHost.ConfigureKestrel(options =>
  95. {
  96. options.Limits.MaxRequestBodySize = 200 << 20;//200MB
  97. options.ListenAnyIP(Convert.ToInt32(_localPort), listenOptions =>
  98. {
  99. listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
  100. });
  101. });
  102. builder.Services.Configure<FormOptions>(options =>
  103. {
  104. options.MultipartBodyLengthLimit = 200 << 20;//通过表单上传最大200MB
  105. });
  106. #endregion
  107. #region 初始化日志
  108. //取消默认的日志Provider
  109. builder.Logging.ClearProviders();
  110. builder.Host.UseSerilog((builderContext, config) =>
  111. {
  112. var basePath = AppContext.BaseDirectory;
  113. var outputTemplate = "{Timestamp:yyyy-MM-dd HH:mm:ss.fff}[线程={ThreadId}][{Level:u3}]{Message:lj}{NewLine}{Exception}";
  114. config.Enrich.FromLogContext()
  115. .Enrich.With(new SerilogEnricher())
  116. .WriteTo.Console(outputTemplate: outputTemplate)
  117. .WriteTo.Logger(p => p.Filter.ByIncludingOnly(e => e.Level == Serilog.Events.LogEventLevel.Information)
  118. .WriteTo.File(Path.Combine(basePath, "Logs", "Info", ".log"), rollingInterval: Serilog.RollingInterval.Day, outputTemplate: outputTemplate))
  119. .WriteTo.Logger(p => p.Filter.ByIncludingOnly(e => e.Level == Serilog.Events.LogEventLevel.Warning)
  120. .WriteTo.File(Path.Combine(basePath, "Logs", "Warning", ".log"), rollingInterval: Serilog.RollingInterval.Day, outputTemplate: outputTemplate))
  121. .WriteTo.Logger(p => p.Filter.ByIncludingOnly(e => e.Level == Serilog.Events.LogEventLevel.Error)
  122. .WriteTo.File(Path.Combine(basePath, "Logs", "Error", ".log"), rollingInterval: Serilog.RollingInterval.Day, outputTemplate: outputTemplate));
  123. });
  124. builder.Logging.AddSerilog();
  125. #endregion
  126. #region 启用静态文件缓存和压缩(已屏蔽,采集文件压缩率不高)
  127. //builder.Services.AddResponseCaching();
  128. //builder.Services.AddResponseCompression();
  129. #endregion
  130. #region 注入常用服务
  131. //系统缓存,可以其它地方使用IMemoryCache接口
  132. builder.Services.AddMemoryCache();
  133. builder.Services.AddSignalR();
  134. //http上下文
  135. builder.Services.AddSingleton<IHttpContextAccessor, Microsoft.AspNetCore.Http.HttpContextAccessor>();
  136. //HttpClient
  137. builder.Services.AddHttpClient();//IHttpClientFactory
  138. builder.Services.AddGrpc();
  139. //builder.Services.AddGrpcClient<object>(p=>
  140. //{
  141. // p.Address = new Uri("http://127.0.0.1:16001");
  142. //}).ConfigureChannel(p =>
  143. //{
  144. //});
  145. builder.Services.AddEndpointsApiExplorer();
  146. builder.Services.AddSwaggerGen(c =>
  147. {
  148. c.SwaggerDoc("v1", new OpenApiInfo
  149. {
  150. Version = "v1",
  151. Title = $"{Path.GetFileNameWithoutExtension(Process.GetCurrentProcess().MainModule.FileName)}Http接口文档",
  152. });
  153. var basePath = AppDomain.CurrentDomain.BaseDirectory;
  154. var xmlPath = Path.Combine(basePath, controllerXmlName);//Controller xml
  155. c.IncludeXmlComments(xmlPath, true);
  156. xmlPath = Path.Combine(basePath, "05.DW5S.WebApi.xml");//BaesController xml
  157. c.IncludeXmlComments(xmlPath, true);
  158. c.OrderActionsBy(o => o.RelativePath);
  159. xmlPath = Path.Combine(basePath, dtoXmlName);//dto xml
  160. c.IncludeXmlComments(xmlPath);
  161. });
  162. #endregion
  163. #region 注入Controller、Service、Repository、HostedService
  164. //让Controller的实例由autofac创建,而不是由dotnet框架创建,以便在控制器中使用autofac高级功能
  165. builder.Services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>());
  166. //将框架默认IOC容器替换为Autofac容器
  167. builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
  168. var hostBuilder = builder.Host.ConfigureContainer<Autofac.ContainerBuilder>(builder =>
  169. {
  170. //注入IRepository
  171. builder.RegisterGeneric(typeof(Repository<,>)).As(typeof(IRepository<,>))
  172. .InstancePerLifetimeScope();
  173. builder.RegisterAssemblyTypes(assemblies)
  174. .Where(t => t.Name.Contains("Repository"))
  175. .AsClosedTypesOf(typeof(IRepository<,>))
  176. .InstancePerLifetimeScope();
  177. //注入DbContext
  178. builder.RegisterType<SqliteContext>()
  179. .InstancePerLifetimeScope();
  180. //注入IUnitOfWork
  181. builder.RegisterType<UnitOfWork>().As<IUnitOfWork>()
  182. .InstancePerLifetimeScope();
  183. //注入Controllerer并自动注入Controller中的IUnitOfwork、ILogger、Service等属性
  184. builder.RegisterAssemblyTypes(assemblies)
  185. .Where(type => type.Name.EndsWith("Controller"))
  186. .PropertiesAutowired((propInfo, instance) =>//Controller中的属性支持自动注入
  187. propInfo.Name.EndsWith("Autowired") || //以Autowired名称结尾的属性自动注入
  188. propInfo.GetCustomAttribute<AutowiredAttribute>() != null ||//带有Autowired特性的属性自动注入
  189. propInfo.PropertyType == typeof(Serilog.ILogger) ||
  190. propInfo.PropertyType == typeof(IUnitOfWork) ||
  191. propInfo.PropertyType.ToString().EndsWith("Service"))//以Service结尾的属性自动注入
  192. .InstancePerLifetimeScope();
  193. //注入Service
  194. builder.RegisterAssemblyTypes(assemblies)
  195. .Where(type => type.Name.EndsWith("Service") && type.BaseType == typeof(object))
  196. .PropertiesAutowired((propInfo, instance) =>//Service中的属性支持自动注入
  197. propInfo.Name.EndsWith("Autowired") || //以Autowired名称结尾的属性自动注入
  198. propInfo.GetCustomAttribute<AutowiredAttribute>() != null||
  199. propInfo.PropertyType == typeof(Serilog.ILogger) ||
  200. propInfo.PropertyType == typeof(IUnitOfWork))//带有Autowired特性的属性自动注入
  201. .AsSelf()//接口的默认实现
  202. .InstancePerLifetimeScope();
  203. //注入BackgroundService后台服务
  204. builder.RegisterAssemblyTypes(assemblies)
  205. .Where(type => type.Name.EndsWith("Service") && type.IsSubclassOf(typeof(BackgroundService)))
  206. .PropertiesAutowired((propInfo, instance) =>//BackgroundService类中的属性支持自动注入
  207. propInfo.Name.EndsWith("Autowired") || //以Autowired名称结尾的属性自动注入
  208. propInfo.GetCustomAttribute<AutowiredAttribute>() != null ||//带有Autowired特性的属性自动注入
  209. propInfo.PropertyType == typeof(Serilog.ILogger) ||
  210. propInfo.PropertyType == typeof(IUnitOfWork) ||
  211. propInfo.PropertyType.ToString().EndsWith("Service"))//以Service结尾的属性自动注入
  212. .As<IHostedService>()//后台服务方式注入
  213. .SingleInstance();
  214. });
  215. #endregion
  216. builder.Services.AddControllers(c =>
  217. {
  218. //过滤器
  219. //c.Filters.Add<LogActionFilter>();
  220. })
  221. //这种方式不方便加载Controller分布在不同dll中的情况,因此使用了ConfigureApplicationPartManager
  222. //.AddApplicationPart(controllerAssemblies.First())
  223. .ConfigureApplicationPartManager(apm =>
  224. {
  225. foreach (var item in controllerAssemblies)
  226. {
  227. var part = new AssemblyPart(item);
  228. apm.ApplicationParts.Add(part);
  229. }
  230. })
  231. .AddNewtonsoftJson(options =>
  232. {
  233. //修改属性名称的序列化方式,首字母小写
  234. options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
  235. //修改时间的序列化方式
  236. options.SerializerSettings.Converters.Add(new IsoDateTimeConverter() { DateTimeFormat = "yyyy-MM-dd HH:mm:ss" });
  237. });
  238. var app = builder.Build();
  239. var container = app.Services.GetAutofacRoot();//autofac容器
  240. IocContainer.Init(container);
  241. #region 启用静态资源访问
  242. foreach (var item in staticDir)
  243. {
  244. //静态文件物理路径
  245. if (string.IsNullOrWhiteSpace(item)) continue;
  246. string path;
  247. if (item.Contains(":"))
  248. path = item;//绝对路径
  249. else
  250. path = Path.Combine(AppContext.BaseDirectory, item);//相对路径
  251. Directory.CreateDirectory(path);
  252. //静态资源缓存时间(300秒,600秒)
  253. var cachePeriod = app.Environment.IsDevelopment() ? "300" : "600";
  254. //启用静态文件路由
  255. app.UseStaticFiles(new StaticFileOptions
  256. {
  257. ServeUnknownFileTypes = true,
  258. FileProvider = new PhysicalFileProvider(path),
  259. RequestPath = $"/{Path.GetFileName(item)}",
  260. OnPrepareResponse = ctx =>
  261. {
  262. ctx.Context.Response.Headers.Append("Cache-Control", $"public, max-age={cachePeriod}");
  263. }
  264. });
  265. //启用目录浏览所有文件
  266. app.UseDirectoryBrowser(new DirectoryBrowserOptions()
  267. {
  268. FileProvider = new PhysicalFileProvider(path),
  269. RequestPath = $"/{Path.GetFileName(item)}"
  270. });
  271. }
  272. app.UseResponseCaching();
  273. #endregion
  274. //app.MapGrpcService<FileService>();
  275. app.Urls.Add($"http://+:{_localPort}");
  276. app.UseSwagger();
  277. app.UseSwaggerUI(c =>
  278. {
  279. c.SwaggerEndpoint("/swagger/v1/swagger.json", "v1版本");
  280. });
  281. app.UseExceptionHandler("/Home/Error");
  282. //app.UseAuthentication();//身份验证
  283. app.UseAuthorization(); //授权
  284. //app.UseMiddleware<ExceptionHandlingMiddleware>();//全局异常处理
  285. app.MapControllers();
  286. //app.Map("/", () => "必须通过GRPC客户端访问此接口");
  287. //app.MapHub<ChatHub>("/Chat");
  288. app.RunAsync(_cts.Token);
  289. //app.Run();
  290. }
  291. /// <summary>
  292. /// 结束WebAPI
  293. /// </summary>
  294. public static void Stop()
  295. {
  296. _cts?.Cancel();
  297. }
  298. }
  299. }