Category Archives: .Net Core

Feature Flags in Asp.Net Core – Advanced Features – Targeting a Specific Audience using Feature Filters

In this post, I introduced the concept, and Microsoft’s implementation, of Feature Flags. In fact, there’s a lot more to both than I covered in this initial post. In this post, I want to cover how you could use this to turn a particular feature on for a single user, or a group of users. You can even specify groups for those users, and allow all, or some of those users to see the feature.

It’s worth noting that, at the time of writing, this functionality is currently only in preview; you’ll need the following NuGet package (or later) for it to work:

<PackageReference Include="Microsoft.FeatureManagement.AspNetCore" Version="2.2.0-preview" />

Before we get into how to use this, let’s have a quick look at the source. The interesting method is EvaluateAsync. Essentially this method returns a boolean indicating whether or not the feature is available; you could simply return true and the feature would always be enabled; but let’s see an abridged version of this method:

        public Task<bool> EvaluateAsync(FeatureFilterEvaluationContext context, ITargetingContext targetingContext)
        {
            . . .

            //
            // Check if the user is being targeted directly
            if (targetingContext.UserId != null &&
                settings.Audience.Users != null &&
                settings.Audience.Users.Any(user => targetingContext.UserId.Equals(user, ComparisonType)))
            {
                return Task.FromResult(true);
            }

            //
            // Check if the user is in a group that is being targeted
            if (targetingContext.Groups != null &&
                settings.Audience.Groups != null)
            {
                foreach (string group in targetingContext.Groups)
                {
                    GroupRollout groupRollout = settings.Audience.Groups.FirstOrDefault(g => g.Name.Equals(group, ComparisonType));

                    if (groupRollout != null)
                    {
                        string audienceContextId = $"{targetingContext.UserId}\n{context.FeatureName}\n{group}";

                        if (IsTargeted(audienceContextId, groupRollout.RolloutPercentage))
                        {
                            return Task.FromResult(true);
                        }
                    }
                }
            }

            //
            // Check if the user is being targeted by a default rollout percentage
            string defaultContextId = $"{targetingContext.UserId}\n{context.FeatureName}";

            return Task.FromResult(IsTargeted(defaultContextId, settings.Audience.DefaultRolloutPercentage));
        }

So, the process is that it looks for specific users and, if it finds them, they can see the feature; if it cannot find them then it looks through the groups (we’ll come back to IsTargeted later), and finally reverts to the DefaultRolloutPercentage – again, we’ll look into what that is later on.

Let’s start with a single user

If you have a look at the previous post, you’ll see that the Feature Management system is being added using the following syntax:

services.AddFeatureManagement();

In order to add one of the pre-defined filters, we’ll need to add to this like so:

            services.AddFeatureManagement()
                .AddFeatureFilter<TargetingFilter>();

You’ll also need the following class importing:

using Microsoft.FeatureManagement.FeatureFilters;

If you run this now, you’ll get the following error:

System.InvalidOperationException: ‘Unable to resolve service for type ‘Microsoft.FeatureManagement.FeatureFilters.ITargetingContextAccessor’ while attempting to activate ‘Microsoft.FeatureManagement.FeatureFilters.TargetingFilter’.’

The reason being that you need to identify a ITargetingContextAccessor. What exactly this looks like is up to the implementer, but you’ll find an example implementation here.

We’ll come back to this shortly in the groups section.

Let’s now have a look at what our appsettings.json might look like:

  "FeatureManagement": {
    "MyFeature": {
      "EnabledFor": [
        {
          "Name": "Targeting",
          "Parameters": {
            "Audience": {
              "Users": [
                "[email protected]"
              ]
            }
          }
        }
      ]      
    }

If we have a look at the default HttpContextTargetingContextAccessor (see the link above for the ITargetingContextAccessor), we’ll see that the UserId is being set there:

            TargetingContext targetingContext = new TargetingContext
            {
                UserId = user.Identity.Name,
                Groups = groups
            };

This isn’t particularly controversial – at least the User Id part isn’t; however, it doesn’t have to be this; for example, you could get the family_name claim, and return that – and then you could target your feature to anyone named Smith. It’s a bit of a silly example, but the point is that you can customise how this works (in fact, you can write a completely custom filter, which I’ll probably cover in a later post).

This part of the Feature Management is not to be underestimated: you could release a feature, in live, to only one or two Beta Testers. However, it is quite specific; that’s where Groups come in.

Groups

Groups are slightly more interesting. You can specify which groups are in and out using the following configuration:

  "FeatureManagement": {

    "MyFeature": {
      "EnabledFor": [
        {
          "Name": "Targeting",
          "Parameters": { 
            "Audience": {
              "Users": [],
              "Groups": [
                {
                  "Name": "Group1",
                  "RolloutPercentage": 80
                },
                {
                  "Name": "Group2",
                  "RolloutPercentage": 40
                }
              ],
              "DefaultRolloutPercentage": 20
            }
          }
        }
      ]
    },

The RolloutPercentage here indicates what proportion of the group that you wish to be included.

Do you remember the IsTargeted method from earlier? This is what it looks like:

        private bool IsTargeted(string contextId, double percentage)
        {
            byte[] hash;

            using (HashAlgorithm hashAlgorithm = SHA256.Create())
            {
                hash = hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes(contextId));
            }

            //
            // Use first 4 bytes for percentage calculation
            // Cryptographic hashing algorithms ensure adequate entropy across hash
            uint contextMarker = BitConverter.ToUInt32(hash, 0);

            double contextPercentage = (contextMarker / (double)uint.MaxValue) * 100;

            return contextPercentage < percentage;
        }

To an extent, this is a random selection, but it uses the User ID and feature name to calculate the hash for that selection (that’s what gets passed into contextId), meaning that the same user will see the same thing each time. You may also find when playing with this that for small numbers, it doesn’t really match the expectation; for example, at 40%, you would expect around two out of five users to see the feature, but when I ran my test, all the five users could see the feature. Larger numbers work better, although the fact that this is tied to the User Id makes it a little tricky to test (you can’t simply launch the site and press Ctrl-F5 until it switches over).

Again, it’s worth pointing out that what a group is, is determined by you (or at least the creator of the HttpContextTargetingContextAccessor). This means that you can base this on a claim, on the first letter of the username, the time of day, anything you like. I haven’t tried it, but I suspect you could put a DB query in here, too. That’s probably not the best idea, because it gets called a lot, but I believe it’s possible.

Default Rollout Percentage

Here we have a catch-all – if the user is not in the group, and not identified as a user, this will allow you to expose your feature to a percentage of the user base. Again, this isn’t something you can easily check by refreshing your page, as it’s based on a hash of the user, group, and feature name. In fact, this won’t work very well at all if you’re not using any kind of identiy.

References

http://dontcodetired.com/blog/post/Using-the-Microsoft-Feature-Toggle-Library-in-ASPNET-Core-(MicrosoftFeatureManagement)

https://github.com/microsoft/FeatureManagement-Dotnet

Calling a Web API from a Console App and Creating a Performance Test

While working on a proof of concept, I needed to create a dummy API, and then run a stress test on it. Whilst the activity itself may seem pointless (creating a templated API and stress testing it), I felt the process might be worth documenting.

Create the API

We’ll start with creating the API:

dotnet new webapi

If you just run this, you should see the following:

The next step is to call that from a console app.

Create a console app to call the API

Add a new console app project to your solution, and replace the code in Program.cs with the following:

    class Program
    {
        static HttpClient client = new HttpClient();
        static string path = "https://localhost:44356/weatherforecast";


        static async Task Main(string[] args)
        {
            Console.WriteLine("Press enter to start test");
            Console.ReadLine();
            string? data = await CallWeatherForecast();

            Console.WriteLine(data ?? "No data returned");
        }

        private static async Task<string?> CallWeatherForecast()
        {            
            HttpResponseMessage response = await client.GetAsync(path);
            if (response.IsSuccessStatusCode)
            {
                string data = await response.Content.ReadAsStringAsync();
                return data;
            }

            return null;
        }
    }

It’s worth noting that I switched on nullable reference types here (in the csproj file):

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <Nullable>enable</Nullable>
  </PropertyGroup>

To test this, you’ll need to start both the projects (either select Set Startup Projects… from the solution context menu, or run the API, and then right-click the console app and select Debug -> Start New Instance).

Once you’ve checked that this works, build the solution in release mode (remember, we’re going to be running a stress test, so debug mode will show skewed results).

Call the console app from JMeter

For the stress test, I’m going to use Jmeter. You’ll need to download it from here.

I won’t go into too much detail about how to set this up, but briefly, extract the downloaded zip somewhere, and run the jmeter.bat file. You should be presented with the following screen:

Add a thread group, so we can simulate multiple users:

Then add an OS Process Sampler to run the console app:

Remember to run the API first, then click the green play arrow. You’ll see the users ramp up:

We don’t have any listeners, so the results are, unfortunately lost. Let’s add a couple:

As you can see, we now have some information. How long these calls are taking on average, the error count, etc. The throughput we’re getting is around 3/second… In fact, running a stress test locally on the same machine, it’s difficult to break it, because as the resources get used up, the JMeter process itself suffers, too. This is a good reason to run JMeter from a VM in the cloud.

Whilst it’s quite difficult to kill the service, I certainly managed to slow it down considerably:

These figures are in milliseconds, which means that 90% of calls are taking over 2 minutes. This entire test took around 15 minutes, and around 10 requests per second was about the best it got to (I ran 10 loops of 1000 concurrent users).

There’s a few things you can do to identify where the performance starts to degrade, but I’m more interested in what happens to these figures if I add DB access.

Note: when you’re playing with this, the reports don’t automatically clear each run, so you have to select each, right-click and clear.

Add calls to a DB

Let’s add Entity Framework to our project.

We can then change the controller to allow adding new records and retrieval by date:

        public WeatherForecastController(
            ILogger<WeatherForecastController> logger,
            StressTestDbContext stressTestDbContext)
        {
            _logger = logger;
            _stressTestDbContext = stressTestDbContext;
        }

        [HttpGet]
        public IEnumerable<WeatherForecast> GetNew()
        {
            var rng = new Random();
            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            })
            .ToArray();
        }
        
        [HttpGet("/[controller]/GetByDate/{dateTime}")]
        public IEnumerable<WeatherForecast> GetByDate(DateTime dateTime)
        {
            var forecasts = _stressTestDbContext.Forecast.Where(a => a.Date.Date == dateTime.Date);
            return forecasts;
        }

        [HttpPost]
        public IActionResult Post(WeatherForecast weatherForecast)
        {
            DailyForecast forecast = new DailyForecast()
            {
                Date = weatherForecast.Date,
                Summary = weatherForecast.Summary,
                TemperatureC = weatherForecast.TemperatureC
            };

            _stressTestDbContext.Add(forecast);
            if (_stressTestDbContext.SaveChanges() != 0)
            {
                return Ok();
            }
            return BadRequest();
        }

Finally, we can change the main program to call those functions:

        static async Task Main(string[] args)
        {
            Console.WriteLine("Press enter to start test");
            Console.ReadLine();

            ConsoleTitle("CallGetNewWeatherForecast");
            string? dataGet = await CallGetNewWeatherForecast();
            Console.WriteLine(dataGet ?? "No data returned");

            ConsoleTitle("AddForecast");
            string? dataAdd = await AddForecast(_rnd.Next(30),
                DateTime.Now.AddDays(_rnd.Next(10)), _summaries[_rnd.Next(_summaries.Length)]);
            Console.WriteLine(dataAdd ?? "No data returned");

            ConsoleTitle("CallGetWeatherForecast");
            string? dataGetDate = await CallGetWeatherForecast(DateTime.Now.AddDays(_rnd.Next(10)));
            Console.WriteLine(dataGetDate ?? "No data returned");
        }

        private static void ConsoleTitle(string title)
        {
            Console.ForegroundColor = ConsoleColor.Blue;
            Console.WriteLine(title);
            Console.ResetColor();
        }

        private static async Task<string?> CallGetNewWeatherForecast()
        {            
            HttpResponseMessage response = await client.GetAsync($"{path}");
            if (response.IsSuccessStatusCode)
            {
                string data = await response.Content.ReadAsStringAsync();
                return data;
            }

            return null;
        }

        private static async Task<string?> CallGetWeatherForecast(DateTime dateTime)
        {
            string dateString = dateTime.ToString("yyyy-MM-dd");

            HttpResponseMessage response = await client.GetAsync($"{path}/GetByDate/{dateString}");
            if (response.IsSuccessStatusCode)
            {
                string data = await response.Content.ReadAsStringAsync();
                return data;
            }

            return null;
        }

        private static async Task<string> AddForecast(int temperature, DateTime date, string summary)
        {
            var forecast = new WeatherForecast()
            {
                TemperatureC = temperature,
                Date = date,
                Summary = summary
            };

            HttpResponseMessage response = await client.PostAsJsonAsync($"{path}", forecast);
            if (response.IsSuccessStatusCode)
            {
                string data = await response.Content.ReadAsStringAsync();
                return data;
            }

            return null;
        }

To get a sensible reading, you’ll need to do this from an empty database:

For the first run, we’ll do 500 users, and 3 iterations:

The output from the first run is:

And let’s just check that that’s created the records we expected:

EF async calls vs non-async

To satisfy a curiosity that I’ve had for a while, I’m now going to change the update API method to async:

        [HttpPost]
        public async Task<IActionResult> Post(WeatherForecast weatherForecast)
        {
            DailyForecast forecast = new DailyForecast()
            {
                Date = weatherForecast.Date,
                Summary = weatherForecast.Summary,
                TemperatureC = weatherForecast.TemperatureC
            };

            _stressTestDbContext.Add(forecast);
            if (await _stressTestDbContext.SaveChangesAsync() != 0)
            {
                return Ok();
            }
            return BadRequest();
        }

Again, 1500 records were created:

Here’s the report:

What an interesting result. Making the update async seems to have slightly reduced the throughput. This is running locally, and I only have a 4 core machine, but I would have expected throughput to slightly increase here, rather than decrease.

Debugging an Asp.Net Core React Application in Azure

I’ve recently been working with an Asp.Net Core ReactJS application. When trying to debug this remotely, I switched on Development mode in order to get a stack trace when it crashed:

Instead of the stack trace, I got this:

An unhandled exception occurred while processing the request. AggregateException: One or more errors occurred. (One or more errors occurred. (The NPM script ‘start’ exited without indicating that the create-react-app server was listening for requests. The error output was: )) System.Threading.Tasks.Task.ThrowIfExceptional(bool includeTaskCanceledExceptions)
InvalidOperationException: The NPM script ‘start’ exited without indicating that the create-react-app server was listening for requests. The error output was:

This is, in fact, caused by the following code:

            app.UseSpa(spa =>
            {
                spa.Options.SourcePath = "ClientApp";

                if (_env.IsDevelopment())
                {
                    spa.UseReactDevelopmentServer(npmScript: "start");
                }
            });

This uses the setting “Development” to determine whether to start a local React server; which will fail on a remote server. However, I want to see a stack trace, which is here:

            if (_env.IsDevelopment())            
            {
                app.UseDeveloperExceptionPage();
            }

The problem here is that “Development” has two functions – it displays a stack trace, and it manages all these variables that should only run on your machine. What we need are two settings that both mean “Development”; one that means that we’re running locally, and one that we’re trying to debug. Start with an environment variable:

You can set this to anything you choose… But I’ve gone with “LocalDevelopment”.

The next step is to find all the places that check IsDevelopment, and replace them. What we essentially want is this:

                //if (_env.IsDevelopment())
                if (_env.IsEnvironment("LocalDevelopment"))
                {

However, we can create our own extension method, so that the code looks a lot neater:

        public static bool IsLocalDevelopment(this IWebHostEnvironment env)
        {
            return (env.IsEnvironment("LocalDevelopment"));
        }

Remember that IsEnvironment() is actually an extension method itself, so you would need to include:

using Microsoft.Extensions.Hosting;

In your extension class.

What to change

The following places will, at a minimum, need replacing for a standard web app. The stack trace should be displayed in either situation:

        public void Configure(IApplicationBuilder app)
        {
            if (_env.IsDevelopment() || _env.IsLocalDevelopment())                  
            {
                app.UseDeveloperExceptionPage();
            }

The React check that started all this:

            app.UseSpa(spa =>
            {
                spa.Options.SourcePath = "ClientApp";

                if (_env.IsLocalDevelopment())
                {
                    spa.UseReactDevelopmentServer(npmScript: "start");
                }
            });

Also, if you’re using local secrets, you’ll need this in Program.cs:

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder
                        .ConfigureAppConfiguration((hostingContext, config) =>
                        {
                            if (hostingContext.HostingEnvironment.IsEnvironment("LocalDevelopment"))
                            {
                                config.AddUserSecrets<Program>();
                            }

Because, by default, local secrets are only added for Development only.

Summary

That’s it, you can now set the ASPNETCORE_ENVIRONMENT to Development on a remote server, and you should get a stack trace.

Add Storage Queue Message

I’ve written quite extensively in the past about Azure, and Azure Storage. I recently needed to add a message to an Azure storage queue, and realised that I had never written a post about that, specifically. As with many Azure focused .Net activities, it’s not too complex; but I do like to have my own notes on things.

If you’ve arrived at this post, you may find it’s very similar to the Microsoft documentation.

How to add a message

The first step is to install a couple of NuGet packages:

Install-Package Microsoft.Azure.Storage.Common
Install-Package Microsoft.Azure.Storage.Queue

My preference for these kinds of things is to create a helper: largely so that I can mock it out for testing; however, even if you fundamentally object to the concept of testing, you may find such a class helpful, as it keeps all your code in one place.

 public class StorageQueueHelper
{
        private readonly string _connectionString;
        private readonly string _queueName;

        public StorageQueueHelper(string connectionString, string queueName)
        {
            _connectionString = connectionString;
            _queueName = queueName;
        }

        public async Task AddNewMessage(string messageBody)
        {
            var queue = await GetQueue();

            CloudQueueMessage message = new CloudQueueMessage(messageBody);
            await queue.AddMessageAsync(message);
        }

        private async Task<CloudQueue> GetQueue()
        {
            CloudStorageAccount storageAccount = CloudStorageAccount.Parse(_connectionString);
            CloudQueueClient queueClient = storageAccount.CreateCloudQueueClient();
            CloudQueue queue = queueClient.GetQueueReference(_queueName);
            await queue.CreateIfNotExistsAsync();

            return queue;
        }
}

The class above works for a single queue, and storage account. Depending on your use case, this might not be appropriate.

The GetQueue() method here is a bit naughty, as it actually changes something (or potentially changes something). Essentially, all it’s doing is connecting to a cloud storage account, and then getting a reference to a queue. We know that the queue will exist, because we’re forcing it to (CreateIfNotExistsAsync()).

Back in AddNewMessage(), once we have the queue, it’s trivial to simply create the message and add it.

References

https://docs.microsoft.com/en-us/azure/storage/queues/storage-tutorial-queues

Debugging GitHub Actions

I’ve recently been playing with GitHub actions. Having been around the block a few times, I’ve seen a fair few methods of building and deploying software, and of those, a fair few that are automated in some way. Oddly, debugging these things seems to be in the same place it was around 10 years ago: you see an error, try to work out what caused it, fix it, and run the build / deploy, rinse and repeat.

In some respects, this process may have actually become harder to debug since the days of TFS (at least with TFS, you could connect to the server and see why the software wasn’t building).

Anyway, onto GitHub actions

I’ve been trying to set-up an automated CI/CD pipeline for a new utility that I’ve been playing with.

After I’d configured a basic build, I started getting the following error:

MSBUILD : error MSB1003: Specify a project or solution file. The current working directory does not contain a project or solution file.

So, I thought future me (and perhaps one or two other people) may like to see the process that I went through to resolve (or at least to diagnose) this.

1. Git Bash

Your build is very likely trying to run on a Linux platform. If you have a look at your build file, it will tell you (or, more accurately, you will tell it) where it’s building:

So, the first step is to load up bash and manually type in the commands that the build is executing, one at a time. Again, these are all in the build file:

Make sure that you do this from the root directory, in case your problem relates to the path.

2. Add Debug Steps

Assuming that you’ve done step one and everything is working, the next stage is to start adding some logging. I strongly suspect that someone reading this will tell me there’s an easier way to go about this (please tell me there’s an easier way to go about this!) but this is how I went about adding tracing:

steps:	
	- uses: actions/[email protected]
	- name: Setup .NET Core
	uses: actions/[email protected]
	with:
	dotnet-version: 3.1.101
	- name: where are we
	run: pwd
	- name: list some key files
	run: ls -lrt
	- name: try a different die
	run: ls -lrt websitemeta
	- name: Install dependencies
	run: dotnet restore ./websitemeta/

As you can see, I was working under the assumption that my build was failing because the directory paths were incorrect. In fact, the paths were fine:

With the logging stage, there’s two ways to look at this
1. You’re closely following the scientific method of establishing a hypothesis and testing it; or
2. You’re blindly logging as much information as you can to try and extract a clue.

You’d be surprised how quickly 1 turns into 2!

3. Remember the platform that you’re running on

Okay, so in Step 1, I stated that you should try running the build in bash; but remember that, despite the Unix like interface, you’re still on Windows. As you can see from my build file, my build is on Ubuntu. In my particular case, this held the key – in fact, my build was failing because I’d used the wrong case for the directory path.

This is also true for your tests; for example, if you’ve hard-coded a directory path like this in your test:

string path = "tmp\myfile.txt";

It will fail, because in Unix, it would be:

string path = "tmp/myfile.txt";

Feature Flags – Asp.Net Core

Feature flags enable you to create a new piece of functionality in the main code branch, but behind a mask – so the end user doesn’t see the change. This means that you can decide at a later date to turn the feature on, or off. For example, say you wanted to make a change the colour of your web-site; or maybe you wanted to introduce an “Express Checkout” button; you can create these behind a feature flag, meaning that, should sales suddenly plummet when these changes are released, you can simply flick a switch to turn them off.

In this post, I’m going to cover the most basic usage of a feature flag. We’ll create a new web-site and, where a feature flag is turned on, we’ll show a button; and where it is not, we won’t. Before we start on the specifics, create a blank Asp.Net Core MVC project.

1. Add NuGet packages

The concept of feature flags exist outside of Microsoft. In fact, Feature Flags are just the idea of enabling functionality based on a setting, so there was probably one in the first computer program ever written. In this post, we’re talking about the implementation of this concept that’s available from Microsoft for Asp.Net Core.

The first step, as with more Microsoft libraries these days, is NuGet:

Install-Package Microsoft.FeatureManagement.AspNetCore

2. Add to DI

In Startup.cs, we’ll need to add the feature into the IoC. In the `ConfigureServices` method, add the following code:

services.AddFeatureManagement();

3. Config appsettings.json

Add the following section to appsettings.json:

  "FeatureManagement": {
    "MyFeature": true
  }

All we’re doing here is building up a key value pair underneath the “FeatureManagement” header. These settings can be more complex, but all we’re doing here is saying that our feature is on.

4. Inject the Functionality

Now that we’ve registered this in the IoC (using AddFeaturemanagement) we can inject it into our controller or service; for example:

public MyService(IFeatureManager featureManager)
{
    _featureManager = featureManager;
} 

Then you can simply check whether the feature is enabled:

if (await featureManager.IsEnabledAsync("MyFeature")) { // Run the following code }

There is an intrinsic flaw with using magic strings like this – if you rename them, you could miss one. Microsoft recommend using an enum, such as this:

public enum MyFeatureFlags { FeatureA, FeatureB, FeatureC }

Which would enable you to reference it like this:

if (await featureManager.IsEnabledAsync(nameof(MyFeatureFlags.FeatureA)))

5. Access to controllers and views

You can hide an entire controller behind a flag; for example:

[FeatureGate(MyFeatureFlags.FeatureA)] 
public class HomeController : Controller 
{ ... }

You can use these on specific controller actions, too.

We did say at the start of this post that we’d display a button when a flag was on, so let’s see what that would look like in the view; after all, whilst the rest of this is useful, the most likely thing that you’ll want to do in enabling or disabling a feature is change what the user can see. The following code would only show a button if “MyFeature” is on:

<feature name="MyFeature"> <button>MyFeature Button</button> </feature>

A slight caveat here, in order to use this HTML tag, you’ll need to modify your _ViewImports.cshtml file:

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, Microsoft.FeatureManagement.AspNetCore

Summary and Notes

Seems pretty smooth to me. There are other feature flag managers out there; and, to be honest, it’s not like you couldn’t create your own in an hour or so, but this one struck me as a very neat option.

Also, because it’s from Microsoft, you can use Azure App Configuration to manage the flags; this gives you a proper UI to manage the flags. Obviously, if they’re in the appsetting.json, you have a number of other options, but if you’re using Azure, why not take advantage.

References

https://docs.microsoft.com/en-us/azure/azure-app-configuration/use-feature-flags-dotnet-core

https://docs.microsoft.com/en-us/azure/azure-app-configuration/quickstart-feature-flag-aspnet-core?tabs=core2x

https://docs.microsoft.com/en-us/dotnet/api/microsoft.featuremanagement?view=azure-dotnet-preview

Add Entity Framework Core to an Existing Asp.Net Core Project

This is one of those posts born of the fact that I’m constantly googling this, and coming up with videos and Microsoft docs – I just want a single, quick step by step guide; so here it is.

Step One – NuGet packages

If you’re using SQL Server, then you’ll need these packages (technically, you only need the first, unless you want to actually create or run a migration yourself):

Install-Package Microsoft.EntityFrameworkCore.SqlServer
Install-Package Microsoft.EntityFrameworkCore.Tools

Step Two – Create DBContext (and entity classes if needed)

The minimum DBContext looks like this:

    public class MyDbContext : DbContext
    {

        public MyDbContext(DbContextOptions<MyDbContext> options)
            : base(options)
        {

        }

        public DbSet<MyEntity> MyEntities { get; set; }
        public DbSet<MyOtherEntity> MyOtherEntities { get; set; }
    }

You can feed your model directly into EF. That’s much easier to do than it used to be, for example, any field called Id will automatically be inferred to be a Primary Key; however, at some point, you’re going to want to do something data specific to your model, and at that point, you lose the separation between data and business layers. A nice way that I’ve found around this is to subclass your models:

    public class MyEntity : MyModel
    {
    }

Step Three – Startup.cs

You’ll need to register the DbContext in your DI pipeline:

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            services.AddDbContext<MyDbContext>(a =>
                a.UseSqlServer(Configuration.GetConnectionString("SqlConnectionString")));
        }

Your connection string goes in appsettings.json:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "SqlConnectionString": "Server=(localdb)\\mssqllocaldb;Database=MyDatabase;Trusted_Connection=True;MultipleActiveResultSets=true"
  }
}

Step Four – Migration

Since you’ve installed EF Tools, you can add a migration:

Add-Migration InitialMigration -StartupProject MySolution.MyProject

The other option is to switch on automatic migrations; although, I would personally advise against that, as you can quickly lose track of what it’s doing.

That’s it – once you run:

Update-Database

You should have a working EF instance.

Step Five (optional) – Auto-Migration

You may also wish to have the system upgrade itself to the latest version of the DB. Whether you choose to do this depends on exactly how much control you need over the changes to your DB. I’ve heard that some people create the migrations as SQL Scripts and hand them to a DBA.

Should you wish to have EF manage that for you, then you can do the following:

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            UpdateDatabase(app);

. . .

private void UpdateDatabase(IApplicationBuilder app)
        {
            using var serviceScope = app.ApplicationServices
                .GetRequiredService<IServiceScopeFactory>()
                .CreateScope();

            using var context = serviceScope.ServiceProvider.GetService<CourseSelectDbContext>();

            context.Database.Migrate();            
        }

That’s it – now, next time I want to find this, I can search my own website!

References

https://docs.microsoft.com/en-us/ef/core/get-started/install/

https://docs.microsoft.com/en-us/aspnet/core/data/ef-rp/intro?view=aspnetcore-3.1&tabs=visual-studio

https://blog.rsuter.com/automatically-migrate-your-entity-framework-core-managed-database-on-asp-net-core-application-start/

My object won’t deserialise using System.Text.Json

I imagine this is a very common scenario, so I thought it worth documenting.

Let’s take the following controller method in a bog standard Asp.Net Core API:

        public async Task<DataContainer> Get()
        {
            var data = Enumerable.Range(1, 5).Select(index => new MyData
            {
                Value1 = index,
                Value2 = "some other value"
            })
            .ToList();

            return new DataContainer()
            {
                Data = data
            };
        }

Here’s a method that consumes it:

            var client = _clientFactory.CreateClient();
            var result = await client.GetAsync($"https://localhost:22222/endpoint");

            using var responseStream = await result.Content.ReadAsStreamAsync();
            var data = await JsonSerializer.DeserializeAsync<DataContainer>(responseStream);

So, you’ve expect this to work, right? It makes no difference what DataContainer looks like, because you’re serialising and deserialising using the same technology. In fact, it just returns a correctly structured object, but every value will be null.

I’ll tell you what does work, is this:

            var client = _clientFactory.CreateClient();
            var result = await client.GetAsync($"https://localhost:22222/endpoint");

            var responseString = await result.Content.ReadAsStringAsync();
            var data = Newtonsoft.Json.JsonConvert.DeserializeObject<DataContainer>(responseString);             

If you look at the JSON, it looks kosher enough (again, the exact structure is irrelevant). So, what’s the issue?

Interestingly, when the JSON is serialised, it is serialised into camelCase; so if you have a variable such as MyData, it will get serialised as myData. Newtonsoft deals with this, because it’s a common use case in .Net; however, System.Text.Json.Deserialize* assumes that the casing will match the object!

This is odd considering it changes it on the way out!!

Okay, so what’s the fix?

You just tell Deserialize* to use camelCase:

            var client = _clientFactory.CreateClient();
            var result = await client.GetAsync($"https://localhost:22222/endpoint");

            using var responseStream = await result.Content.ReadAsStreamAsync();

            var options = new JsonSerializerOptions()
            {
                PropertyNamingPolicy = JsonNamingPolicy.CamelCase
            };
            var data = await JsonSerializer.DeserializeAsync<DataContainer>(responseStream, options);

Annoying, eh?

References

https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to#table-of-differences-between-newtonsoftjson-and-systemtextjson

https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-how-to#use-camel-case-for-all-json-property-names

Using HttpClientFactory

In .Net Framework (prior to .Net Core), HttpClient was something of a pain. You essentially kept it around in a static variable. If you didn’t, and used too many of them, you could end up issues such as socket exhaustion.

I think most people got around this by implementing their own version of a HttpClientFactory… but now you don’t need to.

In this post, I’m covering the simplest possible use case inside an Asp.Net Core application. The articles linked at the bottom of this post go into much more detail.

Usage

The first step to using the HttpClientFactory is to add it to the IoC:

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();
            services.AddHttpClient();
        }

Then, simply inject it into your controller; for example:

        public HomeController(ILogger<HomeController> logger,
            IHttpClientFactory clientFactory)
        {
            _logger = logger;
            _clientFactory = clientFactory;
        }

Finally, you request a client like this:

var client = _clientFactory.CreateClient();

Then you can use it as a normal HttpClient:

var result = await client.GetAsync($"https://localhost:22222/endpoint");

References

https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-3.1

https://www.stevejgordon.co.uk/introduction-to-httpclientfactory-aspnetcore

Manually Adding DbContext for an Integration Test

In EF Core, there is an extension method that allows you to add a DBContext, called AddDBContext. This is a really useful method, however, in some cases, you may find that it doesn’t work for you. Specifically, if you’re trying to inject a DBContext to use for unit testing, it doesn’t allow you to access the DBContext that you register.

Take the following code:

services.AddDbContext<MyDbContext>(options =>
                options.UseSqlServer());
         

I’ve previously written about using UseInMemoryDatabase. However, this article covered unit tests only – that is, you are able to instantiate a version of the DBContext in the unit test, and use that.

As a reminder of the linked article, if you were to try to write a test that included that DBContext, you might want to use an in memory database; you might, therefore, build up a DBContextOptions like this:

var options = new DbContextOptionsBuilder<MyDbContext>()
                .UseInMemoryDatabase(Guid.NewGuid().ToString())
                .EnableSensitiveDataLogging()
                .Options;
var context = new MyDbContext(options);

But in a scenario where you’re writing an integration test, you may need to register this with the IoC. Unfortunately, in this case, AddDbContext can stand in your way. The alternative is that you can simply register the DbContext yourself:

var options = new DbContextOptionsBuilder<MyDbContext>()
                .UseInMemoryDatabase(Guid.NewGuid().ToString())
                .EnableSensitiveDataLogging()
                .Options;
var context = new MyDbContext(options);
AddMyData(context);
services.AddScoped<MyDbContext>(_ => context);

AddMyData just adds some data into your database; for example:

private void AddTestUsers(MyDbContext context)
{            
    MyData data = new MyData()
    {
        value1 = "test",
        value2 = "1"                
    };
    context.MyData.Add(subject);
    context.SaveChanges();
}

This allows you to register your own, in memory, DbContext in your IoC.