How To Use Roslyn CodeFix to add a ToString method

I recently had the opportunity to update the quality of logging in an application. Exceptions were handled well, but it was hard to see the values passed through the layers. I ended up using a Roslyn CodeFix to add a ToString method. This is what I did.

Setup

Once you’ve created a new “Analyzer with Code Fix” project, there’s two parts to write:

  1. The Analyzer – the code that highlights the problem by adding a green squiggly line;
  2. The CodeFix – the code that runs to “fix” the problem highlighted by the analyzer

The Analyzer

My requirement was straight foreward, “add a ToString method” to a class. Turns out the code created in the new project is almost good enough. It highlights any classes declared with lowercase letters. As I want all classes, we can just change the AnalyzeSymbol method in Analyzer.cs to:

private static void AnalyzeSymbol(SymbolAnalysisContext context)
{
    var namedTypeSymbol = (INamedTypeSymbol)context.Symbol;

    var diagnostic = Diagnostic.Create(Rule,
        namedTypeSymbol.Locations[0],
        namedTypeSymbol.Name);

    context.ReportDiagnostic(diagnostic);
}

That’s it for the analyzer. It passes all Class Declarations to Rosylyn to create a Diagnostic.

If you press F5 now to debug the project, a new instance of Visual Studio will open. This new instance has our analyzer installed. So create a simple console app and you will see all class declarations are decorated with a green squiggly line.

Rosylyn Green Squiggly
Rosylyn Green Squiggly
So far, so good. Now to create the codefix.

The CodeFix

This is a bit more involved. I didn’t know what I was doing and struggled with the documentation. After some trial and error, this is what I ended up doing:

  • Find the class declaration (again, I don’t think the information from the analyzer gets passed across)
  • Loop over all the public properties of that class and construct the ToString method
  • Add the new ToString method

Find the class declaration

The project sample uses RegisterCodeFixesAsync in the CodeFixProvider.cs file as the entry point. I changed RegisterCodeFixesAsync by first adding:

var classDeclaration = root.DescendantNodes().FirstOrDefault(
    node => node is ClassDeclarationSyntax) as ClassDeclarationSyntax;
if (classDeclaration == null) return;

And then updating the call to RegisterCodeFix to pass this new classDeclaration variable. The whole method looks like:

public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
    var root = await context.Document
        .GetSyntaxRootAsync(context.CancellationToken)
        .ConfigureAwait(false);
    var diagnostic = context.Diagnostics.First();

    var classDeclaration = root.DescendantNodes()
        .FirstOrDefault(node => node is ClassDeclarationSyntax)
         as ClassDeclarationSyntax;
    if (classDeclaration == null) return;

    // Register a code action that will invoke the fix.
    context.RegisterCodeFix(
        CodeAction.Create(
            title: title,
            createChangedSolution: c =>
                MakeUppercaseAsync(context.Document, classDeclaration, c),
            equivalenceKey: title),
        diagnostic); 
}

That passes all instances of class declarations to a method called MakeUppercaseAsync. For this example, it is a terrible name, but it’s the one created by the project. Feel free to rename it to something more accurate. I’ve left it to help with cutting and pasting for this post.

Now we’ve found the class declaration, let’s build up the ToString method.

Building the ToString body

To construct the body, we need to find all the properties in the class, loop over them, and create a string that will represent the body.

This gets complicated quickly. We need to write some C#, that will write the C# that makes the ToString method. Confused? I certainly was. I ended up thinking of it as layers on the onion.

The innermost layer of the onion is what’s in the logs. I wanted the log to contain something like IntProp1: 5 for a property called IntProp1 with a value of 5.

The next layer of the onion, is the C# code that you would write to achieve that log format. In this example, it would be something like:

Console.WriteLine("{0}: {1}, ", nameof(IntProp1), IntProp1);

The outer layer of the onion is the code we put in the CodeFix. It’s output will be added to the class. It needs to create the C# code, that when ran will output the above Console.WriteLine.

Rather than using lots of Console.WriteLine statements, I decided to use a StringBuilder, but the outcome is the same. I ended up with the following to construct the body of the ToString method:

SyntaxNode root = await document.GetSyntaxRootAsync(cancellationToken)
    .ConfigureAwait(false);
var props = root.DescendantNodes().Where(x => x is PropertyDeclarationSyntax);

// Construct the contents of the ToString method
StringBuilder sb = new StringBuilder(@"
    StringBuilder resultSb = new StringBuilder("""");
");

foreach (SyntaxNode currentProp in props)
{
    var currentSyntax = currentProp as PropertyDeclarationSyntax;

    sb.Append("resultSb.AppendFormat(\"{0}: {1}, \", nameof(" +
        currentSyntax.Identifier.Value + "), " +
        currentSyntax.Identifier.Value + ");");
    sb.Append(Environment.NewLine);
}

As you can see, this loops over all the descendants of the root node (the class declaration) that are Property Declarations. Then appends to a StringBuilder the format we want.

Putting it all together

My final MakeUppercaseAsync method looked like:

private async Task<Solution> MakeUppercaseAsync(Document document,
    ClassDeclarationSyntax classDecl,
    CancellationToken cancellationToken)

It’s the code above, with some extra helper methods that I found on stack overflow. For this project, just put them at the end of the class, but a helper library would be a better long term choice.

Helper Methods

Credit goes to Nvalchev in this answer on stack overflow for these. They help create method declarations and constructing parameter lists.

public MethodDeclarationSyntax GetMethodDeclarationSyntax(string returnTypeName, string methodName, string body)
{
    var syntax = SyntaxFactory.ParseStatement(@"" + body + "return resultSb.ToString();");
    var parameterList = SyntaxFactory.ParameterList(SyntaxFactory.SeparatedList(GetParametersList(new string[0], new string[0])));
    var modifiers = new SyntaxToken[] { SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.OverrideKeyword) };

    return SyntaxFactory.MethodDeclaration(attributeLists: SyntaxFactory.List<AttributeListSyntax>(),
                  modifiers: SyntaxFactory.TokenList(modifiers),
                  returnType: SyntaxFactory.ParseTypeName(returnTypeName),
                  explicitInterfaceSpecifier: null,
                  identifier: SyntaxFactory.Identifier(methodName),
                  typeParameterList: null,
                  parameterList: parameterList,
                  constraintClauses: SyntaxFactory.List<TypeParameterConstraintClauseSyntax>(),
                  body: SyntaxFactory.Block(syntax),
                  semicolonToken: SyntaxFactory.Token(SyntaxKind.SemicolonToken))
          // Annotate that this node should be formatted
          .WithAdditionalAnnotations(Formatter.Annotation);
}

private IEnumerable<ParameterSyntax> GetParametersList(string[] parameterTypes, string[] paramterNames)
{
    for (int i = 0; i < parameterTypes.Length; i++)
    {
        yield return SyntaxFactory.Parameter(attributeLists: SyntaxFactory.List<AttributeListSyntax>(),
                                                 modifiers: SyntaxFactory.TokenList(),
                                                 type: SyntaxFactory.ParseTypeName(parameterTypes[i]),
                                                 identifier: SyntaxFactory.Identifier(paramterNames[i]),
                                                 @default: null);
    }
}

If you run this code now, and apply the code fix to a class with some properties, it should add a ToString method.

ToString Method Added
ToString Method Added

Alternatives

Before winding up this post, I think it’s worth pointing out some other ways of doing it.

My first thought was to use reflection. The idea was to reflect over the objects being logged. I imagine it would’ve worked, but probably isn’t great performance wise.

Then I considered just relying on converting the objects to JSON. This has the advantage of “just working” for every object. And unless you have a good reason not to go down that route, I think it’s a great idea.

Unfortunately, both of those suffer the same problem. They are a bit inflexible and they are both all or nothing. I had some properties that shouldn’t be in the log files. Making sure those weren’t logged is a lot easier when you have a ToString method.

I’m sure there’s a way of using attributes (or something else) to make the other two handle that. But that relies on the dev knowing they are there and it’s too easy for an object to slip through the gaps. Using a ToString method at least makes it explicit.

Finally, there’s always ReSharper!

Conclusions

The solution in this post is far from perfect. There are a number of things that can be improved:

  • It doesn’t handle classes that already have a ToString method very well;
  • It doesn’t add the required “using” statements;
  • It adds an extra semi-colon at the end
  • Property type is ignored, Collections, Lists etc is not handled

Despite those issues, it’s already saved me a lot of typing. Also, creating a Roslyn CodeFix to add a ToString method was a good learning experience. Getting started was difficult, but the more I use the features of Roslyn, the more convinced I am that I’ll be doing a lot more in the future.

Let me know if you spot a mistake or an area I can improve on. I’ve uploaded the code so far to GitHub. Feel free to make a PR or raise an issue.

Command Pattern in Web API2 with MediatR and Ninject

I ran across MediatR the other day while looking into the command pattern. I’ve been working a lot with micro-services. So I wanted to see how I could use the Command Pattern in Web API 2 with MediatR and Ninject.

Project Configuration

From the project wiki, it seems Jimmy Bogard prefers StructureMap as a DI container. I’ve been mostly using Ninject and the documentation wasn’t quite as clear.

Rather than showing the least amount of code, let’s create a new project and send an example command.

Step 1 – Start a New ASP.NET Web Application and make sure Web API is selected.

Step 2 – Installing NuGet Packages

Install the following NuGet packages

a. Ninject.Web.WebApi.WebHost

b. MediatR

c. Swashbuckle

We don’t need Swashbuckle, but it makes it a lot easier to test.

Using MediatR

I’ll show a trivial example that negates the command passed in. Your real world usage will be more complex. But this will show the technology without complicating the example.

Step 1 – Create a IRequest object

Objects that implement the IRequest interface represent Commands. Create a simple class called CommandExample like:

public class CommandExample : IRequest<bool>
{
    public bool NotMe { get; set; }
}

Step 2 – Create a Handler

Handlers should implement IRequestHandler<in TRequest, out TResponse>. Where TRequest is the type of object we created in step 1 and TResponse is the type you want the handler to return.

So, for us, TRequest is CommandExample and as we’re negating the bool passed in, TResponse is bool. So create a new class called CommandExampleHandler:

public class CommandExampleHandler : IRequestHandler<CommandExample, bool>
{
    public bool Handle(CommandExample message)
    {
        return !message.NotMe;
    }
}

And that’s it for the plumbing of MediatR. We have now implemented with command pattern.

But before this will work, we need to configure our DI container, in my case Ninject.

Ninject Configuration

I struggled with this as I couldn’t find any clear examples. The project github repos has an example, but I couldn’t get it to compile . When I finally did, it didn’t work. I failed to use Ninject.Common.Extensions , but the below definitely works.

First, take a copy of https://github.com/jbogard/MediatR/blob/master/samples/MediatR.Examples.Ninject/ContravariantBindingResolver.cs.

Then, in App_Start/NinjectWebCommon.cs add the following to RegisterServices

kernel.Components.Add<IBindingResolver, ContravariantBindingResolver>();
kernel.Bind<IMediator>().To<Mediator>();

kernel.Bind<IRequestHandler<CommandExample, bool>>().To<CommandExampleHandler>();
kernel.Bind<SingleInstanceFactory>().ToMethod(ctx => t => ctx.Kernel.TryGet(t));
kernel.Bind<MultiInstanceFactory>().ToMethod(ctx => t => ctx.Kernel.GetAll(t));

Note the highlighted line. It is specific to the command and handler classes we created above. Change them for your classes if you’re not following along.

All that’s left is using our commands in our Web API 2 application.

Web API 2 controller

I intend to call mediator from within my controllers. It doesn’t have to be there, but I like to keep thin controllers.

For this example, I updated the ValuesController. First, create a constructor that takes IMediator as an argument and sets a field.

private IMediator _mediator;

public ValuesController(IMediator mediator)
{
    _mediator = mediator;
}

Then, in my case, I updated the POST action method to

// POST api/values
public async void Post(CommandExample message)
{
    response = await _mediator.Send(message);
    return;
}

As you can see, we now have a nice thin controller. Note, the use of async and await.

Let’s make sure it works.

Confirming It Works

This is where Swashbuckle comes in handy. Set a breakpoint in the POST action method and press F5. Navigate to http://localhost:/swagger and expand Values and POST. Fill out the message like below

MediatR Swagger Post
MediatR Swagger Post

Click “Try it out!” and you should be able to step through the code. Travelling through your handler. And finally back to the controller to see the value passed in negated:

Conclusion

MediatR is a small library, but makes adding the command pattern to your .net projects simple. Getting Ninject working was a little harder than I expected, but nothing too hard. Give it a try and let me know if I’m missing a trick.

How To Setup Intellisense in VSCode for React.js

I’ve been getting back into React.js development and was missing the rich developer experience visual studio gives you with things like intellisense.

I thought I had something working as Ctrl+Space opened intellisense with a sensible suggestion, but this turned out to be the IDE using what I’d typed earlier to make an educated guess. Clever, but I was hoping for more.

No Intellisense
No Intellisense

Dead Ends

Unfortunately I couldn’t find a simple “how to” guide and the stuff I did get from various github issues and stackoverflow didn’t really help.

After several dead ends and lots of hair pulling I eventually got intellisense working using these steps.

How To Setup Intellisense in VSCode for React.js

Step 1 – Create a jsconfig.json file

With a project folder open, look in the bottom right and you should see a lightbulb:

VSCode Lightbulb
VSCode Lightbulb

Click the lightbulb, and you should get a popup at the top of the IDE asking if you want to create a jsconfig.json file

Create JSConfig.json
Create JSConfig.json

Click “Create jsconfig.json” and vscode should do the rest.

Step 2 – Install Typings

The Typescript Definition Manager typings should be installed globally with

This will allow you to install typescript definition files which is what we’ll do next.

Step 3 – Install React Typescript Definitions

In the folder of the project enter the following commands:

&gt; typings install dt~react --global

You should end up with a new “typings” folder with the following contents:

│   index.d.ts
│
└───globals
    └───react
            index.d.ts
            typings.json

Step 4 – Install typescript

You can install typescript globally, but I prefer to put it in each project with the following command

Which vscode detects automatically, so there’s nothing else to it.

Step 5 – Confirm it Works

Now you should be able to see some intellisense for react.js.

VSCode With Intellisense
VSCode With Intellisense

Conclusion

While attempting to get this working I found some what appears to be old and obsolete advice.

  • I haven’t created a CODETSJS or VSCODETSJS environment variable
  • There are also a couple places mentioning ‘tsd’, but that has been superseded by typings.
  • I don’t have (Salsa) in the bottom right of the IDE. In a different setup I had the version number of typescript, but that doesn’t appear to be essential.

In all honestly, I don’t really know what I’m doing, but it appears to work. Hopefully it will work for you too, but please let me know if it doesn’t in a comment below or on twitter.