using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Reflection; using System.Runtime.Remoting.Contexts; using System.Text; using System.Threading; using System.Web.Http; using System.Web.Http.Description; using System.Web.Http.Filters; using System.Web.Http.Metadata; using System.Web.Http.Routing; using System.Web.Http.Validation; using System.Web.Http.Validation.Providers; using System.Xml; using System.Xml.Linq; using Autofac; using Autofac.Integration.Owin; using Microsoft.Owin; using Microsoft.Owin.Cors; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using Owin; using Swashbuckle.Application; using Swashbuckle.Swagger; using Autofac.Integration.WebApi; using Autofac.Core; using Microsoft.Owin.FileSystems; using Microsoft.Owin.StaticFiles; using System.Threading.Tasks; using System.Diagnostics; using System.Web.Http.Controllers; using Microsoft.Owin.Hosting; [assembly: OwinStartup(typeof(XdCxRhDW.WebApi.Startup))] namespace XdCxRhDW.WebApi { /// /// WebApi启动类 /// public class Startup { private static List svrs = new List(); private static List configs = new List(); private static string _controllerXmlName { get; set; } private static string _dtoXmlName { get; set; } internal static string _timeZoneUtc; /// /// 启动http服务,会自动关闭之前启动的服务 /// /// /// controller所在程序集xml文件名称 /// dto所在程序集xml文件名称 public static void Start(int port, string controllerXmlName, string dtoXmlName, string timeZoneUtc = "UTC+08:00") { //不要删除这句代码,VS引用优化检测到没有使用Microsoft.Owin.Host.HttpListener.dll则可能不会将此dll复制到本地导致http服务启动失败 Console.WriteLine(typeof(Microsoft.Owin.Host.HttpListener.OwinHttpListener)); _timeZoneUtc = timeZoneUtc; _controllerXmlName = controllerXmlName; _dtoXmlName = dtoXmlName; foreach (var item in svrs) { try { item.Dispose(); } catch { } } foreach (var item in configs) { try { item.Filters.Clear(); item.Services.Dispose(); item.Routes.Dispose(); item.Formatters.Clear(); item.Dispose(); } catch { } } svrs.Clear(); configs.Clear(); StartOptions options = new StartOptions(); options.Urls.Add($"http://+:{port}"); var svr = WebApp.Start(options); svrs.Add(svr); GC.Collect(); } /// /// /// /// public void Configuration(IAppBuilder app) { //启用目录浏览和静态文件 Directory.CreateDirectory("wwwroot"); var physicalFileSystem = new PhysicalFileSystem(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "wwwroot"));//目录浏览物理地址 var options = new FileServerOptions { RequestPath = new PathString("/wwwroot"),//目录浏览地址 EnableDefaultFiles = true, EnableDirectoryBrowsing = true,//启用目录浏览 FileSystem = physicalFileSystem }; options.StaticFileOptions.FileSystem = physicalFileSystem; options.StaticFileOptions.ServeUnknownFileTypes = true;//允许下载wwwroot中的所有类型文件 app.UseFileServer(options); HttpConfiguration config = new HttpConfiguration(); configs.Add(config); IEnumerable modelValidatorProviders = config.Services.GetModelValidatorProviders(); DataAnnotationsModelValidatorProvider provider = (DataAnnotationsModelValidatorProvider) modelValidatorProviders.Single(x => x is DataAnnotationsModelValidatorProvider); //var provider2 = (DataMemberModelValidatorProvider) // modelValidatorProviders.Single(x => x is DataMemberModelValidatorProvider); provider.RegisterDefaultValidatableObjectAdapter(typeof(CustomModelValidator)); JsonSerializerSettings setting = new JsonSerializerSettings() { //日期类型默认格式化处理 DateFormatHandling = DateFormatHandling.MicrosoftDateFormat, DateFormatString = "yyyy-MM-dd HH:mm:ss.fff", //驼峰样式 //ContractResolver = new CamelCasePropertyNamesContractResolver(), //空值处理 //NullValueHandling = NullValueHandling.Ignore, //设置序列化的最大层数 MaxDepth = 10, //解决json序列化时的循环引用问题 ReferenceLoopHandling = ReferenceLoopHandling.Ignore }; config.Formatters.JsonFormatter.SerializerSettings = setting; config.Formatters.Remove(config.Formatters.XmlFormatter); config.Filters.Add(new HandlerErrorAttribute()); config.Filters.Add(new ValidateFilter()); //config.Routes.MapHttpRoute("DefaultApiWithId", "Api/{controller}/{id}", new { id = RouteParameter.Optional }, new { id = @"\d+" }); config.Routes.MapHttpRoute("DefaultApiWithAction", "Api/{controller}/{action}"); //config.Routes.MapHttpRoute("DefaultApiGet", "Api/{controller}", new { action = "Get" }, new { httpMethod = new HttpMethodConstraint(HttpMethod.Get) }); //config.Routes.MapHttpRoute("DefaultApiPost", "Api/{controller}", new { action = "Post" }, new { httpMethod = new HttpMethodConstraint(HttpMethod.Post) }); ConfigureSwagger(config); //添加路由路径 config.MapHttpAttributeRoutes(); var builder = new ContainerBuilder(); var controllerAssemblys = AppDomain.CurrentDomain.GetAssemblies().Where(p => p.GetTypes().Any(t => t.BaseType == typeof(BaseController))).ToArray(); builder.RegisterApiControllers(controllerAssemblys); var serviceTypes = AppDomain.CurrentDomain.GetAssemblies().SelectMany(p => p.GetTypes()) .Where(p => p.Namespace != null && p.Namespace.EndsWith(".Service")).ToList(); foreach (var serviceType in serviceTypes) { //单例模式注入Service builder.RegisterType(serviceType).SingleInstance(); } var container = builder.Build(); config.DependencyResolver = new AutofacWebApiDependencyResolver(container); GlobalConfiguration.Configuration.DependencyResolver = config.DependencyResolver; AutofacUtil.Container = container; //app.UseAutofacLifetimeScopeInjector(container); app.UseAutofacMiddleware(container); app.UseAutofacWebApi(config); app.UseCors(CorsOptions.AllowAll); app.UseWebApi(config); } private static void ConfigureSwagger(HttpConfiguration config) { var thisAssembly = typeof(Startup).Assembly; string exeName = Assembly.GetEntryAssembly().GetName().Name; config.EnableSwagger(c => { c.IgnoreObsoleteActions();//忽略过时的方法 c.IgnoreObsoleteProperties();//忽略过时的属性 c.PrettyPrint();//漂亮缩进 c.SingleApiVersion("v1", $"{exeName}Http接口"); c.ApiKey("123456"); var webApiXmlPath1 = $"{AppDomain.CurrentDomain.BaseDirectory}{Path.GetFileNameWithoutExtension(_dtoXmlName)}.xml"; c.IncludeXmlComments(webApiXmlPath1);//dto模型描述 var webApiXmlPath2 = $"{AppDomain.CurrentDomain.BaseDirectory}{Path.GetFileNameWithoutExtension(_controllerXmlName)}.xml"; c.IncludeXmlComments(webApiXmlPath2);//控制器中方法描述 var webApiXmlPath3 = $"{AppDomain.CurrentDomain.BaseDirectory}{typeof(AjaxResult).Assembly.GetName().Name}.xml"; c.IncludeXmlComments(webApiXmlPath3);//返回值描述 //控制器本身描述 string controllerXmlPath = $"{AppDomain.CurrentDomain.BaseDirectory}{Path.GetFileNameWithoutExtension(_controllerXmlName)}.xml"; c.CustomProvider(defaultProvider => new SwaggerControllerDescProvider(defaultProvider, controllerXmlPath)); c.OperationFilter(); c.SchemaFilter(); c.SchemaFilter(); }) .EnableSwaggerUi(c => { c.InjectJavaScript(thisAssembly, $"{thisAssembly.GetName().Name}.Swagger.js"); //c.DocumentTitle($"{exeName}Http接口"); }); } class FileUploadOperation : IOperationFilter { public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription) { if (operation.parameters == null) { operation.parameters = new List(); } var requestAttributes = apiDescription.GetControllerAndActionAttributes(); foreach (var attr in requestAttributes) { operation.parameters.Add(new Swashbuckle.Swagger.Parameter { description = attr.Description, name = attr.Name, @in = "formData", required = true, type = "file", }); operation.consumes.Add("multipart/form-data"); } } } class ValidateFilter : IActionFilter { public bool AllowMultiple { get; } public Task ExecuteActionFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func> continuation) { if (!actionContext.ModelState.IsValid) { string msg = ""; var err = actionContext.ModelState.Values?.Last()?.Errors?.Last(); if (err != null) { if (!string.IsNullOrWhiteSpace(err.ErrorMessage)) msg = err.ErrorMessage; else msg = err.Exception.Message; } return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent( JsonConvert.SerializeObject( new AjaxResult { code = 0, data = null, msg = msg, }), Encoding.UTF8, "application/json") }); } return continuation(); } } class HandlerErrorAttribute : ExceptionFilterAttribute { /// /// 控制器方法中出现异常,会调用该方法捕获异常 /// /// 提供使用 public override void OnException(HttpActionExecutedContext context) { if (context.Exception.GetType() != typeof(System.OperationCanceledException)) Serilog.Log.Error(context.Exception, context.Exception.Message); else return; base.OnException(context); string msg = context.Exception.Message; if (context.Exception.GetType() == typeof(FileNotFoundException)) { //防止程序路径泄露到前端 msg = "未能找到文件" + context.Exception.Message.Substring(context.Exception.Message.LastIndexOf("\\") + 1); } throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent( JsonConvert.SerializeObject( new AjaxResult { code = 0, data = null, msg = msg }), Encoding.UTF8, "application/json") }); } }; class SwaggerControllerDescProvider : ISwaggerProvider { private readonly ISwaggerProvider _swaggerProvider; private static ConcurrentDictionary _cache = new ConcurrentDictionary(); private readonly string _xml; /// /// /// /// /// xml文档路径 public SwaggerControllerDescProvider(ISwaggerProvider swaggerProvider, string xml) { _swaggerProvider = swaggerProvider; _xml = xml; } public SwaggerDocument GetSwagger(string rootUrl, string apiVersion) { var cacheKey = string.Format("{0}_{1}", rootUrl, apiVersion); SwaggerDocument srcDoc = null; //只读取一次 if (!_cache.TryGetValue(cacheKey, out srcDoc)) { srcDoc = _swaggerProvider.GetSwagger(rootUrl, apiVersion); srcDoc.vendorExtensions = new Dictionary { { "ControllerDesc", GetControllerDesc() } }; _cache.TryAdd(cacheKey, srcDoc); } return srcDoc; } /// /// 从API文档中读取控制器描述 /// /// 所有控制器描述 public ConcurrentDictionary GetControllerDesc() { string xmlpath = _xml; ConcurrentDictionary controllerDescDict = new ConcurrentDictionary(); if (File.Exists(xmlpath)) { XmlDocument xmldoc = new XmlDocument(); xmldoc.Load(xmlpath); string type = string.Empty, path = string.Empty, controllerName = string.Empty; string[] arrPath; int length = -1, cCount = "Controller".Length; XmlNode summaryNode = null; foreach (XmlNode node in xmldoc.SelectNodes("//member")) { type = node.Attributes["name"].Value; if (type.StartsWith("T:")) { //控制器 arrPath = type.Split('.'); length = arrPath.Length; controllerName = arrPath[length - 1]; if (controllerName.EndsWith("Controller")) { //获取控制器注释 summaryNode = node.SelectSingleNode("summary"); string key = controllerName.Remove(controllerName.Length - cCount, cCount); if (summaryNode != null && !string.IsNullOrEmpty(summaryNode.InnerText) && !controllerDescDict.ContainsKey(key)) { controllerDescDict.TryAdd(key, summaryNode.InnerText.Trim()); } } } } } return controllerDescDict; } } } /// /// Swagger文件上传特性标注 /// [AttributeUsage(AttributeTargets.Method)] public sealed class SwaggerFormAttribute : Attribute { /// /// /// public SwaggerFormAttribute() { this.Name = "文件"; this.Description = "选择文件"; } /// /// Swagger特性标注 /// /// /// public SwaggerFormAttribute(string name, string description) { Name = name; Description = description; } /// /// 名称 /// public string Name { get; private set; } /// /// 描述 /// public string Description { get; private set; } } /// /// autofac属性注入 /// [AttributeUsage(AttributeTargets.Property)] public class AutowiredAttribute : Attribute { } // 属性注入选择器 class AutowiredPropertySelector : IPropertySelector { public bool InjectProperty(PropertyInfo propertyInfo, object instance) { // 带有 AutowiredAttribute 特性的属性会进行属性注入 return propertyInfo.CustomAttributes.Any(it => it.AttributeType == typeof(AutowiredAttribute)); } } class SwaggerEnumFilter : ISchemaFilter { public void Apply(Schema schema, SchemaRegistry schemaRegistry, Type type) { UpdateSchemaDescription(schema, type); } private void UpdateSchemaDescription(Schema schema, Type type) { if (type.IsEnum)//枚举直接应用在controller接口中 { var items = GetEnumInfo(type); if (items.Length > 0) { var description = GetEnumInfo(type); schema.description = string.IsNullOrEmpty(schema.description) ? description : $"{schema.description}:{description}"; } } else if (type.IsClass && type != typeof(string))//枚举在类的属性中 { if (schema.properties == null) return; var props = type.GetProperties(); foreach (var prop in props) { if (schema.properties.ContainsKey(prop.Name)) { var propScheama = schema.properties[prop.Name]; if (prop.PropertyType.IsClass && prop.PropertyType != typeof(string)) { UpdateSchemaDescription(propScheama, prop.PropertyType); } else { if (prop.PropertyType.IsEnum) { var description = GetEnumInfo(prop.PropertyType); propScheama.description = string.IsNullOrWhiteSpace(propScheama.description) ? description : $"{propScheama.description}:{description}"; propScheama.@enum = null; } } } } } } /// /// 获取枚举值+描述 /// /// /// private string GetEnumInfo(Type enumType) { var fields = enumType.GetFields(); List list = new List(); foreach (var field in fields) { if (!field.FieldType.IsEnum) continue; string description = null; if (description == null)//取DescriptionAttribute的值 { var descriptionAttr = field.GetCustomAttribute(); if (descriptionAttr != null && !string.IsNullOrWhiteSpace(descriptionAttr.Description)) { description = descriptionAttr.Description; } } if (description == null)//取DisplayAttribute的值 { var dispalyAttr = field.GetCustomAttribute(); if (dispalyAttr != null && !string.IsNullOrWhiteSpace(dispalyAttr.Name)) { description = dispalyAttr.Name; } } if (description == null)//取DisplayNameAttribute的值 { var dispalyNameAttr = field.GetCustomAttribute(); if (dispalyNameAttr != null && !string.IsNullOrWhiteSpace(dispalyNameAttr.DisplayName)) { description = dispalyNameAttr.DisplayName; } } if (description == null)//取字段名 { description = field.Name; } var value = field.GetValue(null); list.Add($"{description}={(int)value}"); } if (enumType.GetCustomAttribute() != null)//支持按位与的枚举 { list.Add("(多个类型请将对应数字相加)"); } return string.Join(",", list); } } class CustomModelValidator : ModelValidator { public CustomModelValidator(IEnumerable modelValidatorProviders) : base(modelValidatorProviders) { } public override IEnumerable Validate(ModelMetadata metadata, object container) { if (metadata.IsComplexType && metadata.Model == null) { return new List { new ModelValidationResult { MemberName = metadata.GetDisplayName(), Message = "请求参数对象不能为空" } }; } if (typeof(IValidatableObject).IsAssignableFrom(metadata.ModelType)) { var validationResult = (metadata.Model as IValidatableObject).Validate(new ValidationContext(metadata.Model)); if (validationResult != null) { var modelValidationResults = new List(); foreach (var result in validationResult) { if (result == null) continue; modelValidationResults.Add(new ModelValidationResult { MemberName = string.Join(",", result.MemberNames), Message = result.ErrorMessage }); } return modelValidationResults; } return null; } return GetModelValidator(ValidatorProviders).Validate(metadata, container); } } class SwaggerDefalutValueFilter : ISchemaFilter { public SwaggerDefalutValueFilter() { var cc = this.GetHashCode(); } public void Apply(Schema schema, SchemaRegistry schemaRegistry, Type type) { if (schema.properties == null) { return; } var props = type.GetProperties().Where(p => p.PropertyType == typeof(DateTime) || p.PropertyType == typeof(DateTime?)); var props2 = type.GetProperties().Where(p => p.PropertyType == typeof(DateTimeOffset) || p.PropertyType == typeof(DateTimeOffset?)); foreach (PropertyInfo propertyInfo in props) { foreach (KeyValuePair property in schema.properties) { if (propertyInfo.Name == property.Key) { property.Value.example = "2023-05-12 12:00:00"; if (!string.IsNullOrWhiteSpace(Startup._timeZoneUtc) && propertyInfo.Name.EndsWith("Time")) { property.Value.description = $"{property.Value.description}({Startup._timeZoneUtc})"; } else { } break; } } } foreach (PropertyInfo propertyInfo in props2) { foreach (KeyValuePair property in schema.properties) { if (propertyInfo.Name == property.Key) { property.Value.example = "2023-05-12 12:00:00 +0800"; if (!string.IsNullOrWhiteSpace(Startup._timeZoneUtc) && propertyInfo.Name.EndsWith("Time")) { property.Value.description = $"{property.Value.description}({Startup._timeZoneUtc})"; } else { } break; } } } } } }