Public vs Private ASP.Net Web API 2 Help Pages
I had to go through an exercise at work recently of converting our ASP.Net Web API from version 1 to version 2. One of the great features in version 2 is the automatic help system, allowing you to very easily document your API just by using the method comments. This system is awesome as long as you’re fine with showing all of your methods to everyone with access the help page. However, we needed to hide some of these as they were not for public consumption, potentially revealing how some of our internal LOB systems operate. We already have some custom access control in place, but the help page system is oblivious to this.
So to customize the help system to hide some methods, or show depending upon access level, you’re left with two options.
- Create two separate APIs, one for your public methods and another for your private/restricted methods. This is a really good way of doing it, if the option is available to you.
- Customize the underlying help page system with a system that designates which methods should be public, and which should be private. Not a beginners level exercise.
Since our API was being upgraded and several applications were already in place using this API, we didn’t have much choice and were left with option #2 as really our only choice. I’m going to detail what was involved in this process. It is by no means perfect, so if you have suggestions on how it can be improved, please let me know. I would love to discuss it. I’m using images for most of the code, there is a link to a sample project at the end of this post. If you’re like me and hate when people do this, I apologize up front. There were just too many spots where I had to make changes, and I’m not a good enough technical writer to make this post copy and paste-able without making a mess of it. So lets get into it.
The Fine Print
First things first, a couple of things I won’t cover in this. You need to be familiar with enabling the help system to generate and read from an XML file, that underpins this whole thing. Secondly, I won’t cover any sort of authentication. You need to protect the private API help page and the XML documentation file in some way. Our API lives on the company servers, so I used Windows Authentication, but any of the other normal ASP.Net authentication options should work as well. Lastly, I’m assuming that you’re starting with a Web API 2 project, I’m not covering creating or upgrading one. With that out of the way, lets get into it.
Laying the Groundwork
The basis of the entire help system is the ApiExplorer, which inherits from IApiExplorer. We’ll need to inherit from these appropriately. The ApiExplorer is automatically wired into Web API as a service, so we’ll need to replace it with our customized version. How difficult can that be? As it turns out, far more than I had expected.
Let’s start by creating the filters that do the business of marking which API method should be shown in the public help section, and which should be shown in the private. With the API I was working on, I didn’t want anything to be accidentally exposed publicly by a developer forgetting to apply an attribute, so it defaults everything to private. In the sample project, there are comments indicating how to flip this and make everything public by default in the CustomApiExplorer.cs file. The first thing to to then is to create the attribute we use to mark the methods that are public.
Create a PrivateAccessLevelAttribute with the exact same OnActionExecuting method. You don’t actually use it unless you flip the logic to default to public, but it’s nice to have in case you ever want to be explicit. I have them inheriting from an interface, IAccessLevelAttribute, which actually isn’t needed. I was just trying to future proof with that. So now that we have our attribute, lets put it on some methods so we’ll have something to work with. ValuesController, here we come!
Wow, that’s some boring code, but it gets the point across. The GET methods have the PublicAccessLevel attribute on them, and the POST, PUT, and DELETE will be left to default to private. So now we can start in on customizing the ApiExplorer. Almost all of the remaining changes will happen in the help pages area. (Areas/HelpPages in Solution Explorer.) I love me some Interface, so lets create one in there, called ICustomApiExplorer that will extend the ApiExplorer just a little bit.
Not surprisingly, our we’re going to create CustomApiExplorer next, which will inherit from ICustomApiExplorer, as well as ApiExplorer.
You should be able to see the comments I mentioned earlier about flipping the logic to default the access level to public in there. There are also comments about why it’s keeping a full copy of the ApiExplorer rather that just inheriting from it. Maybe there is a way to inherit from it and get around the lazy loading issues, but I wasn’t clever enough (or patient enough) to figure it out.
The Supporting Details
If you were to copy the CustomApiExplorer class in, you would notice that the HasPublicAccessLevelAttribute method doesn’t exist. Let’s create a class for these extension methods, let’s call it AccessLevelExtensions, in the same spot as our CustomApiExplorer.
It’s important to note the reference count on those. The only one currently being used is the HasPublicAccessLevelAttribute method that was in our CustomApiExplorer class. If you wanted to make a controller level method you would need to override the ShouldExploreController method from ApiExplorer in CustomApiExplorer in a similar fashion to ShouldExploreAction. This would utilize the other extension method overload that carries the HttpControllerDescriptor parameter. Like I mentioned previously, I want a developer to have to mark each and every method that they want in the public help page, so I don’t have the controller level option implemented for the attributes. In my experience, it’s very easy to miss noticing an attribute on the controller level, so I’m not even allowing that option. The extension methods for the private access level attribute are provided for completeness, in case you want to default to public.
Let’s finish up the ApiExplorer customization by replacing it with our CustomApiExplorer in the Web API services.
I deleted most of the commented out code that lives in there when you create a new project so that you can now see where I’ve enabled the path to the XML documentation file. The ApiExplorer replacement happens in the two lines of code below that. This is the key piece that holds everything together, and was honestly the toughest bit of the entire project to figure out.
Displaying the Private Help Page
The toughest bits are past us, and we’re just left with the presentation of the private help page. Now we’ll create a new Action in the HelpController for our private help page.
You need to fully qualify the Authorize attribute as the API one is also available, so it doesn’t know which one to use. Now we need to change the index action to only show methods with the public access attribute.
On both of those, note the GetCustomApiExplorer extension method. It is visible in the extension methods above, but I didn’t mention it then. Since we replaced the ApiExplorer in the services with our CustomApiExplorer, we need to get that type out. This extension method is fundamentally the same as what is normally there, GetApiExplorer, just modified to return our CustomApiExplorer type instead.
Now generate the view for the Private Help Page. I’m a fan of right clicking in the controller method and going to Add View. Once it’s out there, copy and paste the code from the Index view, changing the Page Title and Description to indicate that it is the private help page.
Now we need to be able to reach the Private Help page. How you make this available is up to you and the presentation of the main area of your project. For the demo project, or for any project that is using the default Web API project template, you can add it to the menu in the main layout view. (This live in project root/Views/Shared/_Layout.cshtml.)
The list item below the comment replaces the list item that previously only went to the help page index. This will give you the slick Bootstrap menu instead. It should now be ready to run!
Wrapping Up
As mentioned, a link to the sample project is below. I hope this helps out anyone that runs into the same project requirement that I did. It’s not an easy exercise, but it was an interesting one. There was quite a bit of digging into the Web API source code, which never would have been possible before Microsoft’s shift towards being more open. I think I have contact info on this blog, but if not, you can usually hit me up on Twitter (@slynch78).
Download