using Autofac; using Autofac.Extensions.DependencyInjection; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Hosting; using Serilog; using Microsoft.OpenApi.Models; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Serialization; using Microsoft.Extensions.Logging; using Serilog.Extensions.Logging; using System.Diagnostics; using System.Reflection; using DW5S.Service; using DW5S.Repostory; using Serilog.Core; using Microsoft.EntityFrameworkCore; using DW5S.Entity; namespace DW5S.WebApi { /// /// /// public static class WebApiHelper { private static CancellationTokenSource _cts; /// /// 启动AspNetCore WebAPI /// (如果Controller所在程序集未被代码使用,且入口Exe被发布为自包含程序,会找不到Controller) /// /// 本地端口 /// Controller所在程序集XML描述文档名称,默认使用入口程序生成的xml /// DTO所在程序集XML描述文档,默认使用入口程序生成的xml /// 要启用的静态目录预览及文件下载的目录(已经包含upload、download、logs三个目录) /// 使用DI注入时程序集标识 /// public static void Start(int _localPort, string dtoXmlName = null, string[] staticDir = null, string controllerXmlName = null, string dllKey = "DW5S") { BaseController.EnsureAssemblyLoaded(); _cts = new CancellationTokenSource(); if (controllerXmlName == null) controllerXmlName = $"{AppDomain.CurrentDomain.FriendlyName}.xml"; if (dtoXmlName == null) dtoXmlName = $"{AppDomain.CurrentDomain.FriendlyName}.xml"; List listDir = new List(); if (staticDir != null) { listDir.AddRange(staticDir.Select(p => p.ToLower()).Distinct()); } if (!listDir.Contains("upload")) { listDir.Add("upload"); } if (!listDir.Contains("download")) { listDir.Add("download"); } if (!listDir.Contains("logs")) { listDir.Add("logs"); } staticDir = listDir.ToArray(); var assemblies = AppDomain.CurrentDomain.GetAllAssemblies(dllKey); assemblies = assemblies.Take(assemblies.Length - 1).ToArray(); if (assemblies == null) { throw new Exception($"未扫描到包含{dllKey}字符串的程序集"); } foreach (var item in assemblies) { Console.WriteLine($"已加载DI注入程序集[{item.FullName}]"); } var controllerAssemblies = assemblies.Where(p => p.GetTypes().Any(q => { if (q.Name.EndsWith("Controller") && q.IsSubclassOf(typeof(BaseController))) return true; return false; })).ToList(); if (controllerAssemblies == null || !controllerAssemblies.Any()) { throw new Exception("未找到Controller所在的程序集"); } var builder = WebApplication.CreateBuilder(); builder.Services.AddRouting(t => t.LowercaseUrls = true);//全部路由默认显示小写 #region 请求大小限制200MB及Http2支持 builder.WebHost.ConfigureKestrel(options => { options.Limits.MaxRequestBodySize = 200 << 20;//200MB options.ListenAnyIP(Convert.ToInt32(_localPort), listenOptions => { listenOptions.Protocols = HttpProtocols.Http1AndHttp2; }); }); builder.Services.Configure(options => { options.MultipartBodyLengthLimit = 200 << 20;//通过表单上传最大200MB }); #endregion #region 初始化日志 //取消默认的日志Provider builder.Logging.ClearProviders(); builder.Host.UseSerilog((builderContext, config) => { var basePath = AppContext.BaseDirectory; var outputTemplate = "{Timestamp:yyyy-MM-dd HH:mm:ss.fff}[线程={ThreadId}][{Level:u3}]{Message:lj}{NewLine}{Exception}"; config.Enrich.FromLogContext() .Enrich.With(new SerilogEnricher()) .WriteTo.Console(outputTemplate: outputTemplate) .WriteTo.Logger(p => p.Filter.ByIncludingOnly(e => e.Level == Serilog.Events.LogEventLevel.Information) .WriteTo.File(Path.Combine(basePath, "Logs", "Info", ".log"), rollingInterval: Serilog.RollingInterval.Day, outputTemplate: outputTemplate)) .WriteTo.Logger(p => p.Filter.ByIncludingOnly(e => e.Level == Serilog.Events.LogEventLevel.Warning) .WriteTo.File(Path.Combine(basePath, "Logs", "Warning", ".log"), rollingInterval: Serilog.RollingInterval.Day, outputTemplate: outputTemplate)) .WriteTo.Logger(p => p.Filter.ByIncludingOnly(e => e.Level == Serilog.Events.LogEventLevel.Error) .WriteTo.File(Path.Combine(basePath, "Logs", "Error", ".log"), rollingInterval: Serilog.RollingInterval.Day, outputTemplate: outputTemplate)); }); builder.Logging.AddSerilog(); #endregion #region 启用静态文件缓存和压缩(已屏蔽,采集文件压缩率不高) //builder.Services.AddResponseCaching(); //builder.Services.AddResponseCompression(); #endregion #region 注入常用服务 //系统缓存,可以其它地方使用IMemoryCache接口 builder.Services.AddMemoryCache(); builder.Services.AddSignalR(); //http上下文 builder.Services.AddSingleton(); //HttpClient builder.Services.AddHttpClient();//IHttpClientFactory builder.Services.AddGrpc(); //builder.Services.AddGrpcClient(p=> //{ // p.Address = new Uri("http://127.0.0.1:16001"); //}).ConfigureChannel(p => //{ //}); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Version = "v1", Title = $"{Path.GetFileNameWithoutExtension(Process.GetCurrentProcess().MainModule.FileName)}Http接口文档", }); var basePath = AppDomain.CurrentDomain.BaseDirectory; var xmlPath = Path.Combine(basePath, controllerXmlName);//Controller xml c.IncludeXmlComments(xmlPath, true); xmlPath = Path.Combine(basePath, "05.DW5S.WebApi.xml");//BaesController xml c.IncludeXmlComments(xmlPath, true); c.OrderActionsBy(o => o.RelativePath); xmlPath = Path.Combine(basePath, dtoXmlName);//dto xml c.IncludeXmlComments(xmlPath); }); #endregion #region 注入Controller、Service、Repository、HostedService //让Controller的实例由autofac创建,而不是由dotnet框架创建,以便在控制器中使用autofac高级功能 builder.Services.Replace(ServiceDescriptor.Transient()); //将框架默认IOC容器替换为Autofac容器 builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()); var hostBuilder = builder.Host.ConfigureContainer(builder => { //注入IRepository builder.RegisterGeneric(typeof(Repository<,>)).As(typeof(IRepository<,>)) .InstancePerLifetimeScope(); builder.RegisterAssemblyTypes(assemblies) .Where(t => t.Name.Contains("Repository")) .AsClosedTypesOf(typeof(IRepository<,>)) .InstancePerLifetimeScope(); //注入DbContext builder.RegisterType() .InstancePerLifetimeScope(); //注入IUnitOfWork builder.RegisterType().As() .InstancePerLifetimeScope(); //注入Controllerer并自动注入Controller中的IUnitOfwork、ILogger、Service等属性 builder.RegisterAssemblyTypes(assemblies) .Where(type => type.Name.EndsWith("Controller")) .PropertiesAutowired((propInfo, instance) =>//Controller中的属性支持自动注入 propInfo.Name.EndsWith("Autowired") || //以Autowired名称结尾的属性自动注入 propInfo.GetCustomAttribute() != null ||//带有Autowired特性的属性自动注入 propInfo.PropertyType == typeof(Serilog.ILogger) || propInfo.PropertyType == typeof(IUnitOfWork) || propInfo.PropertyType.ToString().EndsWith("Service"))//以Service结尾的属性自动注入 .InstancePerLifetimeScope(); //注入Service builder.RegisterAssemblyTypes(assemblies) .Where(type => type.Name.EndsWith("Service") && type.BaseType == typeof(object)) .PropertiesAutowired((propInfo, instance) =>//Service中的属性支持自动注入 propInfo.Name.EndsWith("Autowired") || //以Autowired名称结尾的属性自动注入 propInfo.GetCustomAttribute() != null|| propInfo.PropertyType == typeof(Serilog.ILogger) || propInfo.PropertyType == typeof(IUnitOfWork))//带有Autowired特性的属性自动注入 .AsSelf()//接口的默认实现 .InstancePerLifetimeScope(); //注入BackgroundService后台服务 builder.RegisterAssemblyTypes(assemblies) .Where(type => type.Name.EndsWith("Service") && type.IsSubclassOf(typeof(BackgroundService))) .PropertiesAutowired((propInfo, instance) =>//BackgroundService类中的属性支持自动注入 propInfo.Name.EndsWith("Autowired") || //以Autowired名称结尾的属性自动注入 propInfo.GetCustomAttribute() != null ||//带有Autowired特性的属性自动注入 propInfo.PropertyType == typeof(Serilog.ILogger) || propInfo.PropertyType == typeof(IUnitOfWork) || propInfo.PropertyType.ToString().EndsWith("Service"))//以Service结尾的属性自动注入 .As()//后台服务方式注入 .SingleInstance(); }); #endregion builder.Services.AddControllers(c => { //过滤器 //c.Filters.Add(); }) //这种方式不方便加载Controller分布在不同dll中的情况,因此使用了ConfigureApplicationPartManager //.AddApplicationPart(controllerAssemblies.First()) .ConfigureApplicationPartManager(apm => { foreach (var item in controllerAssemblies) { var part = new AssemblyPart(item); apm.ApplicationParts.Add(part); } }) .AddNewtonsoftJson(options => { //修改属性名称的序列化方式,首字母小写 options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); //修改时间的序列化方式 options.SerializerSettings.Converters.Add(new IsoDateTimeConverter() { DateTimeFormat = "yyyy-MM-dd HH:mm:ss" }); }); var app = builder.Build(); var container = app.Services.GetAutofacRoot();//autofac容器 IocContainer.Init(container); #region 启用静态资源访问 foreach (var item in staticDir) { //静态文件物理路径 if (string.IsNullOrWhiteSpace(item)) continue; string path; if (item.Contains(":")) path = item;//绝对路径 else path = Path.Combine(AppContext.BaseDirectory, item);//相对路径 Directory.CreateDirectory(path); //静态资源缓存时间(300秒,600秒) var cachePeriod = app.Environment.IsDevelopment() ? "300" : "600"; //启用静态文件路由 app.UseStaticFiles(new StaticFileOptions { ServeUnknownFileTypes = true, FileProvider = new PhysicalFileProvider(path), RequestPath = $"/{Path.GetFileName(item)}", OnPrepareResponse = ctx => { ctx.Context.Response.Headers.Append("Cache-Control", $"public, max-age={cachePeriod}"); } }); //启用目录浏览所有文件 app.UseDirectoryBrowser(new DirectoryBrowserOptions() { FileProvider = new PhysicalFileProvider(path), RequestPath = $"/{Path.GetFileName(item)}" }); } app.UseResponseCaching(); #endregion //app.MapGrpcService(); app.Urls.Add($"http://+:{_localPort}"); app.UseSwagger(); app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "v1版本"); }); app.UseExceptionHandler("/Home/Error"); //app.UseAuthentication();//身份验证 app.UseAuthorization(); //授权 //app.UseMiddleware();//全局异常处理 app.MapControllers(); //app.Map("/", () => "必须通过GRPC客户端访问此接口"); //app.MapHub("/Chat"); app.RunAsync(_cts.Token); //app.Run(); } /// /// 结束WebAPI /// public static void Stop() { _cts?.Cancel(); } } }