ASP.NET Core: versioning dependency injection in Program.cs

633 views Asked by At

I have been trying to implement versioning in my ASP.NET Core 6.0 Web API using Microsoft.AspNetCore.Mvc.Versioning.

I want to use separate v1 and v2 folders for my versions:

Controllers
 - v1
   - MyController
 - v2
   - MyController

However, going down this path I end up with different V1.0 and V2.0 folders for everything.

I end up with two separate namespaces and module names as well.

namespace MyAPI.Models.V1 
{
     public class MyModelsV1 
     {

namespace MyAPI.Models.V2 
{
     public class MyModelsV2  
     {

A problem arises in Program.cs with dependency injection. I'll use my identity set up as an example.

builder.Services.AddDefaultIdentity<AppUser>()
    .AddRoles<IdentityRole>()
    .AddClaimsPrincipalFactory<AppUserClaimsPrincipalFactory>()
    .AddDefaultTokenProviders()
    .AddEntityFrameworkStores<AppDbContext>();

Now there is AppUserV1, AppUserV2 and AppUserClaimsPrincipalFactoryV1 and AppUserClaimsPrincipalFactoryV2.

And there will be AppDbContextV1 and AppDbContextV2 as well.

So am I using a wrong approach? Do I just build two separate services for V1 and V2?

builder.Services.AddDefaultIdentity<AppUserV1>()
 ...
builder.Services.AddDefaultIdentity<AppUserV2>()
 ...

How would I implement the v1.0 and v2.0 folder approach in program.cs?

Are there any good end to end guides for this versioning approach? Or should I be doing something else?

1

There are 1 answers

3
Guru Stron On

I want to use separate v1 and v2 folders for my versions:

I would argue that there are two main possible scenarios:

  1. You have breaking changes between the APIs but internal implementation is +/- the same. In this case usually you will want both versions of API to operate over the same entities and the Web layer should perform version specific logic over them (i.e. like adapting old versions to new ones, etc.). In this case I would argue that you should better work with some kind of tiered architecture and have versioning applied only to Web and possibly business layer models/DTOs (depending on concrete case). Cross-cutting concerns, DAL and so on should not be affected.

  2. There is a complete overhaul/rewrite is happening between. I would argue that this is far more rare case and in this case your approach is fine, but I would go even a step further and possibly introduce new version of service as separate project and handle the routing on some kind of proxy.

Now there is AppUserV1, AppUserV2 and AppUserClaimsPrincipalFactoryV1 and AppUserClaimsPrincipalFactoryV2.

I would argue that this is an example of cross-cutting concern which should be shared between API versions. In some rare occasions of changes like "from an int to a string guid" I would argue that it would be better to implement two different auth flows which will use different fields as Id (i.e. you add new guid field and set it as unique index filling for old records, potentially changing PK to be the new field, though having guid as PK is considered to be questionable approach by some DBAs)