Startup.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. using System;
  2. using System.Collections.Concurrent;
  3. using System.Collections.Generic;
  4. using System.ComponentModel;
  5. using System.ComponentModel.DataAnnotations;
  6. using System.IO;
  7. using System.Linq;
  8. using System.Net;
  9. using System.Net.Http;
  10. using System.Reflection;
  11. using System.Runtime.Remoting.Contexts;
  12. using System.Text;
  13. using System.Threading;
  14. using System.Web.Http;
  15. using System.Web.Http.Description;
  16. using System.Web.Http.Filters;
  17. using System.Web.Http.Metadata;
  18. using System.Web.Http.Routing;
  19. using System.Web.Http.Validation;
  20. using System.Web.Http.Validation.Providers;
  21. using System.Xml;
  22. using System.Xml.Linq;
  23. using Microsoft.Owin;
  24. using Microsoft.Owin.Cors;
  25. using Newtonsoft.Json;
  26. using Newtonsoft.Json.Serialization;
  27. using Owin;
  28. using Swashbuckle.Application;
  29. using Swashbuckle.Swagger;
  30. using XdCxRhDW.App.WebAPI;
  31. [assembly: OwinStartup(typeof(XdCxRhDW.App.WebAPI.Startup))]
  32. namespace XdCxRhDW.App.WebAPI
  33. {
  34. class Startup
  35. {
  36. public void Configuration(IAppBuilder app)
  37. {
  38. HttpConfiguration config = new HttpConfiguration();
  39. IEnumerable<ModelValidatorProvider> modelValidatorProviders = config.Services.GetModelValidatorProviders();
  40. DataAnnotationsModelValidatorProvider provider = (DataAnnotationsModelValidatorProvider)
  41. modelValidatorProviders.Single(x => x is DataAnnotationsModelValidatorProvider);
  42. provider.RegisterDefaultValidatableObjectAdapter(typeof(CustomModelValidator));
  43. JsonSerializerSettings setting = new JsonSerializerSettings()
  44. {
  45. //日期类型默认格式化处理
  46. DateFormatHandling = DateFormatHandling.MicrosoftDateFormat,
  47. DateFormatString = "yyyy-MM-dd HH:mm:ss",
  48. //驼峰样式
  49. //ContractResolver = new CamelCasePropertyNamesContractResolver(),
  50. //空值处理
  51. //NullValueHandling = NullValueHandling.Ignore,
  52. //设置序列化的最大层数
  53. MaxDepth = 10,
  54. //解决json序列化时的循环引用问题
  55. ReferenceLoopHandling = ReferenceLoopHandling.Ignore
  56. };
  57. config.Formatters.JsonFormatter.SerializerSettings = setting;
  58. config.Formatters.Remove(config.Formatters.XmlFormatter);
  59. config.Filters.Add(new HandlerErrorAttribute());
  60. //config.Routes.MapHttpRoute("DefaultApiWithId", "Api/{controller}/{id}", new { id = RouteParameter.Optional }, new { id = @"\d+" });
  61. config.Routes.MapHttpRoute("DefaultApiWithAction", "Api/{controller}/{action}");
  62. //config.Routes.MapHttpRoute("DefaultApiGet", "Api/{controller}", new { action = "Get" }, new { httpMethod = new HttpMethodConstraint(HttpMethod.Get) });
  63. //config.Routes.MapHttpRoute("DefaultApiPost", "Api/{controller}", new { action = "Post" }, new { httpMethod = new HttpMethodConstraint(HttpMethod.Post) });
  64. ConfigureSwagger(config);
  65. //添加路由路径
  66. config.MapHttpAttributeRoutes();
  67. app.UseCors(CorsOptions.AllowAll);
  68. app.UseWebApi(config);
  69. }
  70. private static void ConfigureSwagger(HttpConfiguration config)
  71. {
  72. var thisAssembly = typeof(Startup).Assembly;
  73. config.EnableSwagger(c =>
  74. {
  75. c.IgnoreObsoleteActions();//忽略过时的方法
  76. c.IgnoreObsoleteProperties();//忽略过时的属性
  77. c.PrettyPrint();//漂亮缩进
  78. c.SingleApiVersion("v1", "多模式融合定位平台Http接口");
  79. c.ApiKey("123456");
  80. //设置接口描述xml路径地址
  81. var webApiXmlPath1 = $"{AppDomain.CurrentDomain.BaseDirectory}{Path.GetFileNameWithoutExtension(System.AppDomain.CurrentDomain.FriendlyName)}.xml";
  82. c.IncludeXmlComments(webApiXmlPath1);
  83. var webApiXmlPath2 = $"{AppDomain.CurrentDomain.BaseDirectory}XdCxRhDw.Dto.xml";
  84. c.IncludeXmlComments(webApiXmlPath2);
  85. //c.UseFullTypeNameInSchemaIds();//使用完整类型名称
  86. //加入控制器描述
  87. c.CustomProvider((defaultProvider) => new SwaggerControllerDescProvider(defaultProvider, webApiXmlPath1));
  88. c.OperationFilter<FileUploadOperation>();
  89. c.SchemaFilter<SwaggerEnumFilter>();
  90. })
  91. .EnableSwaggerUi(c =>
  92. {
  93. c.InjectJavaScript(thisAssembly, "XdCxRhDW.App.WebAPI.Swagger.js");
  94. c.DocumentTitle("多模式融合定位平台Http接口");
  95. });
  96. }
  97. /// <summary>
  98. /// Swagger文件上传特性标注
  99. /// </summary>
  100. [AttributeUsage(AttributeTargets.Method)]
  101. public sealed class SwaggerFormAttribute : Attribute
  102. {
  103. public SwaggerFormAttribute()
  104. {
  105. this.Name = "文件";
  106. this.Description = "选择文件";
  107. }
  108. /// <summary>
  109. /// Swagger特性标注
  110. /// </summary>
  111. /// <param name="name"></param>
  112. /// <param name="description"></param>
  113. public SwaggerFormAttribute(string name, string description)
  114. {
  115. Name = name;
  116. Description = description;
  117. }
  118. /// <summary>
  119. /// 名称
  120. /// </summary>
  121. public string Name { get; private set; }
  122. /// <summary>
  123. /// 描述
  124. /// </summary>
  125. public string Description { get; private set; }
  126. }
  127. public class FileUploadOperation : IOperationFilter
  128. {
  129. public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
  130. {
  131. if (operation.parameters == null)
  132. {
  133. operation.parameters = new List<Parameter>();
  134. }
  135. var requestAttributes = apiDescription.GetControllerAndActionAttributes<SwaggerFormAttribute>();
  136. foreach (var attr in requestAttributes)
  137. {
  138. operation.parameters.Add(new Parameter
  139. {
  140. description = attr.Description,
  141. name = attr.Name,
  142. @in = "formData",
  143. required = true,
  144. type = "file",
  145. });
  146. operation.consumes.Add("multipart/form-data");
  147. }
  148. //if (operation.operationId.ToLower() == "apivaluesuploadpost")
  149. //{
  150. // operation.parameters.Clear();
  151. // operation.parameters.Add(new Parameter
  152. // {
  153. // name = "uploadedFile",
  154. // @in = "formData",
  155. // description = "Upload File",
  156. // required = true,
  157. // type = "file"
  158. // });
  159. // operation.consumes.Add("multipart/form-data");
  160. //}
  161. //判断上传文件的类型,只有上传的类型是IFormCollection的才进行重写。
  162. //var paras = apiDescription.ActionDescriptor.GetParameters();
  163. //if (paras.Any(w => w.ParameterType == typeof(IFormCollection)))
  164. //{
  165. // Dictionary<string, OpenApiSchema> schema = new Dictionary<string, OpenApiSchema>();
  166. // schema["fileName"] = new OpenApiSchema { Description = "Select file", Type = "string", Format = "binary" };
  167. // Dictionary<string, OpenApiMediaType> content = new Dictionary<string, OpenApiMediaType>();
  168. // content["multipart/form-data"] = new OpenApiMediaType { Schema = new OpenApiSchema { Type = "object", Properties = schema } };
  169. // operation.RequestBody = new OpenApiRequestBody() { Content = content };
  170. //}
  171. }
  172. }
  173. public class HandlerErrorAttribute : ExceptionFilterAttribute
  174. {
  175. /// <summary>
  176. /// 控制器方法中出现异常,会调用该方法捕获异常
  177. /// </summary>
  178. /// <param name="context">提供使用</param>
  179. public override void OnException(HttpActionExecutedContext context)
  180. {
  181. base.OnException(context);
  182. Serilog.Log.Error(context.Exception, context.Exception.Message);
  183. //LogFile.WriteError(context.Exception.Message);
  184. throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.OK)
  185. {
  186. Content = new StringContent(
  187. JsonConvert.SerializeObject(
  188. new
  189. {
  190. code = -1,
  191. data = "",
  192. msg = context.Exception.Message
  193. }), Encoding.UTF8, "text/json")
  194. });
  195. }
  196. };
  197. public class SwaggerControllerDescProvider : ISwaggerProvider
  198. {
  199. private readonly ISwaggerProvider _swaggerProvider;
  200. private static ConcurrentDictionary<string, SwaggerDocument> _cache = new ConcurrentDictionary<string, SwaggerDocument>();
  201. private readonly string _xml;
  202. /// <summary>
  203. ///
  204. /// </summary>
  205. /// <param name="swaggerProvider"></param>
  206. /// <param name="xml">xml文档路径</param>
  207. public SwaggerControllerDescProvider(ISwaggerProvider swaggerProvider, string xml)
  208. {
  209. _swaggerProvider = swaggerProvider;
  210. _xml = xml;
  211. }
  212. public SwaggerDocument GetSwagger(string rootUrl, string apiVersion)
  213. {
  214. var cacheKey = string.Format("{0}_{1}", rootUrl, apiVersion);
  215. SwaggerDocument srcDoc = null;
  216. //只读取一次
  217. if (!_cache.TryGetValue(cacheKey, out srcDoc))
  218. {
  219. srcDoc = _swaggerProvider.GetSwagger(rootUrl, apiVersion);
  220. srcDoc.vendorExtensions = new Dictionary<string, object> { { "ControllerDesc", GetControllerDesc() } };
  221. _cache.TryAdd(cacheKey, srcDoc);
  222. }
  223. return srcDoc;
  224. }
  225. /// <summary>
  226. /// 从API文档中读取控制器描述
  227. /// </summary>
  228. /// <returns>所有控制器描述</returns>
  229. public ConcurrentDictionary<string, string> GetControllerDesc()
  230. {
  231. string xmlpath = _xml;
  232. ConcurrentDictionary<string, string> controllerDescDict = new ConcurrentDictionary<string, string>();
  233. if (File.Exists(xmlpath))
  234. {
  235. XmlDocument xmldoc = new XmlDocument();
  236. xmldoc.Load(xmlpath);
  237. string type = string.Empty, path = string.Empty, controllerName = string.Empty;
  238. string[] arrPath;
  239. int length = -1, cCount = "Controller".Length;
  240. XmlNode summaryNode = null;
  241. foreach (XmlNode node in xmldoc.SelectNodes("//member"))
  242. {
  243. type = node.Attributes["name"].Value;
  244. if (type.StartsWith("T:"))
  245. {
  246. //控制器
  247. arrPath = type.Split('.');
  248. length = arrPath.Length;
  249. controllerName = arrPath[length - 1];
  250. if (controllerName.EndsWith("Controller"))
  251. {
  252. //获取控制器注释
  253. summaryNode = node.SelectSingleNode("summary");
  254. string key = controllerName.Remove(controllerName.Length - cCount, cCount);
  255. if (summaryNode != null && !string.IsNullOrEmpty(summaryNode.InnerText) && !controllerDescDict.ContainsKey(key))
  256. {
  257. controllerDescDict.TryAdd(key, summaryNode.InnerText.Trim());
  258. }
  259. }
  260. }
  261. }
  262. }
  263. return controllerDescDict;
  264. }
  265. }
  266. private sealed class ApiComparer : IComparer<string>
  267. {
  268. public int Compare(string x, string y)
  269. {
  270. return x.CompareTo(y);
  271. }
  272. }
  273. }
  274. public class SwaggerEnumFilter : ISchemaFilter
  275. {
  276. public void Apply(Schema schema, SchemaRegistry schemaRegistry, Type type)
  277. {
  278. UpdateSchemaDescription(schema, type);
  279. }
  280. private void UpdateSchemaDescription(Schema schema, Type type)
  281. {
  282. if (type.IsEnum)//枚举直接应用在controller接口中
  283. {
  284. var items = GetEnumInfo(type);
  285. if (items.Length > 0)
  286. {
  287. var description = GetEnumInfo(type);
  288. schema.description = string.IsNullOrEmpty(schema.description) ? description : $"{schema.description}:{description}";
  289. }
  290. }
  291. else if (type.IsClass && type != typeof(string))//枚举在类的属性中
  292. {
  293. if (schema.properties == null) return;
  294. var props = type.GetProperties();
  295. foreach (var prop in props)
  296. {
  297. var propScheama = schema.properties[prop.Name];
  298. if (prop.PropertyType.IsClass && prop.PropertyType != typeof(string))
  299. {
  300. UpdateSchemaDescription(propScheama, prop.PropertyType);
  301. }
  302. else
  303. {
  304. if (prop.PropertyType.IsEnum)
  305. {
  306. var description = GetEnumInfo(prop.PropertyType);
  307. propScheama.description = string.IsNullOrWhiteSpace(propScheama.description) ? description : $"{propScheama.description}:{description}";
  308. propScheama.@enum = null;
  309. }
  310. }
  311. }
  312. }
  313. }
  314. /// <summary>
  315. /// 获取枚举值+描述
  316. /// </summary>
  317. /// <param name="enumType"></param>
  318. /// <returns></returns>
  319. private string GetEnumInfo(Type enumType)
  320. {
  321. var fields = enumType.GetFields();
  322. List<string> list = new List<string>();
  323. foreach (var field in fields)
  324. {
  325. if (!field.FieldType.IsEnum) continue;
  326. string description = null;
  327. if (description == null)//取DescriptionAttribute的值
  328. {
  329. var descriptionAttr = field.GetCustomAttribute<DescriptionAttribute>();
  330. if (descriptionAttr != null && !string.IsNullOrWhiteSpace(descriptionAttr.Description))
  331. {
  332. description = descriptionAttr.Description;
  333. }
  334. }
  335. if (description == null)//取DisplayAttribute的值
  336. {
  337. var dispalyAttr = field.GetCustomAttribute<DisplayAttribute>();
  338. if (dispalyAttr != null && !string.IsNullOrWhiteSpace(dispalyAttr.Name))
  339. {
  340. description = dispalyAttr.Name;
  341. }
  342. }
  343. if (description == null)//取DisplayNameAttribute的值
  344. {
  345. var dispalyNameAttr = field.GetCustomAttribute<DisplayNameAttribute>();
  346. if (dispalyNameAttr != null && !string.IsNullOrWhiteSpace(dispalyNameAttr.DisplayName))
  347. {
  348. description = dispalyNameAttr.DisplayName;
  349. }
  350. }
  351. if (description == null)//取字段名
  352. {
  353. description = field.Name;
  354. }
  355. var value = field.GetValue(null);
  356. list.Add($"{description}={(int)value}");
  357. }
  358. return string.Join(",", list);
  359. }
  360. }
  361. public class CustomModelValidator : ModelValidator
  362. {
  363. public CustomModelValidator(IEnumerable<ModelValidatorProvider> modelValidatorProviders) : base(modelValidatorProviders)
  364. {
  365. }
  366. public override IEnumerable<ModelValidationResult> Validate(ModelMetadata metadata, object container)
  367. {
  368. if (metadata.IsComplexType && metadata.Model == null)
  369. {
  370. return new List<ModelValidationResult> { new ModelValidationResult { MemberName = metadata.GetDisplayName(), Message = "请求参数对象不能为空。" } };
  371. }
  372. if (typeof(IValidatableObject).IsAssignableFrom(metadata.ModelType))
  373. {
  374. var validationResult = (metadata.Model as IValidatableObject).Validate(new ValidationContext(metadata.Model));
  375. if (validationResult != null)
  376. {
  377. var modelValidationResults = new List<ModelValidationResult>();
  378. foreach (var result in validationResult)
  379. {
  380. modelValidationResults.Add(new ModelValidationResult
  381. {
  382. MemberName = string.Join(",", result.MemberNames),
  383. Message = result.ErrorMessage
  384. });
  385. }
  386. return modelValidationResults;
  387. }
  388. return null;
  389. }
  390. return GetModelValidator(ValidatorProviders).Validate(metadata, container);
  391. }
  392. }
  393. }