1 概要
创建ASP.NET Web Api 时模板自带Help Pages框架。
2 问题
1)使用VS创建Web Api项目时,模板将Help Pages框架自动集成到其中,使得Web Api项目引入了MVC框架开发包,使得项目看起来杂乱。
2)自带的Help Pages框架无法针对Odata控制器生成API文档。
3 问题解决方案
1)独立Help Pages项目,以插件形式添加服务
步骤1,添加类ServiceAssembliesResolver,获得服务集
/// <summary> /// 获取插件服务 /// </summary> public class ServiceAssembliesResolver : DefaultAssembliesResolver { public override ICollection<Assembly> GetAssemblies() { //获得已有的服务 ICollection<Assembly> baseAssemblies = base.GetAssemblies(); //初始化 List<Assembly> assemblies = new List<Assembly>(baseAssemblies); //服务插件dll路径 var path = WebConfigSetting.ServicesLocation; //加载每一个服务插件 foreach (string file in Directory.GetFiles(path, "*.dll")) { var controllersAssembly = Assembly.LoadFrom(file); assemblies.Add(controllersAssembly); } return assemblies; } }
步骤2,替换现有服务
在WebApiConfig.Register方法中添加代码
config.Services.Replace(typeof(IAssembliesResolver), new ServiceAssembliesResolver());
完整代码如下:
namespace HY_WebApi.HelpPages { public static class WebApiConfig { public static void Register(HttpConfiguration config) { // Web API 配置和服务 config.Services.Replace(typeof(IAssembliesResolver), new ServiceAssembliesResolver()); // Web API 路由 config.MapHttpAttributeRoutes(); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{action}/{id}", defaults: new { id = RouteParameter.Optional } ); //OData路由,将路由名称设置为控制器(去掉Controller)名称,以便生成Api帮助文档 config.MapODataServiceRoute( routeName: "ODataSearch", routePrefix: "odata", model: GetEdmModel(), batchHandler: new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer)); } private static IEdmModel GetEdmModel() { ODataConventionModelBuilder builder = new ODataConventionModelBuilder(); builder.EntitySet<Institution>("ODataSearch"); builder.Namespace = "Search"; //builder.EntityType<Institution>().Collection.Function("GetByIdEq2").Returns<string>(); return builder.GetEdmModel(); } public class Institution { public int Id { get; set; } public string Name { get; set; } public string Address { get; set; } } } }
步骤3,添加MultiXmlDocumentationProvider类,读取多个XML文档
/// <summary> /// 加载目录下的所有Xml文档 /// </summary> public class MultiXmlDocumentationProvider : IDocumentationProvider, IModelDocumentationProvider { private IList<XmlDocumentationProvider> _documentationProviders; public MultiXmlDocumentationProvider(string xmlDocFilesPath) { _documentationProviders = new List<XmlDocumentationProvider>(); foreach (string file in Directory.GetFiles(xmlDocFilesPath, "*.xml")) { _documentationProviders.Add(new XmlDocumentationProvider(file)); } } public string GetDocumentation(HttpParameterDescriptor parameterDescriptor) { return _documentationProviders.Select(x => x.GetDocumentation(parameterDescriptor)).FirstOrDefault(x => !string.IsNullOrEmpty(x)); } public string GetDocumentation(Type type) { return _documentationProviders.Select(x => x.GetDocumentation(type)).FirstOrDefault(x => !string.IsNullOrEmpty(x)); } //成员导航 public string GetDocumentation(MemberInfo member) { return _documentationProviders .Select(x => x.GetDocumentation(member)) .FirstOrDefault(x => !string.IsNullOrWhiteSpace(x)); } //action 描述 public string GetDocumentation(HttpActionDescriptor actionDescriptor) { return _documentationProviders.Select(x => x.GetDocumentation(actionDescriptor)).FirstOrDefault(x => !string.IsNullOrEmpty(x)); } //Controller 描述 public string GetDocumentation(HttpControllerDescriptor controllerDescriptor) { return _documentationProviders.Select(x => x.GetDocumentation(controllerDescriptor)).FirstOrDefault(x => !string.IsNullOrEmpty(x)); } public string GetResponseDocumentation(HttpActionDescriptor actionDescriptor) { return _documentationProviders.Select(x => x.GetDocumentation(actionDescriptor)).FirstOrDefault(x => !string.IsNullOrEmpty(x)); } }
步骤4,使用MultiXmlDocumentationProvider
将config.SetDocumentationProvider(new MultiXmlDocumentationProvider(WebConfigSetting.ServicesLocation));添加到Register方法中
步骤5,创建服务插件文件夹,将服务插件及其XML文档放在文件夹中。
2)重构ApiExplorer,获得Odata控制器的API文档
步骤1,重构ApiExplorer
public class CustomApiExplorer : ApiExplorer { private HttpConfiguration configuration; public CustomApiExplorer(HttpConfiguration configuration) : base(configuration) { this.configuration = configuration; } public override bool ShouldExploreController(string controllerVariableValue, HttpControllerDescriptor controllerDescriptor, IHttpRoute route) { if (controllerDescriptor == null) { throw new ArgumentNullException("controllerDescriptor"); } if (route == null) { throw new ArgumentNullException("route"); } var c = controllerDescriptor.ControllerName; //获得OData路由 IEdmModel edm = EdmModelCreater.GetEdmModel(); List<string> collectionFromEdms = new List<string>(); foreach (var item in edm.EntityContainer.Elements) { collectionFromEdms.Add(item.Name); } //如果是Odata控制器,那么忽略ApiExplorerSettingsAttribute ApiExplorerSettingsAttribute setting = controllerDescriptor.GetCustomAttributes<ApiExplorerSettingsAttribute>().FirstOrDefault(); bool isOdataController = collectionFromEdms.Contains(controllerDescriptor.ControllerName); bool isBaseApi = controllerDescriptor.ControllerName != "BaseApi"; return isBaseApi||isOdataController || ((setting == null || !setting.IgnoreApi) && MatchRegexConstraint(route, RouteValueKeys.Controller, controllerVariableValue)); } public override bool ShouldExploreAction(string actionVariableValue, HttpActionDescriptor actionDescriptor, IHttpRoute route) { if (actionDescriptor == null) { throw new ArgumentNullException("actionDescriptor"); } if (route == null) { throw new ArgumentNullException("route"); } //获得OData路由 IEdmModel edm = EdmModelCreater.GetEdmModel(); List<string> collectionFromEdms = new List<string>(); foreach (var item in edm.EntityContainer.Elements) { collectionFromEdms.Add(item.Name); } //如果是Odata控制器,那么忽略ApiExplorerSettingsAttribute ApiExplorerSettingsAttribute setting = actionDescriptor.ControllerDescriptor.GetCustomAttributes<ApiExplorerSettingsAttribute>().FirstOrDefault(); bool isOdataController = collectionFromEdms.Contains(actionDescriptor.ControllerDescriptor.ControllerName); bool isBaseApi = actionDescriptor.ControllerDescriptor.ControllerName != "BaseApi"; return isBaseApi||isOdataController || ((setting == null || !setting.IgnoreApi) && MatchRegexConstraint(route, RouteValueKeys.Action, actionVariableValue)); } private static bool MatchRegexConstraint(IHttpRoute route, string parameterName, string parameterValue) { IDictionary<string, object> constraints = route.Constraints; if (constraints != null) { object constraint; if (constraints.TryGetValue(parameterName, out constraint)) { // treat the constraint as a string which represents a Regex. // note that we don‘t support custom constraint (IHttpRouteConstraint) because it might rely on the request and some runtime states string constraintsRule = constraint as string; if (constraintsRule != null) { string constraintsRegEx = "^(" + constraintsRule + ")$"; return parameterValue != null && Regex.IsMatch(parameterValue, constraintsRegEx, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); } } } return true; } }
添加RouteValueKeys类
internal static class RouteValueKeys { // Used to provide the action and controller name public const string Action = "action"; public const string Controller = "controller"; }
添加OdataRelativePath类
public static class OdataRelativePath { public static void GetOdataRelativePath(CustomApiExplorer customApiExplorer, HttpConfiguration configuration) { IEdmModel edm = EdmModelCreater.GetEdmModel(); List<string> collectionFromEdms = new List<string>(); foreach(var item in edm.EntityContainer.Elements) { collectionFromEdms.Add(item.Name); } Collection<ApiDescription> apiColloction = customApiExplorer.ApiDescriptions; foreach (ApiDescription api in apiColloction) { string controllerName = api.ActionDescriptor.ControllerDescriptor.ControllerName; //去掉Odata中控制器的版本号 var controllerSelector = configuration.Services.GetService(typeof(IHttpControllerSelector)) as VersionControllerSelector; string oldString = controllerSelector.RouteVersionSuffixMapping.First(m => m.Key.Contains("OdataRouteVersioning")).Value; controllerName = controllerName.Replace(oldString, ""); if (collectionFromEdms.Contains(controllerName)) { string actionName = api.ActionDescriptor.ActionName; var parameters = api.ActionDescriptor.GetParameters(); string paramStr = null; foreach (var parameter in parameters) { var t = parameter.ParameterType; if (parameter.ParameterType.IsClass) { continue; } if (paramStr != null) { paramStr = string.Format("{0}&({1}={1})", paramStr, parameter.ParameterName); } else { paramStr = string.Format("({0}={0})", parameter.ParameterName); } } api.RelativePath = string.Format("{0}/{1}/{2}/Service.{3}{4}", "odata", "{Version}", controllerName, actionName, paramStr); } else { Regex reg=new Regex("[0-9]"); Match match = reg.Match(api.RelativePath); if(match.Success) { api.RelativePath = api.RelativePath.Replace(string.Format("V{0}",match.Value),""); } } } } }
步骤2;根据OData路由拼出api的URI
使用OdataRelativePath.GetOdataRelativePath方法修改ApiExplorer.ApiDescriptions中的URI
例如在控制器中
public ActionResult Index() { ViewBag.DocumentationProvider = Configuration.Services.GetDocumentationProvider(); CustomApiExplorer customApiExplorer = new CustomApiExplorer(Configuration); OdataRelativePath.GetOdataRelativePath(customApiExplorer,Configuration); Collection<ApiDescription> apiDescriptions = new Collection<ApiDescription>(); List<ApiDescription> list = new List<ApiDescription>(); foreach (ApiDescription ad in customApiExplorer.ApiDescriptions) { if (ad.ActionDescriptor.ControllerDescriptor.ControllerName != "Metadata" && ad.ActionDescriptor.ActionName != "ToJson") { list.Add(ad); } } list = list.OrderBy(m => m.ActionDescriptor.ControllerDescriptor.ControllerName).ToList(); list.ForEach(m => { apiDescriptions.Add(m); }); return View(apiDescriptions); }
注意:配置Odata路由时,将路由名称配置为控制器名称(不含Controller字符串),并且编写服务程序时,遵循一个实体对应一个控制器,对应一个Odata路由。
-----------------------------------------------------------------------------------------
转载与引用请注明出处。
时间仓促,水平有限,如有不当之处,欢迎指正。