How to configure API Versioning in .NET 8
Tags: api, dotnet, versioning
Published: 2024-06-29 22:11:44

### 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:
```xml
<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.

1. **Add the references above to the project**

2. **Configure Services in `Program.cs`**:
   ```csharp
   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();
   ```

3. **Implement Versioned Controllers**:
   Create versioned controllers by decorating them with the `ApiVersion` attribute:
   ```csharp
   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" });
           }
       }
   }
   ```

4. **Handling Evolving Models**:
   When models evolve, create separate model classes for each version to maintain backward compatibility.

   **Version 1 Model:**
   ```csharp
   public class WorkoutV1
   {
       public int Id { get; set; }
       public string Name { get; set; }
   }
   ```

   **Version 2 Model with Additional Fields:**
   ```csharp
   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:
   ```csharp
   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);
           }
       }
   }
   ```

5. **Configure Swagger Documentation**:
   Ensure each API version has its own Swagger documentation:
   ```csharp
   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" });
   });
   ```

6. **Run Your Application**:
   Build and run your application to see the versioned API in action:
   ```sh
   dotnet run
   ```

7. **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:
```csharp
[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:

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
   ```

2. **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`.

3. **Updating Namespaces**:
   - Ensure the namespaces reflect the folder structure to avoid conflicts.
   - Example:
     ```csharp
     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
         }
     }
     ```

4. **Consistent Routing**:
   - Ensure your routing attributes are consistent and clear to indicate the version in the URL path.

### All Done! ###
<img width="100%" style="width:100%" src="https://media.giphy.com/media/VIjf1GqRSbf0OsNG0H/giphy.gif">

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!