DEV Community

Roel
Roel

Posted on

How to configure API Versioning in .NET 8

Implementing API Versioning in .NET 8 with Evolving Models

What if you want to create a robust API and need to manage different versions to ensure backward compatibility, especially when models evolve over time? The default .NET application template doesn’t provide this out of the box, so here's a guide to make this process simpler.

Requirements

  • .NET 8 Web API project
  • The following packages:
<ItemGroup>
    <PackageReference Include="Asp.Versioning.Http" Version="8.0.0" />
    <PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="8.0.0" />
</ItemGroup>
Enter fullscreen mode Exit fullscreen mode

Guide

Follow these steps to implement versioning in your .NET 8 Web API project, including handling evolving models.

  1. Add the references above to the project

  2. Configure Services in Program.cs:

   var builder = WebApplication.CreateBuilder(args);

   builder.Services.AddApiVersioning(options =>
   {
       options.DefaultApiVersion = new ApiVersion(1, 0);
       options.AssumeDefaultVersionWhenUnspecified = true;
       options.ReportApiVersions = true;
       options.ApiVersionReader = ApiVersionReader.Combine(
           new UrlSegmentApiVersionReader(),
           new HeaderApiVersionReader("X-Api-Version")
       );
   }).AddApiExplorer(options =>
   {
       options.GroupNameFormat = "'v'VVV";
       options.SubstituteApiVersionInUrl = true;
   });

   builder.Services.AddControllers();
   builder.Services.AddEndpointsApiExplorer();
   builder.Services.AddSwaggerGen(c =>
   {
       c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API - V1", Version = "v1.0" });
       c.SwaggerDoc("v2", new OpenApiInfo { Title = "My API - V2", Version = "v2.0" });
   });

   var app = builder.Build();

   if (app.Environment.IsDevelopment())
   {
       app.UseSwagger();
       app.UseSwaggerUI();
   }

   app.UseHttpsRedirection();
   app.UseAuthorization();
   app.MapControllers();
   app.Run();
Enter fullscreen mode Exit fullscreen mode
  1. Implement Versioned Controllers: Create versioned controllers by decorating them with the ApiVersion attribute:
   namespace MyApp.Controllers.v1
   {
       [ApiVersion("1.0")]
       [Route("api/v{version:apiVersion}/[controller]")]
       [ApiController]
       public class WorkoutsController : ControllerBase
       {
           [MapToApiVersion("1.0")]
           [HttpGet("{id}")]
           public IActionResult GetV1(int id)
           {
               return Ok(new { Message = "This is version 1.0" });
           }
       }
   }

   namespace MyApp.Controllers.v2
   {
       [ApiVersion("2.0")]
       [Route("api/v{version:apiVersion}/[controller]")]
       [ApiController]
       public class WorkoutsController : ControllerBase
       {
           [MapToApiVersion("2.0")]
           [HttpGet("{id}")]
           public IActionResult GetV2(int id)
           {
               return Ok(new { Message = "This is version 2.0", NewField = "New data" });
           }
       }
   }
Enter fullscreen mode Exit fullscreen mode
  1. Handling Evolving Models: When models evolve, create separate model classes for each version to maintain backward compatibility.

Version 1 Model:

   public class WorkoutV1
   {
       public int Id { get; set; }
       public string Name { get; set; }
   }
Enter fullscreen mode Exit fullscreen mode

Version 2 Model with Additional Fields:

   public class WorkoutV2
   {
       public int Id { get; set; }
       public string Name { get; set; }
       public string Description { get; set; }  // New field
   }
Enter fullscreen mode Exit fullscreen mode

Update the controller methods to use the appropriate models:

   namespace MyApp.Controllers.v1
   {
       [ApiVersion("1.0")]
       [Route("api/v{version:apiVersion}/[controller]")]
       [ApiController]
       public class WorkoutsController : ControllerBase
       {
           [MapToApiVersion("1.0")]
           [HttpGet("{id}")]
           public IActionResult GetV1(int id)
           {
               var workout = new WorkoutV1 { Id = id, Name = "Workout V1" };
               return Ok(workout);
           }
       }
   }

   namespace MyApp.Controllers.v2
   {
       [ApiVersion("2.0")]
       [Route("api/v{version:apiVersion}/[controller]")]
       [ApiController]
       public class WorkoutsController : ControllerBase
       {
           [MapToApiVersion("2.0")]
           [HttpGet("{id}")]
           public IActionResult GetV2(int id)
           {
               var workout = new WorkoutV2 { Id = id, Name = "Workout V2", Description = "This is a description." };
               return Ok(workout);
           }
       }
   }
Enter fullscreen mode Exit fullscreen mode
  1. Configure Swagger Documentation: Ensure each API version has its own Swagger documentation:
   builder.Services.AddSwaggerGen(c =>
   {
       c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API - V1", Version = "v1.0" });
       c.SwaggerDoc("v2", new OpenApiInfo { Title = "My API - V2", Version = "v2.0" });
   });
Enter fullscreen mode Exit fullscreen mode
  1. Run Your Application: Build and run your application to see the versioned API in action:
   dotnet run
Enter fullscreen mode Exit fullscreen mode
  1. Access Different API Versions: Use the URL to access different versions of your API:
    • Version 1.0: https://localhost:5001/api/v1/workouts/{id}
    • Version 2.0: https://localhost:5001/api/v2/workouts/{id}

Deprecating API Versions

To deprecate an old API version, set the Deprecated property on the ApiVersion attribute:

[ApiVersion("1.0", Deprecated = true)]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
public class DeprecatedController : ControllerBase
{
    [HttpGet("{id}")]
    public IActionResult GetV1(int id)
    {
        return Ok(new { Message = "This version is deprecated." });
    }
}
Enter fullscreen mode Exit fullscreen mode

Organizing Versioned Controllers and Models in Solution Explorer

To keep your project organized, especially as you add more versions, follow these tips:

  1. Create a Folder Structure:
    • Create a main folder called Controllers and subfolders for each version, e.g., v1, v2.
    • Place each version of your controllers in the respective folder.

Example structure:

   - Controllers
     - v1
       - WorkoutsController.cs
     - v2
       - WorkoutsController.cs
   - Models
     - v1
       - WorkoutV1.cs
     - v2
       - WorkoutV2.cs
Enter fullscreen mode Exit fullscreen mode
  1. Naming Conventions:

    • Use clear and consistent naming conventions to differentiate between versions.
    • Include the version number in the model and controller class names if needed for clarity, e.g., WorkoutV1, WorkoutV2.
  2. Updating Namespaces:

    • Ensure the namespaces reflect the folder structure to avoid conflicts.
    • Example:
     namespace MyApp.Models.v1
     {
         public class WorkoutV1
         {
             public int Id { get; set; }
             public string Name { get; set; }
         }
     }
    
     namespace MyApp.Models.v2
     {
         public class WorkoutV2
         {
             public int Id { get; set; }
             public string Name { get; set; }
             public string Description { get; set; }  // New field
         }
     }
    
  3. Consistent Routing:

    • Ensure your routing attributes are consistent and clear to indicate the version in the URL path.

All Done!

Now you have a versioned API that can evolve smoothly while maintaining backward compatibility. Don’t forget to document and communicate breaking or big changes! Feel free to experiment and make it even more advanced!

Top comments (1)