网站注册要多少钱,wordpress 文章长度,运营一款app的费用,娄底网站建设最专业前言
前后端分离#xff0c;或许是现如今最为流行开发方式#xff0c;包括UWP、Android和IOS这样的手机客户端都是需要调用后台的API来进行数据的交互。
但是这样对前端开发和APP开发就会面临这样一个问题#xff1a;如何知道每个API做什么#xff1f;
可能#xff0c;…前言
前后端分离或许是现如今最为流行开发方式包括UWP、Android和IOS这样的手机客户端都是需要调用后台的API来进行数据的交互。
但是这样对前端开发和APP开发就会面临这样一个问题如何知道每个API做什么
可能有人会在内部形成一份word文档、pdf有人会建立一个单独的站点然后将API的地址参数等信息列在上面有人会借助第三方的工具来生成一份文档等。
当然这基本是取决于不同公司的规范。
说起API文档就想到前段时间做的微信小程序由于那个不完善的接口文档从而导致浪费了很大一部分时间去询问接口相关的内容(用的是老的接口)。
为了处理这个问题我认为如果能在写某个API的时候就顺带将这个API的相关信息一并处理了是最好不过
不过这并不是让我们写好一个接口后再去打开word等工具去编辑一下这个API的信息这样明显需要花费更多的时间。
下面就针对这一问题探讨一下在Nancy中的实现。
如何实现
其实想在Nancy中生成API文档是一件十分容易的事因为作者thecodejunkie已经帮我们在Nancy内部提前做了一些处理
便于我们的后续扩展这点还是很贴心的。
下面我们先来写点东西后面才能写相应的API文档。
public class ProductsModule : NancyModule{ public ProductsModule() : base(/products) { Get(/, _ { return Response.AsText(product list);}, null, GetProductList); Get(/{productid}, _ { return Response.AsText(_.productid as string);}, null, GetProductByProductId); Post(/, _ { return Response.AsText(Add product);}, null, AddProduct); //省略部分..}
}
基本的CURD没有太多的必要去解释这些内容。当然这里需要指出一点。
正常情况下我们基本都是只写前面两个参数的后面两个参数是可选的。由于我们后面需要用到每个路由的名字
所以我们需要用到这里的第4个参数(当前路由的名字)也就意味着我们要在定义的时候写多一点东西 注: 1.x和2.x的写法是有区别的示例用的2.x的写法所以各位要注意这点 以GET为例方法定义大致如下 API写好了下面我们先来简单获取一下这些api的相关信息
最简单的实现
前面也提到我们是要把这个api和api文档放到同一个站点下面免去编辑这一步骤
世间万物都是相辅相成的我们不想单独编辑自然就要在代码里面多做一些处理
新起一个Module名为DocModule将api文档的相关内容放到这个module中来处理。
public class DocMudule : NancyModule{ private IRouteCacheProvider _routeCacheProvider; public DocMudule(IRouteCacheProvider routeCacheProvider) : base(/docs) { this._routeCacheProvider routeCacheProvider; Get(/, _ { var routeDescriptionList _routeCacheProvider.GetCache().SelectMany(x x.Value).Select(x x.Item2).Where(x !string.IsNullOrWhiteSpace(x.Name)).ToList(); return Response.AsJson(routeDescriptionList);});}
}
没错你没看错就是这几行代码就可以帮助我们去生成我们想要的api文档其实最主要的是IRouteCacheProvider这个接口。
它的具体实现会在后面的小节讲到现在先着重于使用!
先调用这个接口的GetCache方法以拿到缓存的路由信息这个路由信息有必要来看一下它的定义因为不看它的定义我们根本就没有办法继续下去
后续的查找都是依赖于这些缓存信息
public interface IRouteCache : IDictionaryType, ListTupleint, RouteDescription, ICollectionKeyValuePairType, ListTupleint, RouteDescription, IEnumerableKeyValuePairType, ListTupleint, RouteDescription, IEnumerable{ bool IsEmpty();
}
看了上面的定义就可以清楚的知道要用SelectMany去拿到那个元组的内容。再取出元组的RouteDescription。
当然这个时候我们取到的是所有的路由信息这些信息都包含了什么内容呢看看RouteDescription的定义就很清晰了。
public sealed class RouteDescription{ public RouteDescription(string name, string method, string path, FuncNancyContext, bool condition); //The name of the routepublic string Name { get; set; } //The condition that has to be fulfilled inorder for the route to be a valid match.public FuncNancyContext, bool Condition { get; } //The description of what the route is for.public string Description { get; set; } //Gets or sets the metadata information for a route.public RouteMetadata Metadata { get; set; } //Gets the method of the route.public string Method { get; } //Gets the path that the route will be invoked for.public string Path { get; } //Gets or set the segments, for the route, that was returned by the Nancy.Routing.IRouteSegmentExtractor.public IEnumerablestring Segments { get; set; }
}
在查询之后我还过滤了那些名字为空的不让它们显示出来。为什么不显示出来呢理由也比较简单像DocModule我们只定义了一个路由
而且这个路由在严格意义上并不属于我们api的内容而且这个路由也是没有定义名字的所以显示出来的意义也不大。
过滤之后就得到了最终想要的信息简单起见这里是先直接 返回一个json对象便于查看有什么内容便于在逐步完善后再把它结构化。
下面是最简单实现后的大致效果 在图中可以看到GetProductList和GetProductByProductId这两个api的基本信息请求的method请求的路径和路由片段。
但是这些信息真的是太少了连api描述都见不到拿出来肯定被人狠狠的骂一顿
下面我们要尝试丰富一下我们的接口信息
丰富一点的实现
要让文档充实总是需要一个切入点找到切入点事情就好办了。仔细观察上面的效果图会发现里面的metadata是空的。当然这个也就是丰富文档内容的切入点了。
从前面的定义可以看到这个metadata是一个RouteMetadata的实例
public class RouteMetadata{ //Creates a new instance of the Nancy.Routing.RouteMetadata class.public RouteMetadata(IDictionaryType, object metadata); //Gets the raw metadata System.Collections.Generic.IDictionary2.public IDictionaryType, object Raw { get; } //Gets a boolean that indicates if the specific type of metadata is stored.public bool HasTMetadata(); //Retrieves metadata of the provided type.public TMetadata RetrieveTMetadata();
}
这里对我们比较重要的是Raw这个属性因为这个是在返回结果中的一部分它是一个字典键是类型值是这个类型对应的实例。
先定义一个CustomRouteMetadata用于返回路由的Metadata信息(可根据具体情况进行相应的定义)。这个CustomRouteMetadata就是上述字典Type。
public class CustomRouteMetadata{ // group by the modulepublic string Group { get; set; } // description of the apipublic string Description { get; set; } // path of the apipublic string Path { get; set; } // http method of the apipublic string Method { get; set; } // name of the apipublic string Name { get; set; } // segments of the apipublic IEnumerablestring Segments { get; set; }
}
定义好我们要显示的东西后自然要把这些东西用起来才能体现它们的价值。
要用起来还涉及到一个MetadataModule这个命名很像NancyModule看上去都是一个Module。
先定义一个ProductsMetadataModule让它继承MetadataModuleRouteMetadata 具体实现如下
public class ProductsMetadataModule : MetadataModuleRouteMetadata
{ public ProductsMetadataModule() { Describe[GetProductList] desc { var dic new DictionarySystem.Type, object{{ typeof(CustomRouteMetadata), new CustomRouteMetadata{Group Products,Description Get All Products from Database,Path desc.Path,Method desc.Method,Name desc.Name,Segments desc.Segments}}}; return new RouteMetadata(dic);};Describe[GetProductByProductId] desc { var dic new DictionarySystem.Type, object{{ typeof(CustomRouteMetadata), new CustomRouteMetadata{Group Products,Description Get a Product by product id,Path desc.Path,Method desc.Method,Name desc.Name,Segments desc.Segments}}}; return new RouteMetadata(dic); }; //省略部分...}
}
这里的写法就和1.x里写NancyModule的内容是一样的应该也是比较熟悉的。就不再累赘了。其中的desc是一个委托FuncRouteDescription, TMetadata。
默认返回的是一个RouteMetadata实例而要创建一个这样的实例还需要一个字典所以大家能看到上面的代码中定义了一个字典。
并且这个字典包含了我们自己定义的信息其中Group和Description是完全的自定义其他的是从RouteDescription中拿。
当然这里已经开了一个口子想怎么定义都是可以的
完成上面的代码之后再来看看我们显示的结果 可以看到我们添加的metadata相关的内容已经出来了可能这个时候大家也都发现了似乎内容有那么点重复的意思
因为这些重复就会让人感觉这里比较臃肿所以我们肯定不需要取出太多重复的东西目前只需要metadata下面的这些就可以了。
下面来对其进行简化
简化一点的实现
简化分为两步
第一步简化DocModule的简化。
其实DocModule已经是相当的简单了但是还能在简洁一点点。这里用到了RetrieveMetadata这个扩展方法来处理。
前面的做法是拿到路由的信息后用了两个Select来查询而且查询出来的结果有那么一点臃肿
而借助扩展方法可以只取metadata里面的内容也就是前面自定义的内容这才是我们真正意义上要用到的。
下面是具体实现的示例
Get(/, _ {//01//var routeDescriptionList _routeCacheProvider// .GetCache()// .SelectMany(x x.Value)// .Select(x x.Item2)// .Where(x !string.IsNullOrWhiteSpace(x.Name))// .ToList();//return Response.AsJson(routeDescriptionList);//02var routeDescriptionList _routeCacheProvider.GetCache().RetrieveMetadataRouteMetadata().Where(x x ! null);return Response.AsJson(routeDescriptionList);});
经过第一步简化后已经过滤了不少重复的信息了效果如下 第二步简化Metadata的简化
在返回Metadata的时候我们是返回了一个默认的RouteMetadata对象这个对象相比自定义的CustomRouteMetadata复杂了不少
而且从上面经过第一步简化后的效果图也可以发现只有value节点下面的内容才是api文档需要的内容。
所以还要考虑用自定义的这个CustomRouteMetadata去代替原来的。
修改如下
public class ProductsMetadataModule : MetadataModuleCustomRouteMetadata
{ public ProductsMetadataModule() {Describe[GetProductList] desc { return new CustomRouteMetadata{Group Products,Description Get All Products from Database,Path desc.Path,Method desc.Method,Name desc.Name,Segments desc.Segments};};Describe[GetProductByProductId] desc { return new CustomRouteMetadata{Group Products,Description Get a Product by product id,Path desc.Path,Method desc.Method,Name desc.Name,Segments desc.Segments};}; //省略部分..}
}
由于MetadataModuleTMetadata 中的TMetadata是自定义的CustomRouteMetadata所以在返回的时候直接创建一个简单的实例即可
不需要像RouteMetadata那样还要定义一个字典。
同时还要把DocModule中RetrieveMetadata的TMetadata也要替换成CustomRouteMetadata var routeDescriptionList _routeCacheProvider.GetCache() //.RetrieveMetadataRouteMetadata() .RetrieveMetadataCustomRouteMetadata().Where(x x ! null);
经过这两步的简化现在得到的效果就是我们需要的结果了 最后当然要专业一点不能让人只看json吧怎么都要添加一个html页面将这些信息展示出来 当然现在看上去还是很丑文档内容也并不丰富但是已经把最简单的文档做出来了想要进一步丰富它就可以自由发挥了。
实现探讨
既然这样简单的代码就能帮助我们去生成api文档很有必要去研究一下Nancy帮我们做了什么事
从最开始的IRouteCacheProvider入手这个接口对应的默认实现DefaultRouteCacheProvider
public class DefaultRouteCacheProvider : IRouteCacheProvider, IDiagnosticsProvider{ /// summary/// The route cache factory/// /summaryprotected readonly FuncIRouteCache RouteCacheFactory; /// summary/// Initializes a new instance of the DefaultRouteCacheProvider class./// /summary/// param namerouteCacheFactory/parampublic DefaultRouteCacheProvider(FuncIRouteCache routeCacheFactory) { this.RouteCacheFactory routeCacheFactory;} /// summary/// Gets an instance of the route cache./// /summary/// returnsAn see crefIRouteCache/ instance./returnspublic IRouteCache GetCache() { return this.RouteCacheFactory();} //省略部分..}
里面的GetCache方法是直接调用了定义的委托变量。最终是到了IRouteCache的实现类RouteCache这个类算是一个重点观察对象
内容有点多就只贴出部分核心代码了
它在构造函数里去生成了路由的相关信息。
public RouteCache( INancyModuleCatalog moduleCatalog, INancyContextFactory contextFactory, IRouteSegmentExtractor routeSegmentExtractor, IRouteDescriptionProvider routeDescriptionProvider, ICultureService cultureService, IEnumerableIRouteMetadataProvider routeMetadataProviders){ this.routeSegmentExtractor routeSegmentExtractor; this.routeDescriptionProvider routeDescriptionProvider; this.routeMetadataProviders routeMetadataProviders; var request new Request(GET, /, http); using (var context contextFactory.Create(request)){ this.BuildCache(moduleCatalog.GetAllModules(context));}
}
具体的生成方法如下遍历所有的NancyModule找到每个Module的RouteDescription集合(一个Module可以包含多个路由)
然后找到每个RouteDescription的描述路由片段和metadata的信息。最后把这个Module路由信息添加到当前的对象中
private void BuildCache(IEnumerableINancyModule modules){ foreach (var module in modules){ var moduleType module.GetType(); var routes module.Routes.Select(r r.Description).ToArray(); foreach (var routeDescription in routes){routeDescription.Description this.routeDescriptionProvider.GetDescription(module, routeDescription.Path);routeDescription.Segments this.routeSegmentExtractor.Extract(routeDescription.Path).ToArray();routeDescription.Metadata this.GetRouteMetadata(module, routeDescription);} this.AddRoutesToCache(routes, moduleType);}
}
前面提到RouteDescription的描述路由片段和metadata的信息都是通过额外的方式拿到的这里主要是拿metadata来做说明
毕竟在上面最后的一个例子中用到的是metadata的内容。
先调用定义的私有方法GetRouteMetadata这个方法里面的内容是不是和前面的MetadataModule有点类似呢字典和创建RouteMetadata的实例。
private RouteMetadata GetRouteMetadata(INancyModule module, RouteDescription routeDescription){ var data new DictionaryType, object(); foreach (var provider in this.routeMetadataProviders){ var type provider.GetMetadataType(module, routeDescription); var metadata provider.GetMetadata(module, routeDescription); if (type ! null metadata ! null){data.Add(type, metadata);}} return new RouteMetadata(data);
}
重点的是provider。这个provider来源来IRouteMetadataProvider这个接口就两个方法。
Nancy这个项目中还有一个抽象类是继承了这个接口的。但是这个抽象类是没有默认实现的。
public abstract class RouteMetadataProviderTMetadata : IRouteMetadataProvider{ public Type GetMetadataType(INancyModule module, RouteDescription routeDescription) { return typeof(TMetadata); } public object GetMetadata(INancyModule module, RouteDescription routeDescription) { return this.GetRouteMetadata(module, routeDescription);} protected abstract TMetadata GetRouteMetadata(INancyModule module, RouteDescription routeDescription);
} 注前面的原理分析都是基于Nancy这个项目。 这个时候另外一个项目Nancy.Metadata.Modules就起作用了。我们编写的MetadataModule也是要添加这个的引用才能正常使用的。
从上面编写的MetadataModule可以看出这个项目的起点应该是MetadataModule而且有关metadata的核心也在这里了。
public abstract class MetadataModuleTMetadata : IMetadataModule where TMetadata : class{ private readonly IDictionarystring, FuncRouteDescription, TMetadata metadata; protected MetadataModule() { this.metadata new Dictionarystring, FuncRouteDescription, TMetadata();} // Gets see crefRouteMetadataBuilder/ for describing routes.public RouteMetadataBuilder Describe{ get { return new RouteMetadataBuilder(this); }} // Returns metadata for the given RouteDescription.public object GetMetadata(RouteDescription description) { if (this.metadata.ContainsKey(description.Name)){ return this.metadata[description.Name].Invoke(description);} return null;} // Helper class for configuring a route metadata handler in a module.public class RouteMetadataBuilder{ private readonly MetadataModuleTMetadata parentModule; public RouteMetadataBuilder(MetadataModuleTMetadata metadataModule) { this.parentModule metadataModule;} // Describes metadata for a route with the specified name.public FuncRouteDescription, TMetadata this[string name]{ set { this.AddRouteMetadata(name, value); }} protected void AddRouteMetadata(string name, FuncRouteDescription, TMetadata value) { this.parentModule.metadata.Add(name, value);}} //省略部分..}
到这里已经将GetCache的内内外外都简单分析了一下。至于扩展方法RetrieveMetadata就不在细说了只是selectmany和select的一层封装。
写在最后
本文粗略讲解了如何在Nancy中生成API文档以及简单分析了其内部的处理。 下一篇将继续介绍这一块的内容不过主角是Swagger。
原文地址http://www.cnblogs.com/catcher1994/p/6791352.html .NET社区新闻深度好文微信中搜索dotNET跨平台或扫描二维码关注