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.


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);


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.

Roslyn Green Squiggly

Roslyn 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.
            title: title,
            createChangedSolution: c => MakeUppercaseAsync(context.Document, classDeclaration, c),
            equivalenceKey: title),

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 + ");");

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

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


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!


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

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

kernel.Components.Add<IBindingResolver, ContravariantBindingResolver>();

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);

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:


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

Before – 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

> npm install typings --global

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:

> typings init
> typings install dt~react --global

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

│   index.d.ts

Step 4 – Install typescript

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

Npm install typescript@next

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


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

  • I haven’t created a CODE_TSJS or VSCODE_TSJS 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.

Scrum Master Tip – Team Consensus

In Scrum Master Tip – Many Voices I listed a few things to try if your team meetings are dominated by one person and you want to hear from the others in the team.

On a similar note, I’ve found the following tips useful to Team consensus on decisions.

Issue to Avoid

Failing to get buy-in from the whole team on important decisions.

Why is it a problem?

If someone doesn’t agree with a decision, they are unlikely to follow it. At worse, they will become unmotivated and disruptive and slowly drag the team around them down.

I try to save these alternative techniques for the important decisions and fall back on the simple consent check of “does everyone agree?…ok then, decision made” for day to day things.

Potential Solutions

With all of these solutions, there’s a risk that the more timid members of your team are copying the dominant person, so keep an eye out and if need be, split the group up.

Roman Voting

After 3, everyone gives a thumbs up or thumbs down. other.

Vote with your feet

Imagine there is a line along one wall where one end is agree and the other is disagree and ask the team to stand along the wall depending how strongly .

Consensus Check

Use planning poker cards where a higher number is more agreement.

Unit Testing – Check your Return on Investment

I once asked a team to question everything we do. We ended up with a few suggestions about things we could streamline but I was surprised at how quickly everyone on the team said “unit tests are good”.

Are unit tests good?

I’m strongly believe that unit tests are critical, but only if they offer a good return on investment. It takes a scary amount of time writing and maintaining a suite of unit tests, so any efficiency savings can really add up.

Why are you spending time and money writing and maintaining unit tests that are trivial?

Adam Tibi got me thinking about it a lot recently when I read his post on not testing MVC controllers.

When are unit tests “Bad”

I basically agree with Adam, but applied to every single line of code, not just controllers.

I’ve seen teams decide a certain % of code coverage is required and then just mechanically write test upon test until that magic number is hit. What’s the point of unit testing something like a simple wrapper that passes through to another layer. What have you achieved?

I find questioning the ROI of a unit test can also lead to some nice refactoring.


Question your return on investment of every unit test you write and maintain. Why are you spending time and money writing and maintaining unit tests that are trivial.

If the unit test is pointless, mark the code under test with some sort of “ExcludeFromCodeCoverage” attribute and spend your time, and money, on more important things.

Convert unix epoch time to DateTime in C#

I recently had to convert unix epoch time to DateTime and like the rest of the world turned to stackoverflow. The top answer is a very simple solution of adding the unix time, which is in milliseconds, to a DateTime of 1/1/1970.

Nothing wrong with that, but it turns out it was added into DateTimeOffset in .NET 4.6:

static DateTimeOffset FromUnixTimeSeconds(long seconds)
static DateTimeOffset FromUnixTimeMilliseconds(long milliseconds)
long DateTimeOffset.ToUnixTimeSeconds()
long DateTimeOffset.ToUnixTimeMilliseconds()

So now you can do something like:

var dateTimeOffset = DateTimeOffset.FromUnixTimeMilliseconds(1454049938871);
var dateTime = dateTimeOffset.DateTime;
Console.WriteLine($"The time is {dateTime}");     // The time is 29/01/2016 06:45:38

I can’t imagine it will make a massive difference to your application whichever way you choose, but I think it’s really good that little things like this are still being added to the framework after all this time.

Further Reading

If you’re really interested, you can look at the /source and see it’s pretty much the same thing, but I imagine it’s slightly more efficient. I guess you could do some metrics, but if converting to and from unix time is the bottleneck in your application I envy you!

Who owns impediments?

In a previous post I listed some of the different types of impediment to identify as a scrum master. In this post I’m going to go through the same types and list who I think should own them and some ideas of how to try and remove them.

Types of Impediment

Different Types of Impediment to Identify


In the same order of smallest scope, all the way out to the whole organisation.

Technical Impediment

Some organisations I’ve worked with make it the responsibility of the team member to raise support requests to get things like “my monitor is broke” resolved, but I think that’s a mistake. Sorting out issues (or going to PC World to buy parts) is a waste of team members time. The scrum master as “general dogsbody” should be solving this.

In the meantime, is there another machine to use because someone is on holiday, ill or in a meeting? Could they pair with someone? Basically, don’t treat broken equipment as dead time.

Team Member Impediments

Again, ignoring personal issues (they might be a HR problem), these types of impediment fall squarely on the individual.
I’m a strong believer in treating people like adults, so although it may not be their fault they are not familiar with a technology – that’s an organisational issue in resourcing – they now need to become productive.

It’s the Scrum Masters job to help with that in any way they can, say putting them in touch with the company SME or other training resource, but ultimately the individual has to put the work in.

Technology Impediment

I believe the team should prove to themselves that changing the technology is worthwhile. Once they are convinced, getting buy in the from the product owner and stakeholders to complete technical stories and not add business value should be easier.

Using the example from the previous post of upgrading from EF5 to EF6, ask 1 person to come up with a business case detailing the return on investment of performing the upgrade. It’s a balance between how long the upgrade will take, including roll out to production environments vs how much time it will save in the future.

If there’s 2 weeks left on the project, it’s going to take 5 days to upgrade and save 1 day it’s not worth it!

Process Impediment

If you do have a vocal member of the team telling you something is lacking, I would use this opportunity to try and coach them to become solution orientated. I’d task that person to take ownership and come up with a solution but provide lots of assurance that if they’re solution doesn’t work it’s not a problem.

Team Impediment

These types of impediment can be very hard to solve and unfortunately the ownership falls on the scrum master.

If you are unfortunate enough to have a “bad egg” and you’re inexperienced with dealing with these types of issue I recommend you speak to your HR department for advice. If that’s not possible, seek advice from someone who does.


The Scrum Master Podcast calls this “the dreaded system”. I imagine they’ve used the word “dreaded” because changing the system you work in can be almost impossible.

Ownership again lives with the Scrum Master, not to solve them, but to try and isolate the team from them. I find a great exercise to help with this is Circles and Soups retrospective.


Not all impediments are the scrum masters to solve, sometimes it can be really beneficial to pass ownership to a team member.

This obviously isn’t an exhaustive list and I’m not going to claim it’s perfect, so please leave a comment below or catch me on twitter if you have any comments.

Different Types of Impediment to Identify

As a Scrum Master, impediment removal is not only an essential part of your job, but it’s probably one of the biggest ways to have a positive impact on team velocity. Even a highly motivated, highly skilled team are going to struggle if half of their equipment is broke!

What I didn’t realise when I first started out was that there are many different types of impediment. I guess as I have a technical background I automatically focussed on things like “slow pcs”, “technology X isn’t great”, “my monitor is too small” and “technical debt”, but as I work with more teams and different customers I’m beginning to realise the subtle issues are much more common.

Types of Impediment

Different Types of Impediment to Identify


In order of smallest scope, all the way out to the whole organisation, here’s a list of some of the types of impediments you might encounter. Another post will detail who I think should own the impediment and try to resolve it.

Technical Impediment

I find these are the most obvious to spot as the team member suffering tends to point it out. I’m thinking “my mouse is broke” or “my PC won’t boot” types of things.

Team Member Impediments

Ignoring personal issues, these could be an individual team members lack of experience with a technology the team uses. Identifying these types of issue can sometimes be hard as developers don’t like admitting they don’t know something.

Technology Impediment

Similar to team member impediments, but this time something affecting the whole team. It could also be lack of experience, or it could be something more technical. For example, upgrading from Entity Framework 5 to 6 makes unit testing a lot easier, but if you can’t for whatever reason, this would be a technical impediment.

Process Impediment

You still have processes right? “Individuals and interactions over processes and tools” doesn’t mean no processes. So this type of impediment could be something like “the code review process is poorly defined and we’re not getting value for money”.

I find these quite easy to identify as the more experienced members of teams seem to be very vocal if something is lacking. If you aren’t that lucky, try and encourage a more open dialog between the you and the team. If that fails, hold a “processes focussed” retrospective.

Team Impediment

Team impediments can be very hard to see quickly and unfortunately tend to have the highest negative impact. The issue is very likely to have a negative effect on morale let alone velocity. I most often see one team member dominating your planning sessions, talking over everyone and worse, criticising other team members ideas and work.

I’m classing this as a team impediment instead of individual as the whole team is affected and it could be up to the whole team to address it. Unfortunately the most common outcome is often the “bad apple” being removed from the team.


These can be easy to identify, but very hard to resolve as they’re often outside the control of the team. For example, I have a person on my team who is constantly being asked by other managers to help out with other work. As a result, they are never able to properly focus on the work for my project.


That isn’t an exhaustive list, but will hopefully get you thinking about the types of issues a Scrum Master has to deal with. In an upcoming post I’ll go through the same list again stating who I think should own the impediment and therefore get it removed. It’s not always the Scrum Masters job!

Scrum Master Tip – Many Voices

This is a quick tip that I struggled with when I started as a Scrum Master and wish someone told me about.

Issue to Avoid

One person talking for the majority of the time during retrospectives, although this applies to all meetings.

Why is it a problem?

One voice is not only boring for the others, but you’re very unlikely to get the engagement required to improve.

You’re also potentially missing out on a great idea that someone else hasn’t been able to say.

Potential Solutions

Try not to stand at the front talking all the time.

Try asking each team member to present their points and reason for choosing, instead of handing you their post-it.

Try asking a different team member to take the retrospective.

If you’re not getting much in the way of contributions, directly ask the quietest member of the team to say their thoughts on a particular topic.

Try to ask open questions so it’s difficult to give 1 word response. “What do you think of X?” is much more likely to get a good response than “Is X a problem?”.

If after all the above, you’re still getting little input, it may be because everyone agrees. Encourage someone on the to play devil’s advocate and disagree.


Learn the power of silence

Most people hate sitting in a silent meeting. If you keep quiet, someone else will fill the void.

Persist Claims Transformation in a cookie with MVC and OWIN

Claims transformation (or claims augmentation as it’s sometimes called) in an MVC claims based application is “easy”. All you need is a simple piece of code:

Principal.Identity.AddClaim(new Claim(ClaimType, "ClaimValue")); 

Unfortunately, where you add that code isn’t.


I found a number of options that worked, but didn’t behave in the way I needed.

Option 1 – use a custom ClaimsAuthenticationManager as detailed on MSDN.

Option 2 – add the above code into the Application_PostAuthenticateRequest method of Global.asax

Option 3 – if you’re using Owin, to create some Katana Middleware


The problem with all these solutions is the number of times the transformation takes place, i.e. how often that code is executed.

Why would you care about the number of times it’s called? In all the examples I found, you wouldn’t, as “magic strings” are being added to the claims, and therefore it’s really fast. In my case, and I’d imagine most real world cases, you’re likely to be making an IO bound call to a database or web service to lookup the extra claim. You really don’t want to be doing that every single page hit.


I eventually hit upon the solution with the thanks to a StackOverflow post which hinted at using the OnResponseSignIn of the CookieAuthenticationProvider

Provider = new CookieAuthenticationProvider() 
    OnResponseSignIn = async context => 
         // Apply Claims Transformation here

The OnResponseSignIn is the last chance you have to transform the ClaimsIdentity before it is serialized into a cookie during sign in. The code is only executed once, so no need to be concerned about performance when making a call to a lookup service.