123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320 |
- 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
- {
- /// <summary>
- ///
- /// </summary>
- public static class WebApiHelper
- {
- private static CancellationTokenSource _cts;
- /// <summary>
- /// 启动AspNetCore WebAPI
- /// (如果Controller所在程序集未被代码使用,且入口Exe被发布为自包含程序,会找不到Controller)
- /// </summary>
- /// <param name="_localPort">本地端口</param>
- /// <param name="controllerXmlName">Controller所在程序集XML描述文档名称,默认使用入口程序生成的xml</param>
- /// <param name="dtoXmlName">DTO所在程序集XML描述文档,默认使用入口程序生成的xml</param>
- /// <param name="staticDir">要启用的静态目录预览及文件下载的目录(已经包含upload、download、logs三个目录)</param>
- /// <param name="dllKey">使用DI注入时程序集标识</param>
- /// <exception cref="Exception"></exception>
- 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<string> listDir = new List<string>();
- 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<FormOptions>(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<IHttpContextAccessor, Microsoft.AspNetCore.Http.HttpContextAccessor>();
- //HttpClient
- builder.Services.AddHttpClient();//IHttpClientFactory
- builder.Services.AddGrpc();
- //builder.Services.AddGrpcClient<object>(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<IControllerActivator, ServiceBasedControllerActivator>());
- //将框架默认IOC容器替换为Autofac容器
- builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
- var hostBuilder = builder.Host.ConfigureContainer<Autofac.ContainerBuilder>(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<SqliteContext>()
- .InstancePerLifetimeScope();
- //注入IUnitOfWork
- builder.RegisterType<UnitOfWork>().As<IUnitOfWork>()
- .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<AutowiredAttribute>() != 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<AutowiredAttribute>() != 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<AutowiredAttribute>() != null ||//带有Autowired特性的属性自动注入
- propInfo.PropertyType == typeof(Serilog.ILogger) ||
- propInfo.PropertyType == typeof(IUnitOfWork) ||
- propInfo.PropertyType.ToString().EndsWith("Service"))//以Service结尾的属性自动注入
- .As<IHostedService>()//后台服务方式注入
- .SingleInstance();
- });
- #endregion
- builder.Services.AddControllers(c =>
- {
- //过滤器
- //c.Filters.Add<LogActionFilter>();
- })
- //这种方式不方便加载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<FileService>();
- 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<ExceptionHandlingMiddleware>();//全局异常处理
- app.MapControllers();
- //app.Map("/", () => "必须通过GRPC客户端访问此接口");
- //app.MapHub<ChatHub>("/Chat");
- app.RunAsync(_cts.Token);
- //app.Run();
- }
- /// <summary>
- /// 结束WebAPI
- /// </summary>
- public static void Stop()
- {
- _cts?.Cancel();
- }
- }
- }
|