Skip to content

Commit 8d2eb9d

Browse files
authored
Merge pull request #5 from Mathavana/develop
Develop - API Version
2 parents 2fb3e4f + f90bebb commit 8d2eb9d

File tree

11 files changed

+208
-44
lines changed

11 files changed

+208
-44
lines changed

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
# ASP.NET Core Web API development
22

3-
We will try to create an api application using asp.net core.
4-
- Maninly using repository pattern, generics, LINQ, Entity framework core
3+
We will try to create an API application using asp.net core.
4+
- Mainly using repository pattern, generics, LINQ, Entity framework core
55
- Main business logic implemented in InMemeory Database (plan to use MSSQL DB)
66
- ASP NET Identity, JWT Authentication implemented in a separate DB
77
- Swagger for API Document
8+
- API Version
89

9-
Next ToDo List:
10+
### Next ToDo List:
1011
- Role Based Authentication/Authorization
12+
- Standard response for both error/success results
1113
- Logging
1214
- Global Error Handling
1315
- Dockerization

Supermarket/Extensions/ServiceExtensions.cs

Lines changed: 61 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
using Microsoft.AspNetCore.Authentication.JwtBearer;
33
using Microsoft.AspNetCore.Builder;
44
using Microsoft.AspNetCore.Identity;
5+
using Microsoft.AspNetCore.Mvc;
6+
using Microsoft.AspNetCore.Mvc.ApiExplorer;
7+
using Microsoft.AspNetCore.Mvc.Versioning;
58
using Microsoft.EntityFrameworkCore;
69
using Microsoft.Extensions.Configuration;
710
using Microsoft.Extensions.DependencyInjection;
@@ -13,8 +16,11 @@
1316
using Supermarket.Persistent.Context;
1417
using Supermarket.Persistent.Contracts;
1518
using Supermarket.Persistent.Repositories;
19+
using Supermarket.Swagger;
1620
using Swashbuckle.AspNetCore.Swagger;
1721
using System;
22+
using System.Collections.Generic;
23+
using System.Linq;
1824
using System.Text;
1925

2026
namespace Supermarket.Extensions
@@ -66,28 +72,34 @@ public static void ConfigureAutoMapper(this IServiceCollection services)
6672
services.AddAutoMapper();
6773
}
6874

69-
public static void ConfigureSwagger(this IServiceCollection services)
75+
public static void ConfigureSwagger(this IServiceCollection services, Type type)
7076
{
71-
services.AddSwaggerGen(c =>
77+
services.AddSwaggerGen(options =>
7278
{
73-
c.SwaggerDoc("v1", new Info
79+
// Resolve the temprary IApiVersionDescriptionProvider service
80+
var provider = services.BuildServiceProvider().GetRequiredService<IApiVersionDescriptionProvider>();
81+
82+
// Add a swagger document for each discovered API version
83+
foreach (var description in provider.ApiVersionDescriptions)
84+
{
85+
options.SwaggerDoc(description.GroupName, SwaggerInfo.CreateInfoForApiVersion(description, type));
86+
}
87+
88+
// Define the BearerAuth scheme that's in use
89+
options.AddSecurityDefinition("Bearer", new ApiKeyScheme()
90+
{
91+
Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"",
92+
Name = "Authorization",
93+
In = "header",
94+
Type = "apiKey"
95+
});
96+
options.AddSecurityRequirement(new Dictionary<string, IEnumerable<string>>
7497
{
75-
Title = "Supermarket API",
76-
Version = "v1",
77-
Description = "A simple example ASP.NET Core Web API",
78-
TermsOfService = "None",
79-
Contact = new Contact
80-
{
81-
Name = "Mathavan N",
82-
Email = "mathavan@gmail.com",
83-
Url = "https://github.com/Mathavana"
84-
},
85-
License = new License
86-
{
87-
Name = "Use under LICX",
88-
Url = "https://example.com/license"
89-
}
98+
{ "Bearer", Enumerable.Empty<string>() },
9099
});
100+
101+
// Add a custom filter for setting the default values
102+
options.OperationFilter<SwaggerDefaultValues>();
91103
});
92104
}
93105

@@ -111,16 +123,16 @@ public static void ConfigurePasswordPolicy(this IServiceCollection services)
111123
public static void ConfigureAuthentication(this IServiceCollection services, IConfiguration configuration)
112124
{
113125
var key = Encoding.UTF8.GetBytes(configuration["ApplicationSettings:JWT_Secret"].ToString());
114-
services.AddAuthentication(x =>
126+
services.AddAuthentication(options =>
115127
{
116-
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
117-
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
118-
x.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
119-
}).AddJwtBearer(x =>
128+
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
129+
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
130+
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
131+
}).AddJwtBearer(options =>
120132
{
121-
x.RequireHttpsMetadata = false;
122-
x.SaveToken = false;
123-
x.TokenValidationParameters = new TokenValidationParameters
133+
options.RequireHttpsMetadata = false; //only for DEV ENV
134+
options.SaveToken = false;
135+
options.TokenValidationParameters = new TokenValidationParameters
124136
{
125137
ValidateIssuerSigningKey = true,
126138
IssuerSigningKey = new SymmetricSecurityKey(key),
@@ -130,5 +142,28 @@ public static void ConfigureAuthentication(this IServiceCollection services, ICo
130142
};
131143
});
132144
}
145+
146+
public static void ConfigureApiVersioning(this IServiceCollection services)
147+
{
148+
services.AddApiVersioning(options =>
149+
{
150+
options.ReportApiVersions = true;
151+
options.DefaultApiVersion = new ApiVersion(1, 0);
152+
options.AssumeDefaultVersionWhenUnspecified = true;
153+
//options.ApiVersionReader = new MediaTypeApiVersionReader();
154+
options.ApiVersionSelector = new CurrentImplementationApiVersionSelector(options);
155+
});
156+
}
157+
158+
public static void ConfigureVersionedApiExplorer(this IServiceCollection services)
159+
{
160+
services.AddVersionedApiExplorer(options =>
161+
{
162+
//The format of the version added to the route URL
163+
options.GroupNameFormat = "'v'VVV";
164+
//Tells swagger to replace the version in the controller route
165+
options.SubstituteApiVersionInUrl = true;
166+
});
167+
}
133168
}
134169
}

Supermarket/Startup.cs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using Microsoft.AspNetCore.Hosting;
33
using Microsoft.AspNetCore.HttpOverrides;
44
using Microsoft.AspNetCore.Mvc;
5+
using Microsoft.AspNetCore.Mvc.ApiExplorer;
56
using Microsoft.Extensions.Configuration;
67
using Microsoft.Extensions.DependencyInjection;
78
using Newtonsoft.Json.Serialization;
@@ -44,17 +45,20 @@ public void ConfigureServices(IServiceCollection services)
4445

4546
services.AddMvc().AddJsonOptions(options =>
4647
{
47-
//Force Camel Case to JSON
4848
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
4949
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
5050

51+
services.ConfigureVersionedApiExplorer();
52+
53+
services.ConfigureApiVersioning();
54+
5155
services.ConfigureAutoMapper();
5256

53-
services.ConfigureSwagger();
57+
services.ConfigureSwagger(this.GetType());
5458
}
5559

5660
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
57-
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
61+
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApiVersionDescriptionProvider provider)
5862
{
5963
if (env.IsDevelopment())
6064
{
@@ -85,9 +89,13 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env)
8589

8690
// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
8791
// specifying the Swagger JSON endpoint.
88-
app.UseSwaggerUI(c =>
92+
app.UseSwaggerUI(options =>
8993
{
90-
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Supermarket API V1");
94+
//Build a swagger endpoint for each discovered API version
95+
foreach (var description in provider.ApiVersionDescriptions)
96+
{
97+
options.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant());
98+
}
9199
});
92100
}
93101
}

Supermarket/Supermarket.csproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,17 @@
55
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
66
</PropertyGroup>
77

8+
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
9+
<DocumentationFile></DocumentationFile>
10+
<OutputPath></OutputPath>
11+
</PropertyGroup>
12+
813
<ItemGroup>
914
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="6.0.0" />
1015
<PackageReference Include="Microsoft.AspNetCore.App" />
16+
<PackageReference Include="Microsoft.AspNetCore.Mvc.ApiExplorer" Version="2.2.0" />
17+
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="3.1.2" />
18+
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer" Version="3.2.0" />
1119
<PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.2.0" PrivateAssets="All" />
1220
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.2.0" />
1321
<PackageReference Include="Swashbuckle.AspNetCore" Version="4.0.1" />
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using Swashbuckle.AspNetCore.Swagger;
2+
using Swashbuckle.AspNetCore.SwaggerGen;
3+
using System.Linq;
4+
5+
namespace Supermarket.Swagger
6+
{
7+
public class SwaggerDefaultValues : IOperationFilter
8+
{
9+
/// <summary>
10+
/// Applies the filter to the specified operation using the given context.
11+
/// </summary>
12+
/// <param name="operation">The operation to apply the filter to.</param>
13+
/// <param name="context">The current operation filter context.</param>
14+
public void Apply(Operation operation, OperationFilterContext context)
15+
{
16+
if (operation.Parameters == null)
17+
{
18+
return;
19+
}
20+
foreach (var parameter in operation.Parameters.OfType<NonBodyParameter>())
21+
{
22+
var description = context.ApiDescription.ParameterDescriptions.First(p => p.Name == parameter.Name);
23+
var routeInfo = description.RouteInfo;
24+
25+
if (parameter.Description == null)
26+
{
27+
parameter.Description = description.ModelMetadata?.Description;
28+
}
29+
30+
if (routeInfo == null)
31+
{
32+
continue;
33+
}
34+
35+
if (parameter.Default == null)
36+
{
37+
parameter.Default = routeInfo.DefaultValue;
38+
}
39+
40+
parameter.Required |= !routeInfo.IsOptional;
41+
}
42+
}
43+
}
44+
}

Supermarket/Swagger/SwaggerInfo.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using Microsoft.AspNetCore.Mvc.ApiExplorer;
2+
using Swashbuckle.AspNetCore.Swagger;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Reflection;
7+
using System.Threading.Tasks;
8+
9+
namespace Supermarket.Swagger
10+
{
11+
public static class SwaggerInfo
12+
{
13+
public static Info CreateInfoForApiVersion(ApiVersionDescription description, Type type)
14+
{
15+
var info = new Info()
16+
{
17+
Title = $"{type.Assembly.GetCustomAttribute<AssemblyProductAttribute>().Product} {description.ApiVersion}",
18+
Version = description.ApiVersion.ToString(),
19+
Description = "A simple example ASP.NET Core Web API",
20+
TermsOfService = "None",
21+
Contact = new Contact { Name = "Mathavan N", Email = "mathavan@gmail.com", Url = "https://github.com/Mathavana" },
22+
License = new License { Name = "Use under LICX", Url = "https://example.com/license" }
23+
};
24+
25+
if (description.IsDeprecated)
26+
{
27+
info.Description += " This API version has been deprecated.";
28+
}
29+
30+
return info;
31+
}
32+
}
33+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using Microsoft.AspNetCore.Mvc;
2+
3+
namespace Supermarket.V1.Controller
4+
{
5+
[ApiController]
6+
[ApiVersion("1.0")]
7+
[Route("api/v{version:apiVersion}/[controller]")]
8+
public class AboutController : ControllerBase
9+
{
10+
[HttpGet]
11+
public IActionResult Get()
12+
{
13+
return Ok(new { message = "This is about Api Version 1.0" });
14+
}
15+
}
16+
}

Supermarket/Controllers/AccountController.cs renamed to Supermarket/V1/Controllers/AccountController.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@
22
using Microsoft.AspNetCore.Authorization;
33
using Microsoft.AspNetCore.Identity;
44
using Microsoft.AspNetCore.Mvc;
5-
using Microsoft.CodeAnalysis.Options;
65
using Microsoft.Extensions.Options;
76
using Microsoft.IdentityModel.Tokens;
8-
using Supermarket.Entites.Models;
97
using Supermarket.Extensions;
108
using Supermarket.Identity.Models;
119
using Supermarket.Resources;
@@ -16,18 +14,19 @@
1614
using System.Text;
1715
using System.Threading.Tasks;
1816

19-
namespace Supermarket.Controllers
17+
namespace Supermarket.V1.Controller
2018
{
21-
[Route("api/[controller]")]
2219
[ApiController]
20+
[ApiVersion("1.0")]
21+
[Route("api/v{version:apiVersion}/[controller]")]
2322
public class AccountController : ControllerBase
2423
{
2524
private readonly UserManager<ApplicationUser> _userManager;
2625
private readonly SignInManager<ApplicationUser> _signInManager;
2726
private readonly ApplicationSettings _appSettings;
2827
private readonly IMapper _mapper;
2928

30-
public AccountController(UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager,
29+
public AccountController(UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager,
3130
IOptions<ApplicationSettings> appSettings, IMapper mapper)
3231
{
3332
_userManager = userManager;

Supermarket/Controllers/CategoriesController.cs renamed to Supermarket/V1/Controllers/CategoriesController.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@
88
using System.Collections.Generic;
99
using System.Threading.Tasks;
1010

11-
namespace Supermarket.Controllers
11+
namespace Supermarket.V1.Controllers
1212
{
13-
[Route("/api/[controller]")]
13+
[ApiVersion("1.0")]
14+
[Route("api/v{version:apiVersion}/[controller]")]
1415
[Authorize]
16+
[ApiController]
1517
public class CategoriesController : ControllerBase
1618
{
1719
private readonly IServiceWrapper _serviceWrapper;
@@ -76,6 +78,5 @@ public async Task<IActionResult> DeleteAsync(int id)
7678
var categoryResource = _mapper.Map<Category, CategoryResource>(result.Category);
7779
return Ok(categoryResource);
7880
}
79-
8081
}
8182
}

Supermarket/Controllers/ProductsController.cs renamed to Supermarket/V1/Controllers/ProductsController.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@
77
using System.Collections.Generic;
88
using System.Threading.Tasks;
99

10-
namespace Supermarket.Controllers
10+
namespace Supermarket.V1.Controllers
1111
{
12-
[Route("/api/[controller]")]
12+
[ApiVersion("1.0")]
13+
[Route("api/v{version:apiVersion}/[controller]")]
1314
[Authorize]
15+
[ApiController]
1416
public class ProductsController : ControllerBase
1517
{
1618
private readonly IServiceWrapper _serviceWrapper;

0 commit comments

Comments
 (0)