SkillAgentSearch skills...

RunStartupMethodsSequentially

A .NET library that runs methods within a locked state on startup. This is useful if you want to migrate or seed a database on an web application that has multiple instances.

Install / Use

/learn @JonPSmith/RunStartupMethodsSequentially
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

RunStartupMethodsSequentially

This RunStartupMethodsSequentially .NET library (shortened to RunSequentially) to as is designed to updates to single resources, e.g. a database, on startup of an application that has multiple instances running (e.g. a ASP.NET Core web site with Azure Scale Out is turned on). This library runs when the application starts up and run your startup services. Typical startup services are: migrating to a database, seeding a database and/or ensuring that an admin user has been added. It can also be used with non-database resources as Azure blob, common files, etc.

Scale Out: A scale out operation is the equivalent of creating multiple copies of your web site and adding a load balancer to distribute the demand between them (taken from Microsoft Azure docs).

This open-source library available on NuGet as Net.RunMethodsSequentially. The documentation can be found in the README file, and the article How to safely apply an EF Core migrate on ASP.NET Core startup and see the Release notes for details of changes.

How it works, and what to watch out for

This library allows you create services known as startup services which are run sequentially on startup within a DistributedLock global lock. This global lock stops the problem of multiple instances of the application trying to update one common resource at the same time. For instance EF Core's Migrate feature doesn't work if multiple migrations are applied at the same time.

But be aware every startup service will be run on every application's instances, for example if your application is running four instances then your startup service will be run four times. This means your startup services should check if the database has already been updated, e.g. if your service adds an admin user to the the authentication database it should first check that that admin user isn't already been added (NOTE: EF Core's Migrate method checks if the database needs to be updated, which stops your database being migrated multiple times).

Secondly, you should test your use of this library because in ASP.NET Core it is run as a hosted service and if there is an exception the application won't give you any feedback on what went wrong. I have added a class called RegisterRunMethodsSequentiallyTester which makes it easier to test your use of the the RunSequentially library - see the first test in the TestRegisterRunMethodsSequentiallyTester xUnit Test.

Another warning is that applying a database migration is that the migration must not contain what are known as breaking changes - that is a change that will cause a the currently running application from working (see the five-stage app update in this article for more info). This issue isn't specific to this library, but applies whenever you are updating an application without any break in the service, known as 24/7 or continuous application.

What you have to do to use this library?

  1. Add the Net.RunMethodsSequentially NuGet package to your main application.
  2. Create your startup services that will update a common resource, e.g. Migrating a database. Each startup service must implement the RunMethodsSequentially's IStartupServiceToRunSequentially interface.
  3. Register the RegisterRunMethodsSequentially extension method to your dependency injection provider, and select:
    • What global resource(s) you want to lock on.
    • Register your startup service(s) you want run on startup.

If you are working with ASP.NET Core, then that is all you need to do. If you are working with another approach, like a Generic Host you have to manually call the IGetLockAndThenRunServices service (see RegisterAsHostedService in the Extra options section).

1. Add NuGet Net.RunMethodsSequentially to your app

Simply add the Net.RunMethodsSequentially NuGet package to your application. This NuGet library is needed in the project holding your startup services, and the project containing the an ASP.NET Core Program class for registering the library.

2. Creating your startup services

To create a startup service that will be run while in a lock you need to create a class that inherits the IStartupServiceToRunSequentially interface. This has a method defined has a ValueTask ApplyYourChangeAsync(IServiceProvider scopedServices). This method is where you put your code to update a shared resource, such as a database.

Here is an example of a startup service, in this case its a EF Core Migrate of a database.

public class MigrateDbContextService : 
    IStartupServiceToRunSequentially
{
    public int OrderNum { get; }

    public async ValueTask ApplyYourChangeAsync(
        IServiceProvider scopedServices)
    {
        var context = scopedServices
            .GetRequiredService<TestDbContext>();

        //NOTE: The Migrate method will only update 
        //the database if there any new migrations to add
        await context.Database.MigrateAsync();
    }
}

The ApplyYourChangeAsync is given a scoped service provider, which means its copy of the normal services used in the main application. From this you can get the services you need to apply your changes to the shared resource, in this case a database.

NOTE: You can use the normal constructor DI injection, but as a scoped service provider is already available you can use that instead.

The OrderNum is a way to define the order you want your startup services are run. If services have the same OrderNum value, like zero, they will be run in the order they were registered (see next section).

I want to emphasize that your startup services are going to be run multiple times, once for each instance of your application. Therefore your startup service must check if the update it wants to do hasn't already been applies. EF Core's MigrateAsync automatically checks if the database needs to be updated, but if you are seeding a database then you check that the seeded hasn't already been done.

Here is a example of adding a admin user to the individual account authentication database, which checks if the admin user hasn't already been added - see test at the bottom of the code.

public class IndividualAccountsAddSuperUser : IStartupServiceToRunSequentially
{
    public int OrderNum { get; }

    public async ValueTask ApplyYourChangeAsync(IServiceProvider scopedServices)
    {
        var context = scopedServices
            .GetRequiredService<UserManager<TIdentityUser>>();
        var config = scopedServices
            .GetRequiredService<IConfiguration>();
        var superSection = config.GetSection("SuperAdmin");

        var user = await userManager
            .FindByEmailAsync(superSection["Email"]);

        if (user != null)
            return; //ALREADY SO DON'T TRY TO ADD AGAIN

        //... code to add a new user left out
    }
}

3. Register RunMethodsSequentially your application

A typical configuration setup for the RunMethodsSequentially feature depends on what your application is:

For ASP.NET Core

You most likely want to get the database connection string held in the appsettings.json file using the IConfiguration service and an known folder, either the IWebHostEnvironment's ContentRootPath or WebRootPath. The code below shows how to do this in the net6.0 Program class

using RunMethodsSequentially;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration
    .GetConnectionString("DefaultConnection");
var lockFolder = builder.Environment.WebRootPath;

builder.Services.RegisterRunMethodsSequentially(options =>
    {
        options.AddSqlServerLockAndRunMethods(connectionString));
        options.AddFileSystemLockAndRunMethods(lockFolder);
    })
    .RegisterServiceToRunInJob<MigrateDatabaseOnStartup>()
    .RegisterServiceToRunInJob<SeedDatabaseOnStartup>();

//... other setup code left out

NOTE: The RunStartupMethodsSequentially repo contains a ASP.NET Core application called WebSiteRunSequentially that I used to check that it worked on Azure with scale out. The Program class.

For non-ASP.NET Core applications

For applications where you don't have access to the you will need to rely on a constant for your connectionString and static methods such as AppContext.BaseDirectory or Environment.CurrentDirectory to get a folder.

Also, you need to set the RegisterAsHostedService, which will register as a IGetLockAndThenRunServices transi

View on GitHub
GitHub Stars33
CategoryData
Updated6mo ago
Forks7

Languages

C#

Security Score

87/100

Audited on Sep 13, 2025

No findings