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>
Guide
Follow these steps to implement versioning in your .NET 8 Web API project, including handling evolving models.
Add the references above to the project
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();
-
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" });
}
}
}
- 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; }
}
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
}
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);
}
}
}
- 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" });
});
- Run Your Application: Build and run your application to see the versioned API in action:
dotnet run
-
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}
- Version 1.0:
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." });
}
}
Organizing Versioned Controllers and Models in Solution Explorer
To keep your project organized, especially as you add more versions, follow these tips:
-
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.
- Create a main folder called
Example structure:
- Controllers
- v1
- WorkoutsController.cs
- v2
- WorkoutsController.cs
- Models
- v1
- WorkoutV1.cs
- v2
- WorkoutV2.cs
-
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
.
-
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 } }
-
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)