Dependency Inject a Service from Startup back to Program in .Net Core 3.1

Full source code available here.

Over the past couple of years I wrote a few posts about Dependency Injection in .Net Core 2.1, and this week I received comments from a reader telling me that some of the changes in .Net Core 3.1 mean that some of the approaches no longer work. There have been breaking changes https://docs.microsoft.com/en-us/dotnet/core/compatibility/2.2-3.1#hosting-generic-host-restricts-startup-constructor-injection

I wanted to see what would still work so I tried a few things.

You can no longer DI from Program in Startup.

But you can add a transient service and/or a singleton service to the ServiceCollection and use it within Program, and the rest of the application. You can also add a scoped service to the ServiceCollection and use it within the Program, it’s a little different from using transient and singleton so I’ll cover it in the next post.
Here’s how to use transient and singletons inside Program.
Create two services, creatively named ServiceOne and ServiceTwo and have them implement interfaces.

public class ServiceOne : IServiceOne
{
	public static int staticCounter;

	public ServiceOne()
	{
		staticCounter++;
	}
   //snip…

public class ServiceTwo : IServiceTwo
{
	public static int staticCounter;

	public ServiceTwo()
	{
		staticCounter++;
	}
	//snip…

I added a static counter to make it easy to see how many times the constructor is called.
For the transient one I expect it to increment every time the service is injected, for the singleton I expect it to remain at 1 for the lifetime of the application.

In Program.cs I split up CreateHostBuilder(args).Build call from the subsequent .Run().

public static void Main(string[] args)
{
	IHost host = CreateHostBuilder(args).Build();
	DoSomethingWithTheTransientService(host.Services);
	DoSomethingWithTheSingletonService(host.Services);
	host.Run();
}

The CreateHostBuilder() method is not changed –

public static IHostBuilder CreateHostBuilder(string[] args) =>
	Host.CreateDefaultBuilder(args)
	.ConfigureWebHostDefaults(webBuilder =>
	{
		webBuilder.UseStartup<Startup>();
	});

And I have two methods that use the ServiceCollection to access the registered services –

private static void DoSomethingWithTheTransientService(IServiceProvider serviceProvider)
{
	Console.WriteLine("Calling the transient service");

	var serviceOne = serviceProvider.GetService<IServiceOne>();
	Console.WriteLine(serviceOne.StaticCounter());
	Console.WriteLine(serviceOne.GetHashCode());
}

private static void DoSomethingWithTheSingletonService(IServiceProvider serviceProvider)
{
	Console.WriteLine("Calling the singleton service");

	var serviceTwo = serviceProvider.GetService<IServiceTwo>();
	Console.WriteLine(serviceTwo.StaticCounter());
	Console.WriteLine(serviceTwo.GetHashCode());
}

To make what’s happening more obvious I added the services to the constructor call of the WeatherForecastController() and added the counter and hash codes to the data returned by the action method.

public class WeatherForecastController : ControllerBase
{
	private IServiceOne _serviceOne;
	private IServiceTwo _serviceTwo;
	public WeatherForecastController(IServiceOne serviceOne, IServiceTwo serviceTwo)
	{
		_serviceOne = serviceOne;
		_serviceTwo = serviceTwo;
	}

For completeness, here is the ConfigureServices method in Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<IServiceOne, ServiceOne>();
    services.AddSingleton<IServiceTwo, ServiceTwo>();
    services.AddControllers();
}

Put some breakpoints in Program Main(), DoSomethingWithTheTransientService(), DoSomethingWithTheSingletonService() and in the WeatherForecastController.Get().

Start the application and browse to http://localhost:5000/weatherforecast to see what happens.

I’m going to follow up on this post with a version that shows how to use scoped dependencies in Startup .NET 5.

Full source code available here.

Executing a Method on All Implementations of an Interface

Full source code available here.

Have you ever wanted to execute a method on all implementations of an interface? Say you have an interface called IGenerator and four implementations of it – GeneratorA, GeneratorB, GeneratorC, and GeneratorD.
IGenerator is very simple, it has a single Generate(string details) method.

What I don’t want to do is –

var generatorA = new GeneratorA();
var generatorB = new GeneratorB();
var generatorC = new GeneratorC();
var generatorD = new GeneratorD();
generatorA.Generate(someDetails);
generatorB.Generate(someDetails);
generatorC.Generate(someDetails);
generatorD.Generate(someDetails);

I also don’t want to do this –

List<IGenerator> generators = new List<IGenerator>() { new GeneratorA(), new GeneratorB(), new GeneratorC(), new GeneratorD() } ;
foreach (var generator in generators) 
{
    generator.Generate(someDetails)
}

What I want to do is find all the implementations of the interface without explicitly naming them, create instances of the implementations and call the Generate(..) method. This way I can add a GeneratorE and GeneratorF and I’ll be sure their Generate(..) methods will be called.

Finding the Implementations

This code will find all the implementations of the IGenerator interface –

var iGenerator = typeof(IGenerator);
var generatorsTypes = AppDomain.CurrentDomain.GetAssemblies() 
    .SelectMany(assembly => assembly.GetTypes())
    .Where(type => iGenerator.IsAssignableFrom(type) && type.IsClass);

On line 1 I specify the type I am going to look for.

Line 2 gets all the assemblies, line 3 gets all the types in those assemblies. Line 4 limits the type to those that implement IGenerator and are a class, checking that the type is a class is important, because without it the interface itself would be included in the result.

Activating the Implementations

At this point all we have done is find the types, we have not instantiated them, so we cannot yet call the Generate(..) methods.
But of course there is a way to do just that.

List<IGenerator> generators = new List<IGenerator>();

foreach (var generator in generatorsTypes)
{
    generators.Add(Activator.CreateInstance(generator) as IGenerator);
}

Now I have a list with instances of all the implementations of IGeneraotor.

Calling the methods

Easy –

generators.ForEach(g => g.Generate("Hi from a generator."));

That’s it, find the implementations, activate them, call the method. The end.

Full source code available here.

Synchronous For Loop in Node.js

Full source code available here.

In C# it is very easy to avoid doing something asynchronously, but I found out that this is not the case with JavaScript and Node.js.

I wanted to load a large amount of data in a remote service via and API, but had to do it one request at a time. There was no bulk option.

For a little while I’ve been learning JavaScript and some Node, but had not had a chance to use the language on anything useful. This blog post is the first I’ve written on JavaScript, so if you find something that is incorrect, leave a comment and I will update it.

There are many choices for making http requests with Node, for no particular reason I chose Axios.

Here is a very simple example of post method –

async function postDataToApi(index, dataToPost) {
    try {
        const response = await axios.post("http://httpbin.org/post", {
            dataToPost
        });
        console.log(index, response.status)
    } catch (error) {
        console.log(error);
    }
}

I am calling await on the axios.post(...) method.

For the sake of this example I am going to generate the data to post using the Casual library. This create a fake product with random data in it –

casual.define('product', function () {
    return {
        productId: casual.uuid,
        productName: casual.word,
        color: casual.color_name
    }
});

Attempt 1

Now I needed call the remote service, sending it data in some sort of loop.

A simple for loop seemed the best candidate.

function simpleForLoop(start, end) {
    for (let index = start; index <= end; index++) {
        console.log("request:", index, "for loop calling postDataToApi");
        postDataToApi(index, casual.product);
    }
}

But this ran through the whole loop before it made a single outbound request and then attempted to make a very large number of requests simultaneously. This overwhelmed the remote service and got back a bunch of failures.

Here is what the output of the for loop looks like –

request:  1 simpleForLoop calling postDataToApi
request:  2 simpleForLoop calling postDataToApi
request:  3 simpleForLoop calling postDataToApi
response: 2 200 postDataToApi called remote service
response: 1 200 postDataToApi called remote service
response: 3 200 postDataToApi called remote service

The three requests happen before any response is received. The responses don’t necessarily come in the order the requests were made.

All the requests are happening asynchronously. This is not what I want.

Attempt 2

Let’s try adding an async and an await to the for loop.

async function loopWithAwait() {
    for (let index = 1; index <= 3; index++) {
        console.log("request: ", index, "loopWithAwait calling post");
        await postDataToApi(index, getProduct());
    }
}

Run the application again, and this time I get the output I want. A request is made, a response is received, then the next request is made and so on.

request:  1 loopWithAwait calling postDataToApi
response: 1 200 postDataToApi called remote service
request:  2 loopWithAwait calling postDataToApi
response: 2 200 postDataToApi called remote service
request:  3 loopWithAwait calling postDataToApi
response: 3 200 postDataToApi called remote service

Full source code available here.

AutoMapper, ProjectTo() – Instance Version

Full source code available here.

In my previous post I showed how to use the wonderful AutoMapper ProjectTo() feature, the demo code shown worked with AutoMapper up to v8.1.1. It looked like this –

_salesContext.Products.OrderBy(p => p.ProductId).Take(count).ProjectTo<ProductModel>()

But this will not work with AutoMapper v9 and above. You will be promptly informed that No overload for the method ‘ProjectTo’ takes 0 arguments.

What happened? Well, AutoMapper moved away from a static API to an instance only API.
To get this working you need to make a few minor changes compared to previous post.
In ConfigureServices(…) replace this –

Mapper.Initialize(cfg => {
    cfg.AddProfile<MappingProfile>();
});

With this –

services.AddAutoMapper(typeof(Startup));

Then in the controller, change this

public class ProductsController : ControllerBase
{
    private readonly SalesContext _salesContext;  

    public ProductsController (SalesContext salesContext)
    {
        _salesContext = salesContext;
    }
    // snip…

To this –

 
private readonly SalesContext _salesContext;
private readonly IMapper _mapper;

public ProductsController (SalesContext salesContext, IMapper mapper)
{
    _salesContext = salesContext;
    _mapper = mapper;
}
// snip..

And finally, the ProjectTo call changes from this –

[HttpGet("projected/{count}")]
public ActionResult GetProjected(int count)
{
    return Ok(_salesContext.Products.OrderBy(p => p.ProductId).Take(count).ProjectTo<ProductModel>());
}

To this –

[HttpGet("projected/{count}")]
public ActionResult GetProjected(int count)
{
    return Ok(_mapper.ProjectTo<ProductModel>(_salesContext.Products.Take(count).OrderBy(p => p.ProductId).Take(count)));
}

The SQL produced by the instance ProjectTo() is the same as what was produced by the static version.

That’s it, easy once you figure it out.

Full source code available here.

AutoMapper, ProjectTo() – Static Version

Full source code available here.

I’ve been using AutoMapper for quite a few years, but one of the features that I like the most is the ProjectTo method. When using Entity Framework it lets me reduce the number of fields I query from the database to match the model I want to return to the caller.
For example, I have a Product table in the database and a DTO that represents the Product that looks like this –

public class Product
{
	public int ProductId { get; set; }
	public string Name { get; set; }
	public ProductCategory ProductCategory { get; set; }
	public string Description { get; set; }
	public decimal Price { get; set; }
	public string SKU { get; set; }
	public string Code { get; set; }
}  

But say I wanted to return only the ProductId, Name and Code, I could run a query to return the full Product and the map the results to a model that looks like this –

public class ProductModel
{
	public int ProductId { get; set; }
	public string Name { get; set; }
	public string Code { get; set; }
}

In this scenario, the generated SELECT statement will request all seven fields in product and return them to the application, I then have to them to the ProductModel with the three fields I want.
Here is the SQL produced an executed –

SELECT TOP(@__p_0) [p].[ProductId], [p].[Code], [p].[Description], [p].[Name], [p].[Price], [p].[ProductCategory], [p].[SKU]
FROM [Products] AS [p]
ORDER BY [p].[ProductId]

Instead of requesting all seven fields the ProjectTo method will examine the model and generate only the SQL needed to return the relevant fields.

Here is the SQL produced –

SELECT TOP(@__p_0) [p].[Code], [p].[Name], [p].[ProductId]
FROM [Products] AS [p]
ORDER BY [p].[ProductId]

And here is how to write the C# code that performs this time saving, energy saving work –

var productModels = _salesContext.Products.OrderBy(p => p.ProductId).Take(count).ProjectTo<ProductModel>();

To get this to work you need to do some wiring up.
First add the nuget package AutoMapper.Extensions.Microsoft.DependencyInjection v6.1.1 to your project.

Then create a mapping profile, for this scenario it is very simple. The below code maps Product to ProductModel.

public class MappingProfile : Profile
{
	public MappingProfile()
	{
		CreateMap<Product, ProductModel>();
	}
}

In starup.cs add a call to Mapper.Initialize as shown here.

public void ConfigureServices(IServiceCollection services)
{
	Mapper.Initialize(cfg => {
		cfg.AddProfile<MappingProfile>();
	});

That’s all you need, but that’s not the end of the story…

This approach works with AutoMapper.Extensions.Microsoft.DependencyInjection up to v6.1.1, and AutoMapper v8.1.1, but if you move to newer versions you need a slightly different approach because the static API has been removed.

I’ll show that in the next blog post.

Full source code available here.

Simple Func<T> and Func<T1, T2, TResult> Examples

Full source code available here.

About a month ago I wrote a post with a simple explanation of how to use methods that take Action or Action<T> as parameters. Actions themselves take 0 to 16 parameters, and return nothing.

This is a follow up with some examples of how to call methods that take Func<TResult> or Func<T1, T2, TResult> as parameters. Funcs return a value and take 0 to 16 parameters.

As with Actions, there are a variety of ways to call methods that take Funcs.

Example 1

It’s not easy to come up with a non contrived examples for this post, and especially difficult for Func, a function that takes no parameters and returns something.

public static string DateCalculator(Func<double> daysSinceSomeDate)
{
   return $"{daysSinceSomeDate()} days in your date calculation";
}

This method expects a Func<double>, that is, a Func<TResult> that returns a double and takes no parameters.

Here are two examples calling this –

Console.WriteLine(DateCalculator(() => Math.Round(DateTime.Now.Subtract(new DateTime(2000,1,1)).TotalDays, 2)));
	
Console.WriteLine(DateCalculator(() => Math.Round(new DateTime(2020,12,31).Subtract(DateTime.Now).TotalDays , 2)));

The calls pass in a Func that performs a calculation with date subtraction, not very useful, as I said, it’s not easy to come up with a simple and useful example of using a Func<TResult>.

Example 2

This method expects a Func<int, int, int>, that is, a Func<T1, T2, TResult>. It takes two ints and returns an int.

public static void SomeMath(Func<int, int, int> mathFunction)
{
	int result = mathFunction(7, 5);
	Console.WriteLine($"The mathFunction returned {result}");
}

There are a variety of ways to call this. Here are some examples.

Create a method that takes two ints and returns an int.

public static int Subtraction(int number1, int number2)
{
	return number1 - number2;
}

You can now call SomeMath(..) like this –

SomeMath(Subtraction);

Or this –

Func<int, int, int> myFuncAsMethod = Subtraction;
SomeMath(myFuncAsMethod);

I like showing how you to pass a method into a method that takes a Func, because if you are ever struggling writing a lambda to satisfy a Func parameter, stop, and write a simple method instead to work out what you need to do. It can be easier to read, and is usually easier to debug.

Speaking of lambdas, here’s how to call the SomeMath(..) method with lambdas.

This is the simplest approach –

SomeMath((a, b) => a + b);

This lambda will take two parameters, named a and b, then performs addition on them. You can think of (a, b) as parameters to the “method body” to the right of =>. That “method body” adds a and b. There is also an implied “return” statement, but it can be left out of single line lambdas like that. For multi-line lambdas it is necessary.

Here is such an example –

SomeMath((x, y) =>
{
	int temp = x * y; // you can make your lambdas multi-line
	int calculatedNumber = temp * temp; 
	return calculatedNumber;
});

You can also assign a lambda to a declared Func, but I don’t see that done very often. You might do this if you are going to use the same lambda logic in multiple places.

Func<int, int, int> myFuncAsLambda = (a, b) => a * b;
SomeMath(myFuncAsLambda);

There are other ways to satisfy the method SomeMath(..), but these should be enough for most scenarios you will come across.

Full source code available here.

Streaming Results from Entity Framework Core and Web API Core – Part 2

Full source code available here.

Some time ago I wrote a post showing how to stream results from Entity Framework over Web API. This approach a few benefits – the results would not be materialized in the API code, a small amount of memory would be used irrespective of the size of the data returned, the results would being steaming as soon as possible and the speed of the request was faster than doing something like .ToList() or .ToListAsync().

In the example code I directly accessed the DbContext from the API controller, a reader of the blog got in touch to ask the database access code could be kept away from the API controller. This blog post shows a simple way of achieving this.

In the related post I had a method like this –

[HttpGet("streaming/")]
public IActionResult GetStreaming()
{
    IQueryable<Product> products = _salesContext.Products.AsNoTracking();
    return Ok(products);
}

The _salesContext was passed into the constructor of the ProductsController class using dependency injection and access directly.

In this post I’m going to pass a ProductsService into the ProductsController and use it to make the request to the database.

Here is how the controller now looks –

    [Route("api/[controller]")]
    [ApiController]
    public class ProductsController : ControllerBase
    {
        private readonly ProductsService _productsService;

        public ProductsController(ProductsService productsService)
        {
            _productsService = productsService;
        }

        [HttpGet("streamingFromService/{count}")]
        public ActionResult GetStreamingFromService(int count)
        {
            return Ok(_productsService.GetStreaming(count));
        }

        [HttpGet("streamingFromServiceWithProjection/{count}")]
        public ActionResult GetStreamingFromServiceWithProjection(int count)
        {
            return Ok(_productsService.GetStreamingWithProjection(count));
        }

        [HttpGet("nonStreamingFromService/{count}")]
        public async Task<ActionResult> GetNonStreamingFromService(int count)
        {
            return Ok(await _productsService.GetNonStreaming(count));
        }
    }

The ProductService is very simple, it makes the relevant calls to the database and returns the Products or ProductModels. For good measure I’ve added Automapper to project the Product to ProductModel, separating the underlying data type from the one presented to the caller.

    public class ProductsService
    {
        private readonly SalesContext _salesContext;
        private readonly IMapper _mapper;

        public ProductsService(SalesContext salesContext, IMapper mapper)
        {
            _salesContext = salesContext;
            _mapper = mapper;
        }

        public IQueryable<Product> GetStreaming(int count)
        {
            IQueryable<Product> products = _salesContext.Products.OrderBy(o => o.ProductId).Take(count).AsNoTracking();
            return products;
        }

        public IQueryable<ProductModel> GetStreamingWithProjection(int count)
        {
            var productModels = _mapper.ProjectTo<ProductModel>(_salesContext.Products.OrderBy(o => o.ProductId).Take(count).AsNoTracking());
            return productModels;
        }

        public async Task<IList<Product>> GetNonStreaming(int count)
        {
            IList<Product> products = await _salesContext.Products.OrderBy(o => o.ProductId).Take(count).ToListAsync();
            return products;
        }
    }

Now, some people might be concerned about returning an IQueryable, if you are see this post by Mark Seemann https://blog.ploeh.dk/2012/03/26/IQueryableTisTightCoupling/ to start digging into the topic.

While writing this I came across what seems like a severe performance bug in Entity Framework Core 3.x, I’m working on another post which will cover this is detail.

Full source code available here.

Simple Action and Action<string> Examples

Full source code available here.

A junior engineer colleague of mine recently asked me “how the f*** do I call this method?”. Seemed like a reasonable question. It was a method that took a complicated Func, and an Action with a series of parameters. We broke it down into each part and I explained them to him separately.

I think this is a problem that more people have so I going to try to give some simple examples that hopefully you can learn from and build on yourself.

This post will talk only about Actions, a later post will talk about Funcs. I’m not going to go into the details of delegates, anonymous methods, how they get called, when they get executed, etc, there are plenty of blog posts and articles out there on those topics already. Instead I’m going to focus on a few examples and explain them.

Example 1

This method takes an Action as a parameter, the Action itself has no parameters.

public static void Printer(Action myPrinterAction)
{
    myPrinterAction();
}

How do we call this? Here are few examples.
Create a second method –

public static void PrinterMethod()
{
    Console.WriteLine("Hello from PrinterMethod");
}

Now you can call Printer(..) like this –

Printer(PrinterMethod);

Or this

Printer(() => PrinterMethod());

To short cut this, you can use a lambda as the parameter to Printer(..). The lambda must take no arguments and return nothing, because that is how the Action the Printer method task is defined.

Printer(() => Console.WriteLine("Hello from Console.WriteLine"));

Generally, you will want to go for the lambda approach as it many people consider it the easiest to read.

There are more ways of calling Printer(..).
You can explicitly define an Action and assign the PrinterMethod to it.

Action printerActionAsMethod = PrinterMethod;
Printer(printerActionAsMethod);

You can explicitly define an Action and assign lambda to it.

Action printerActionAsLambda = () => Console.WriteLine("Explicitly defined printer action");
Printer(printerActionAsLambda);

There are other ways, but I think these are going to cover most of your needs.

Example 2

In this example the Printer method takes an Action as a parameter, this Action method takes a string as a parameter. The Printer(..) method also takes a string, this string is what the Action method will print.

public static void Printer(Action<string> myPrinterAction, string value)
{
    myPrinterAction(value);
}

How we call this is a little different than above. Here are few examples.
Create a second method –

public static void PrinterMethod(string text)
{
    Console.WriteLine($"Hello from PrinterMethod - {text}");
}

Then use it like this –

Printer(PrinterMethod, "Hi there");

Or this –

Printer((text) => PrinterMethod(text), "Calling print method");

Again, a lambda is probably the better approach. The lambda takes a single string as the argument and returns nothing, because that is how the Action the Printer(..) method task is defined.

Printer(someMessage => Console.WriteLine($"Hello from Console.WriteLine - {someMessage}"), "this is a lambda");

The included source code shows a few other ways of making the calls, but I think the ones discussed here will cover most of the scenarios you will hit.

Full source code available here.

Fluent Validation in ASP.NET Core 3.1

Full source code available here.

This is an update to a post I wrote in 2017 talking about Fluent Validation in ASP.NET Core.

The example is the same but there has been few updates. The first is how you setup the FluentValidation in Startup.cs, and the second is that you don’t need a ActionFilterAttribute anymore. I have included an example of how to call the action method using Fiddler.

Step 1

Firstly add the package to your Web Api project.
Install-Package FluentValidation.AspNetCore.
If you are going to keep your validators in the Web Api project that is all you need to add.

But if you put the validators in a different project you need to add the FluentValidation package to that project
Install-Package FluentValidation.

Step 2

In startup.cs add –

public void ConfigureServices(IServiceCollection services)
{
     services.AddControllers()
        .AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining<PersonModelValidator>());}
Step 3

Add a model and a validator.

public class PersonModel
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

public class PersonModelValidator : AbstractValidator<PersonModel>
{
    public PersonModelValidator()
    {
        RuleFor(p => p.FirstName).NotEmpty();
        RuleFor(p => p.LastName).Length(5);
    }
}

Example Usage

In Fiddler, Postman or any other tool you POST to localhost:5000/person with the following header –

Content-Type: application/json

And body –

{
    "firstName": "",
    "lastName": "This is too long"
}

Here is how the request looks in Fiddler –

The response will look like this –

{
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    "title": "One or more validation errors occurred.",
    "status": 400,
    "traceId": "|84df05e2-41e0d4841bb61293.",
    "errors": {
        "LastName": [
            "'Last Name' must be 5 characters in length. You entered 16 characters."
        ],
        "FirstName": [
            "'First Name' must not be empty."
        ]
    }
}

Easy to read that there are two errors.

Full source code available here.

The terminal shell path “dotnet” is a directory – Visual Studio Code

I have been using Visual Studio Code in Ubuntu Linux for a while, but it was not an easy process to get it working the first time.

I just got around to installing .NET Core 3.1 and hit a familiar problem –

The terminal shell path "dotnet" is a directory

This happens when I try to build inside VS Code using a build task, I point this out because building from a terminal inside VS Code works fine.

Here is the output –

> Executing task: dotnet build /home/bryan/dev/blog/SomeProject/SomeProject.csproj /property:GenerateFullPaths=true /consoleloggerparameters:NoSummary <

The terminal shell path "dotnet" is a directory

Terminal will be reused by tasks, press any key to close it.

If you google this you will find some suggested fixes but they didn’t work for me.

Here is what does work for me –

1. Install the SDKs to a directory NOT named dotnet, my SDK’s are in $HOME/msdotnet
2. Update your PATH to include this directory – PATH=$PATH:$HOME/msdotnet
3. Update your DOTNET_ROOT to this – DOTNET_ROOT=$HOME/msdotnet

That’s what works for me.

Whenever I need to install a new SDK I have do follow the manual steps outlined
here, making sure to unpack the SDK to $HOME/msdotnet.

Hope this works for you, please leave a comment if it does or even if it doesn’t.