asp.net web api 构建api帮助文档

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路由。

 -----------------------------------------------------------------------------------------

时间仓促,水平有限,如有不当之处,欢迎指正。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏大内老A

我的WCF之旅(7):面向服务架构(SOA)和面向对象编程(OOP)的结合——如何实现Service Contract的继承

当今的IT领域,SOA已经成为了一个非常时髦的词,对SOA风靡的程度已经让很多人对SOA,对面向服务产生误解。其中很大一部分人甚至认为面向服务将是面向对象的终结...

1865
来自专栏Golang语言社区

Go语言test之类方法测试

Go语言提供了完善的单元测试支持,开发人员可以方便的编写测试代码,保证自己代码的质量。在目前的例子中,一般看到都是普通函数的例子。下面我将举类方法的测试例子来展...

36610
来自专栏DOTNET

asp.net web api 版本控制

版本控制 版本控制的方法有很多,这里提供一种将Odata与普通web api版本控制机制统一的方法,但也可以单独控制,整合控制与单独控制主要的不同是:整合控制通...

5406
来自专栏GreenLeaves

C#核编之System.Environment类

      在前面的例子中用来了Environment.GetCommandLineArgs()这个方法,这个方法就是获取用户的命令行输入,是Environme...

2267
来自专栏me的随笔

模板方法模式实践

在实际编程中,会经常遇到多个类中的某些方法实现逻辑类似的情况,这时我们可以将这些类中的相同部分抽象到父类中,对于有差异的地方,子类根据自身的实际需求来各自实现。

962
来自专栏函数式编程语言及工具

Akka(36): Http:Client-side-Api,Client-Connections

   Akka-http的客户端Api应该是以HttpRequest操作为主轴的网上消息交换模式编程工具。我们知道:Akka-http是搭建在Akka-stre...

2419
来自专栏跟着阿笨一起玩NET

如何让DevExpress TreeList的每个结点高亮显示?

原文地址:http://www.devexpresscn.com/devResources/page-18-78.html

1462
来自专栏大内老A

Enterprise Library Policy Injection Application Block 之三:PIAB的扩展—创建自定义CallHandler(提供Source Code下载)

本系列的第一部分对PIAB使用场景进行了简单的介绍,作中阐述了通过PI(Policy Injection)的方式实现了Business Logic和Non-Bu...

33410
来自专栏偏前端工程师的驿站

网页优化系列三:使用压缩后置viewstate

  Asp.net中的服务器控件都启用了viewstate,虽然方便了开发人员,但页面大小及性能上确实有所影响,对于无需viewstate的控件及页面可以直接把...

2095
来自专栏草根专栏

使用两种方法让 ASP.NET Core 实现遵循 HATEOAS 结构的 RESTful API

HATEOAS(Hypermedia as the engine of application state)是 REST 架构风格中最复杂的约束,也是构建成熟 ...

53511

扫码关注云+社区