Building Web API Visual Studio support tools with Roslyn - StrathWeb

Strath

February 3rd, 2013

Building Web API Visual Studio support tools with Roslyn

Because Roslyn is amazing

In my humble opinion, Microsoft Roslyn is one of the most exciting things on the .NET stack. One of the many (MANY) things you can do easily with Roslyn, is write your own development-time code analysis tools.

We have talked about Roslyn scripting capabilities on this blog before (twice actually). Let’s look at code analysis today and see how we could built tools that could help Web API developers build nice clean HTTP services.

More after the jump.

Background

I was recently called by Tugberk “a designated community crazy posts guy,” which I hope was a compliment :) I guess here comes one.

So I had this idea that perhaps Roslyn could make it easier for the devs to embrace HTTP standards. What Roslyn allows us to write, are refactoring tools, which would scan the code as it’s being written and hint about possible changes and fixes (just like Resharper does). You can think of the Roslyn API as reflection, only available prior to compilation, in real time during code writing.

Obviously this is applicable to any code base, not just Web API – we’ll just use Web API context as an example which we’ll create here.

Our Web API Roslyn refactoring tool

In this example, we will create a code issue provider, which will find all methods that are:
– Inside an ApiController
– are responding to POST requests
– are void

In a typical RESTful API, POST on a resource would result in creation of a resource, and most often it would mean the client should get a 201 response (it’s not a rule, but as I said, that’s by far most common). Web API automatically issues 204 for void methods.

So we will underline the return type with a little warning, infomring the developer that he should consider changing the status code. Additionally, we will provide a refactoring action – just like Resharper does – when on click, the method will be re-written according to our suggestion.

Getting started with Roslyn

I already provided a brief introduction to Roslyn the last time we used it here, so I will not duplicate that, in short you need to install Roslyn CTP. You can get it here.

Once you got everything in place, create a new project, using the template Code Issue.

CodeIssue

Initially we need to implement an ICodeIssueProvider. This will find the errors as the developer writes code, and allow us to underline it.

  1. 1. SyntaxNodeTypes property, allows us to export only specific syntax types from the document we analyze – in our case, we export classes – ClassDeclarationSyntax.

  2. 2. We check if the class inherits from ApiController – if not, we cease further processing.

  3. 3. We export all the methods (MethodDeclarationSyntax) which meet our predefined conditions. For example we not only select Post methods based on the name, but also based on HttpPostAttribute. Void is represented in Roslyn as SyntaxKind.VoidKeyword so we can easily test against such return type.

  4. 4. Finally, we issue a warning for the developer, indicating that it should span over the method return type – which means it will underline the void keyword.

This already works – we can run the application – when you debug Roslyn solution it will actually start a 2nd VS instance (after all your solution is going to be an extension to VS), where you can open any project and verify whether your CodeIssue works correctly.

If we run it against a WebAPI project we get a nice result, as expected:

But of course, that’s just a warning now. We still need a second piece of the puzzle, Code Action, which will provide an option for the developer to automatically rewrite his code according to the warning’s suggestion.

CodeAction

This time we implement ICodeAction. This class will be capable of rewriting the entire document the developer is currently working on.

By default we need to just implement a single method, GetEdit. Interestingly it doesn’t take any useful parameters, so we pass whatever we need through a custom constructor.

This looks a bit convoluted, but in fact is very simple – as soon as you get used to different Roslyn artefacts.

  1. 1. We pass the IDocument (what the developer is working on) and a MethodDeclarationSyntax (corresponds to the methods we found in the CodeIssue). Notice we don’t pass an array of methods, since each CodeAction executes seperately!

  2. 2. We create a new return type and a new expression statement for the method (without return keyword though). Notice that since we are rewriting raw uncompiled code, we have to take care of everything – like spaces, semicolons, curly braces and so on. Roslyn has syntax that allows you to operate on all of that.

  3. 3. We compose a new ReturnStatement – out of SyntaxKind.ReturnKeyword, our expression and – yes – semi colon (SyntaxKind.SemicolonToken).

  4. 4. We then copy the existing method’s body – except its return statement, if it already has one (yes, it might even though it’s a void method – remember this code does not need to compile to be possible to be processed by Roslyn). We insert our return statement in there. On a side note, notice how Roslyn collections are immutable, and I need to reassign them to the original variable after every change. That’s obviously for performance reasons.

  5. 5. We create a new method based on the old method’s data and the changes we have made so far. This new method will have a correct return type (HttpResponseMessage).

  6. 6. We get the existing syntax root from the developer’s workspace – IDocument, and replace old method with new method. Finally we just do some basic formatting and we are done!

In order for this to work, we still have to modify the CodeIssue to call the CodeAction after it finds the matches, so we change the yield to:

We can now run this solution and it works as expected. Notice a little lightbulb icon providing us with more info. When we hover on there, we get a nice little preview of what will happen if we click. Notice the rest of the method’s body is preserved.

Voila:

Installation

By default, this CodeIssue project template compiles to VSIX – Visual Studio extension. Indeed, if you compile it, you will find the *.vsix installer in the Debug/Release folder.

You can install it as any other Visual Studio Extension.

Important: In order for this to work (remember Roslyn is a CTP) you need to run Visual Studio with /rootsuffix Roslyn. So:

If you open VS, you will see the extension in the installed list:

And it will be inspecting your code in real time (all your open documents will be marked with [Roslyn]).

Summary & source

I hope this was an interesting exercise. Using the exact same pattern, you can build rich development time design tools, for example to enforce certain rules in your team or to simply provide shortcuts and easy refactoring mechanisms.

I think it would be nice to package various HTTP standards-driven semantics (like the one I showed here) into a big VSIX, and distribute it to provide rich Web API tooling. For example we could easily sniff out the dreaded ambigous action found error at design time :)

Source code, as usually is on GitHub.

Be Sociable, Share!

  • Pingback: The Morning Brew - Chris Alcock » The Morning Brew #1287

  • Pingback: Dew Drop – February 4, 2012 (#1,493) | Alvin Ashcraft's Morning Dew

  • Donatas Mačiūnas

    That is really great! Thank you for sharing :)

  • http://www.tonicodes.net/blog/ Toni Petrina

    Wow, great tutorial. Thanks so much :)

  • James Telfer

    This is a great article — Roslyn is truly interesting and I had no idea it could do this. Thanks for putting it up!

    In your example however, I’m not sure 201 Created is a valid default suggestion for a return from a POST. Perhaps you meant PUT? A POST will typically modify resources, creates should probably be handled by PUT and it’s nice to return a Location header from the PUT with a URI of the created resource.

    201 from POST is not wrong, but I don’t think it’s typical.

    • http://www.strathweb.com/ Filip W

      Thanks a lot.

      While HTTP spec does not force you to create resources with this or that method explicitly, it’s been a rather commonly adapted pattern that POST creates and PUT updates resources [1]. In fact, ASP.NET Web API itself tries to force you into this pattern through its default templates.

      RFC2616 [2] specifies that “The PUT method requests that the enclosed entity be stored under the supplied Request-URI.” This implies that that resource either exists already or at least has to be URI-addressable upfront. So if you would want to create a new resource with PUT under /api/items/1 you’d need to make the request to that URI (rather than /api/items/). Since clients almost never have knowledge of IDs upfront, this makes it difficult to use PUT for resource creation. Another thing is that PUT is idempotent which may not be ideal for resource creation.

      [1] http://stackoverflow.com/questions/3655505/put-versus-post-rest
      [2] http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.5