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;
}
}
}
}
}
}