Every once in a while you will be in a position to solve the challenge of writing and maintaining a professional technical documentation for your product REST API. Building an API is easy, right? Technically speaking it is. The biggest challenge comes when someone has to use your API for the first time. It is then, when you figure out that no-one except for the development team understands what the hack is going on behind the scenes. To avoid this potential pitfall, I would like to share our personal experience with our product’s API for sending invoices – Envoice. 1. Planning Businesses today have complex business processes which expands over different software systems. Today most used software systems are not the ones that are offering plenty of functionalities, but rather the ones that offer API and out of the box integrations with other popular services. Having said that, if you are planning your next venture or already developing a product you will need to spend some time to get the things sorted and allow your customers to integrate your service as part of their eco-system. There are several aspects you need to consider when preparing for API development including:
2. Writing an API endpoint Since you are reading this post I would assume that you’ve already created a Web API 2.0 Controller as in the example bellow: We have an endpoint that describes the process associated with the “Client” domain entities. We’ve decided that we would like to enable the developers to programmatically:
2.1 What kind of actions you are going to allow through the API? First things first, we need to decide what type of data we are going to process and return back to the consumer. Ex: Mapping client details data call Client API: In our example we have a custom DTO that we are going to use as a response when retrieving details about the client. 2.2 Securing the endpoint Besides setting up a RequireSsl attribute, we need to make sure that we can identify the caller when accessing the API. Let’s say we need to secure the process of creating a new client. A simple yet effective approach would be to use “API Auth Key” and “API Auth Secret”. With RNGCryptoServiceProvider we can create an Auth Key and Secret and associate them with the user’s account. Make sure they can be regenerated. Note: Recommended key length would be 128 for both Auth Key and Secret. To be able to authenticate the call, implement Application_AuthenticationRequest method in Global.asax.cs. All request that require authentication should contain an Auth Key and Secret headers. If the key and secret are valid, we will allow the request to be executed. This verification can be achieved with a custom authorize attribute. 3. Tools to document and maintain the API documentation At the moment there is a huge support and community activity around Swagger – framework of API development tools for the Open API Specification. In our favour, it is supported in the .NET eco system via the NuGet package: Swashbuckle. Installing this package in your Web application, will add support for Swagger 2.0. After the installation you will find a newly generated Swagger configuration file under App_Start\SwaggerConfig.cs. The configuring is extensive and allows you to go into details on generating the open specification for you API. Once done, you can check the results on your predefined API documentation route. The generated API specification will contain all of the exposed Web API methods together with the associated comments and DTOs. The associated comments are going to be read from the XML comments from the output directory. Usually that is Project.Namespace.XML. 3.1 Configuration extension Since the requirements may vary, you might end up needing a functionality which is not supported out of the box. Great thing is that you will be able to write extensions and override the process of creating the API specification. In case you need to extend, you can write the following types of extensions: – Operation filters – override the specification on an operation level. Ex: Create a new client action; – Schema filters – override the specification schema. Ex: exclude a property from the request/response; – Document filters – override the document. Highest in hierarchy. Ex: Set all operations to lowercase; There are many useful examples that can be found on Swashbuckle for Document, Schema and Operation filters. Make sure you check them before rolling out on your own. 3.2 Generating a usable UI/UX for your API specification I understand that developers are not always happy when it comes to creating a usable UI/UX, but it is a prerequisite to have good API documentation. Swashbuckle offers an UI out of the box, but it is poorly designed and renders raw information from the API specification. There is one library that comes to the rescue: REDOC. Redoc lets you generate a modern UI by simply providing the library with the absolute URL to the API specification. You should get the following result after refresh: 3.3 When documentation is not enough Often after releasing the API, you will face the hard truth, that the developers who are integrating your API with some existing product out there will need help to understand what your API does and how they can use it in their programming language. This can decrease your team productivity time, as you will spend countless hours into explaining how to use the API in .NET, PHP, Go, Ruby, Python. In order to cut down the time spent on support, there are some activities that you can do beforehand: – Creating a Postman collection; – Creating cURL examples on GitHub; – Embedding the Postman collection and GitHub examples as part of your API docs; Postman allows you to create an environment from which anyone who is interested in your API can utilize it to execute requests, explore the API capabilities and export the examples in their preferred programming language. Having this, will save you from the trouble to write and manage client libraries in different programming languages and to reduce the learning curve for your custom libraries. 4. Wrapping up While you keep developing your API don’t forget that your documentation will be updated automatically but you will still need to update your Postman docs and GitHub examples. While iterating, reserve some time for maintaining the documentation and analyze you haven’t broken your existing client’s integrations.
We are living in a time when technology is developing with fast pace. Today most of the users can enjoy the privilege of having an internet connection, using modern web browsers and using modern web applications. As a result of the technology, browsers can now host and serve complex applications. Rule of the thumb is that no matter how speed the internet connection is, if not properly develop, the web application that your team is building might end up with a not so great user experience at the end. In order to excel our customer expectations we might need to go one step further into optimizing what we’ve developed so far or even better – to develop our next web application with optimization practices in mind. If you wonder what you need to do there is a great development check list published by Yahoo! development team. Most of the rules are general development/configuration rules. I will go into technical “How to” for implementing them in ASP.NET MVC/IIS. First thing first, we will start with the simple rules. Add style sheets in the HEAD This is a simple rule that combine several sub rules like:
Add your scripts at the bottom of the <body>User defer tag in <script src=”PATH_TO_SCRIPT” defer/> to prevent blocking from the html render. Add favicon All modern browsers will first try to resolve your favicon. Add favicon to speedup page resolve and load. You will notice that faulted requests that try to access the favicon will disappear from your web app log file. Don’t forget to add favicon route exception in you ASP.NET MVC web app. routes.IgnoreRoute("{*favicon}", new { favicon = @"(.*/)?favicon.ico(/.*)?" }); Minimize request for the static data by using CSS sprites Put all of your icons and assets that you are using for your design into one file. Create CSS file to access the resources. You will minimize n*request per resource time that the browser would call for the separate assets. There is a great online tool for creating CSS from a sprite. Check out Sprite cow – http://www.spritecow.com. Once you’ve got the images that will be used in your web app, use https://tinypng.com/ to compress the sprites. The service will trim all of the metadata in the sprites and minimize the file size. Configure IIS and your webapp for GZIP and Cacheing To configure IIS please use this guide from Microsoft http://technet.microsoft.com/en-us/library/cc730629%28v=ws.10%29.aspx. Once successfully configured under system.webServer configuration section add: <system.webServer> <staticContent> <remove fileExtension=".js" /> <remove fileExtension=".css" /> <mimeMap fileExtension=".js" mimeType="text/javascript" /> <mimeMap fileExtension=".css" mimeType="text/css" /> <clientCache cacheControlCustom="public" cacheControlMode="UseMaxAge" cacheControlMaxAge="500.00:00:00" /> </staticContent> <urlCompression doStaticCompression="true" doDynamicCompression="true" /> </system.webServer> Minimize your CSS and JavaScript Files In order to minimize your CSS and JavaScript files you will need to install SquishIt and SquishIt MVC extensions. Personally I prefer SquishIt over ASP.NET Bundle. The following syntax can be used in your views to minimize the CSS files: @(Bundle.Css() .Add("~/Content/base.css") .Add("~/Content/CSS/Plugins/BootstrapDatepicker/daterangepicker-bs3.css") .MvcRender("~/Content/CSS/min/combined_#.css")) … And for Javascript: @(Bundle.JavaScript() .Add(@"~/Scripts/lib/jquery-2.0.2.min.js") .Add(@"~/Scripts/lib/jquery.slimscroll.min.js") .Add(@"~/Scripts/lib/jquery-ui-1.10.3.custom.min.js") .Add(@"~/Scripts/lib/typeahead.min.js") .Add(@"~/Scripts/lib/daterangepicker.js") .Add(@"~/Scripts/lib/moment.min.js") .MvcRender("~/Scripts/min/combined_#.js")) Trickiest of all – Cookie less domain For webapps that use Forms authentication this optimization is mandatory. You may ask why? When the user is authenticated, the web application will create an authentication cookie. The cookie is encrypted for security and the standard size is somewhere around 2KB. Each request that your browser will create while trying to retrieve the static content like: images, CSS files, JavaScript files will contain the cookie. So we have back and forth 2KB per requests. This sucks! We are left with two options:
Now we will use a modified version of an MVC cache breaker:
namespace System.Web.Mvc { public static class CdnExtensions { private static readonly Regex ScriptRegex = new Regex("<script.+?src=[\"'](.+?)[\"'].*?>", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Multiline); private static readonly Regex CssRegex = new Regex("<link.+?href=[\"'](.+?)[\"'].*?>", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Multiline); public static string CdnContent(this UrlHelper url, string link, bool isDynamicResource = false) { link = link.ToLower(); // last write date ticks to hex string cacheBreaker = string.Empty; if (!isDynamicResource) { cacheBreaker = Convert.ToString(File.GetLastWriteTimeUtc(url.RequestContext.HttpContext.Request.MapPath(link)).Ticks, 16); } link = link.TrimStart(new[] {'~', '/'}); link = string.Format("{0}/{1}", “BASE_CDN_URL”, link); // returns the file URL in static domain return isDynamicResource ? link : string.Format("{0}?v={1}", link, cacheBreaker); } public static MvcHtmlString CdnContent(this UrlHelper url, MvcHtmlString mvcLink, bool isDynamicResource = false) { var context = mvcLink.ToHtmlString().ToLower(); // match the scripts var linksMatches = ScriptRegex.Matches(context); var sb = new StringBuilder(); foreach (Match linkMatch in linksMatches) { var link = linkMatch.Groups[1].Value; link = CdnContent(url, link, isDynamicResource); var script = string.Format("<script type='text/javascript' src='{0}'></script>", link); sb.AppendLine(script); } // match the styles linksMatches = CssRegex.Matches(context); foreach (Match linkMatch in linksMatches) { var link = linkMatch.Groups[1].Value; link = CdnContent(url, link, isDynamicResource); var script = string.Format("<link href='{0}' rel='stylesheet' type='text/css' />", link); sb.AppendLine(script); } return new MvcHtmlString(sb.ToString()); } } } HTML: @(Url.CdnContent(Bundle.Css() .Add("~/Content/base.css") .Add("~/Content/CSS/Plugins/BootstrapDatepicker/daterangepicker-bs3.css") .MvcRender("~/Content/CSS/min/combined_#.css"))) … And for Javascript: @(Url.CdnContent(Bundle.JavaScript() .Add(@"~/Scripts/lib/jquery-2.0.2.min.js") .Add(@"~/Scripts/lib/jquery.slimscroll.min.js") .Add(@"~/Scripts/lib/jquery-ui-1.10.3.custom.min.js") .Add(@"~/Scripts/lib/typeahead.min.js") .Add(@"~/Scripts/lib/daterangepicker.js") .Add(@"~/Scripts/lib/moment.min.js") .MvcRender("~/Scripts/min/combined_#.js"))) What is your experience with Web Apps optimization? Happy hacking! |