Introduce
In this article, we will learn how to generate the API document in Nancy.
Background
Nowadays, separation of front-end and back-end is the most popular development mode. Not only do websites use this mode but the apps also. Because of the separation, communication may be a big problem when we begin to develop.
As for me, the back-end developers can spend some time to write a document of the APIs that can help the front-end developers to understand what the API is supposed to do.
Now, let's begin to write a API document in Nancy step by step.
Step 1
Before we write the document, we must have some APIs.
Here, we are creating a new module class. This class contains the basic operations of CRUD.
Look at the following class definition.
- 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");
-
- Put("/{productid}", _ =>
- {
- return Response.AsText("Update product");
- }, null, "UpdateProductByProductId");
-
- Delete("/{productid}", _ =>
- {
- return Response.AsText("Delete product");
- }, null, "DeleteProductByProductId");
- }
- }
Note
You may find some difference on the usage. We often use only two parameters on GET/POST/PUT/DELETE, but you will find four parameters above. This is because we need the fourth parameter, name of the route, when we generate the document.
Step 2
We already have some APIs. Now, we can generate the document.
Let's now create a new module class named DocModule; we will generate the document in this module.
Here is an example code:
- 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);
- });
- }
- }
It's very easy! The interface IRouteCacheProvider helps us to do the job.
Note
I have filtered some routes where the name of route is either null or empty, such as the routes in DocModule class. This happens because the routes in DocModule are not part of our APIs.
For demonstration, I let the code return a JSON object at first. We need to know the result of the expression which can help us to complete the document.
When running the code, here is the screenhost of the result page.
But, there is not too much infomation of the APIs. We need to do more things so that we can make the document richer.
Step 3
We can find out that the metadata is empty in the above result. But actually, metadata is a way of making our document richer.
The Metadata is an instance of the RouteMetadata, and here is the definition of it.
- public class RouteMetadata
- {
-
- public RouteMetadata(IDictionary<Type, object> metadata);
-
-
- public IDictionary<Type, object> Raw { get; }
-
-
- public bool Has<TMetadata>();
-
-
- public TMetadata Retrieve<TMetadata>();
- }
As you can see, the raw property is the part of the above JSON result. It means, we should create a new type/class to get what we need.
Define a custom Metadata class at first. This class contains some additional infomation about the route, such as the group. I use the group to distinct the different APIs.
- public class CustomRouteMetadata
- {
-
- public string Group { get; set; }
-
-
- public string Description { get; set; }
-
-
- public string Path { get; set; }
-
-
- public string Method { get; set; }
-
-
- public string Name { get; set; }
-
-
- public IEnumerable<string> Segments { get; set; }
- }
And, we need to install the package Nancy.Metadata.Modules in our project so that we can define what we need to show in the document.
Creating a new MetadataModule class named ProductsMetadataModule
- public class ProductsMetadataModule : MetadataModule<CustomRouteMetadata>
- {
- 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
- };
- };
-
-
- }
- }
After adding the description of the APIs , here is the result.
It makes the document richer but it also makes some infomation duplicated! We need to make it easier because what we need is the content under the node value.
Step 4
The interface ICacheRoute has an extension method named RetrieveMetadata which can help us to simplify the result of the document.
Go back to the DocModule, and edit the code as follows.
- var routeDescriptionList = _routeCacheProvider
- .GetCache()
- .RetrieveMetadata<CustomRouteMetadata>()
- .Where(x => x != null);
Now, the result is what we need!
As a document, we will not show only a JSON object to others. We should add an HTML page to make it easy to understand.
Step 5
Add an HTML page named doc.html to our project. Here is the content of the page.
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="utf-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
- <title>Nancy Api Demo</title>
- <link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css" />
- </head>
- <body>
- <div class="container body-content">
- @Each
- <div class="col-md-6 ">
- <div class="panel panel-default">
- <div class="panel-body">
- <p>Group:<strong>@Current.Group</strong></p>
- <p>Name:<strong>@Current.Name</strong></p>
- <p>Method:<strong>@Current.Method</strong></p>
- <p>Path:<strong>@Current.Path</strong></p>
- <p>Description:<strong>@Current.Description</strong></p>
- </div>
- </div>
- </div>
- @EndEach
- <hr />
- <footer>
- <p>© 2017 - CatcherWong</p>
- </footer>
- </div>
-
- <script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.2.0.min.js"></script>
- <script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/bootstrap.min.js"></script>
- </body>
- </html>
Running the project, you may get the result as follows.
What's next?
Swagger is a vary popular API Framework! Next time, I will introduce how to use swagger in Nancy by using the open source project Nancy.Swagger.
Thanks for your patient reading!!