Why is api version not applied? - asp.net-core

I have the following code Statup.cs to setup web api with swagger and multiple versions. The problem is that the version is not used - see screenshot below.
I did used AddApiVersioning .. also UrlSegmentApiVersionReader as ApiVersionReader in the configuration options.
What am I missing ?
The framework I used is .NetCore 3.0.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Versioning;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;
using System;
using System.IO;
using System.Reflection;
namespace SwaggerUI
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Register the Swagger generator, defining 1 or more Swagger documents
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo
{
Title = "Employee API",
Version = "v1",
Description = "An API to perform Employee operations",
TermsOfService = new Uri("https://example.com/terms"),
Contact = new OpenApiContact
{
Name = "John Walkner",
Email = "John.Walkner#gmail.com",
Url = new Uri("https://twitter.com/jwalkner"),
},
License = new OpenApiLicense
{
Name = "Employee API LICX",
Url = new Uri("https://example.com/license"),
}
});
c.SwaggerDoc("v2", new OpenApiInfo
{
Title = "Employee API",
Version = "v2",
Description = "An API to perform Employee operations",
TermsOfService = new Uri("https://example.com/terms"),
Contact = new OpenApiContact
{
Name = "John Walkner",
Email = "John.Walkner#gmail.com",
Url = new Uri("https://twitter.com/jwalkner"),
},
License = new OpenApiLicense
{
Name = "Employee API LICX",
Url = new Uri("https://example.com/license"),
}
});
// Set the comments path for the Swagger JSON and UI.
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
});
services.AddControllers();
services.AddApiVersioning(o => {
o.ReportApiVersions = true;
o.AssumeDefaultVersionWhenUnspecified = true;
o.DefaultApiVersion = new ApiVersion(1, 0);
o.ApiVersionReader = new UrlSegmentApiVersionReader();
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseStaticFiles();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
// Enable middleware to serve generated Swagger as a JSON endpoint.
app.UseSwagger(x => x.SerializeAsV2 = true);
// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
// specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
c.SwaggerEndpoint("/swagger/v2/swagger.json", "My API V2");
});
}
}
}

I found the answer:
after services.AddApiVersioning I added services.AddVersionedApiExplorer and before I needed to add a reference to Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer
services.AddVersionedApiExplorer(o =>
{
o.GroupNameFormat = "'v'VVV";
o.SubstituteApiVersionInUrl = true; // this is needed to work
});
See code here: https://github.com/LucaGabi/SwaggerUI
Also see here https://github.com/LucaGabi/WebApplication1 more complex setup.

Related

Swashbuckle extension version switch

I am new to asp.net core and I am using swagger. I downloaded it by following the steps Install-Package Swashbuckle.AspNetCore -Version 5.6.3.Then add middleware
services.AddSwaggerGen();Then add app.UseSwagger(c =>
{
c.SerializeAsV2 = true;
});
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
}); to the ge
nerated JSON document and Swagger UI.
Finally add the header information as per the documentation.
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo
{
Version = "v1",
Title = "ToDo API",
Description = "A simple example ASP.NET Core Web API",
TermsOfService = new Uri("https://example.com/terms"),
Contact = new OpenApiContact
{
Name = "Shayne Boyer",
Email = string.Empty,
Url = new Uri("https://twitter.com/spboyer"),
},
License = new OpenApiLicense
{
Name = "Use under LICX",
Url = new Uri("https://example.com/license"),
}
});
});
These are all requirements to follow the documentation. But I need to extend the requirements now, I need to add the version information of the API, similar to the API V1 API V2 version switch. I have referenced some sources but don't have the complete code, can you guys help me? Any help is excellent! !
Are you trying to switch versions like this?
First I created 2 versions of folders and controllers.Therefore, the namespace of each controller corresponds to its folder, like this:
V1 version:
namespace WebApplication129.Controllers.V1
{
[ApiController]
[Route("api/v1/[controller]")]
public class HomeController : ControllerBase
{
[Route("test")]
[HttpGet]
public string Test()
{
return "v1 test";
}
}
}
V2 version:
namespace WebApplication129.Controllers.V2
{
[ApiController]
[Route("api/v2/[controller]")]
public class HomeController : ControllerBase
{
[Route("test")]
[HttpGet]
public string Test()
{
return "v2 test";
}
}
}
Then create an agreement to inform Swagger, in this way, we can control how Swagger generates Swagger documents, thereby controlling the UI.
Create the following class:
public class GroupingByNamespaceConvention : IControllerModelConvention
{
public void Apply(ControllerModel controller)
{
var controllerNamespace = controller.ControllerType.Namespace;
var apiVersion = controllerNamespace.Split(".").Last().ToLower();
if (!apiVersion.StartsWith("v")) { apiVersion = "v1"; }
controller.ApiExplorer.GroupName = apiVersion;
}
}
Now we must apply the convention. For that we go to AddControllers in ConfigureServices and add the convention:
services.AddControllers(options =>
{
options.Conventions.Add(new GroupingByNamespaceConvention());
});
The final complete startup.cs configuration is as follows:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;
using System;
using WebApplication129.Controllers.conf;
namespace WebApplication129
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(options =>
{
options.Conventions.Add(new GroupingByNamespaceConvention());
});
services.AddSwaggerGen(config =>
{
var titleBase = "Test API";
var description = "This is a Web API for Test operations";
var TermsOfService = new Uri("https://xxxxxx");
var License = new OpenApiLicense()
{
Name = "Test"
};
var Contact = new OpenApiContact()
{
Name = "Test",
Email = "Test#hotmail.com",
Url = new Uri("https://xxxxxx")
};
config.SwaggerDoc("v1", new OpenApiInfo
{
Version = "v1",
Title = titleBase + " v1",
Description = description,
TermsOfService = TermsOfService,
License = License,
Contact = Contact
});
config.SwaggerDoc("v2", new OpenApiInfo
{
Version = "v2",
Title = titleBase + " v2",
Description = description,
TermsOfService = TermsOfService,
License = License,
Contact = Contact
});
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseSwagger();
app.UseSwaggerUI(config =>
{
config.SwaggerEndpoint("/swagger/v1/swagger.json", "API v1");
config.SwaggerEndpoint("/swagger/v2/swagger.json", "API v2");
});
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}

how to solve 401 unauthorized error in postman when calling a .Net API

I have a .net core webapi working fine and tested with swagger, also the method has set to allow anonymous access so no authentication should be required. But when testing the POST method with Postman, I always get the 401 error.. Appreciate any help!
Since I am not clear about your specific code implementation, I wrote a demo here, which is an example of generating token from user login to access permission API. You can refer to it, maybe it will help you a little:
First,open the appsettings.json file and change the section named Jwt:
"Jwt": {
"Issuer": "testUser",
"Audience": "user",
"Key": "this is my custom Secret key for authnetication"
}
Enable the JWT authentication scheme and swagger authorization configuration when the configuration starts, the entire code is as follows:
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using WebApplication129.Controllers.conf;
namespace WebApplication129
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
//services.AddSingleton<IConfiguration>(Configuration);
services.AddAuthentication(opt => {
opt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
opt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["Jwt:Issuer"],
ValidAudience = Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
};
});
services.AddControllers(options =>
{
options.Conventions.Add(new GroupingByNamespaceConvention());
});
services.AddSwaggerGen(config =>
{
config.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description =
"JWT Authorization header using the Bearer scheme. \r\n\r\n Enter 'Bearer' [space] and then your token in the text input below.\r\n\r\nExample: \"Bearer 12345abcdef\"",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer",
BearerFormat="JWT"
});
config.AddSecurityRequirement(new OpenApiSecurityRequirement()
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
},
Scheme = "oauth2",
Name = "Bearer",
In = ParameterLocation.Header,
},
new List<string>()
}
});
var titleBase = "Test API";
var description = "This is a Web API for Test operations";
var TermsOfService = new Uri("https://xxxxxx");
var License = new OpenApiLicense()
{
Name = "MIT"
};
var Contact = new OpenApiContact()
{
Name = "Test",
Email = "Test#hotmail.com",
Url = new Uri("https://xxxxxx")
};
config.SwaggerDoc("v1", new OpenApiInfo
{
Version = "v1",
Title = titleBase + " v1",
Description = description,
TermsOfService = TermsOfService,
License = License,
Contact = Contact
});
config.SwaggerDoc("v2", new OpenApiInfo
{
Version = "v2",
Title = titleBase + " v2",
Description = description,
TermsOfService = TermsOfService,
License = License,
Contact = Contact
});
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
config.IncludeXmlComments(xmlPath);
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseSwagger();
app.UseSwaggerUI(config =>
{
config.SwaggerEndpoint("/swagger/v1/swagger.json", "Test v1");
//config.SwaggerEndpoint("/swagger/v2/swagger.json", "Test v2");
});
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
Log in and generate the jwt part as follows. Since I did not use it with a database, I customized a user:
Model:
public class Usuario
{
public string NomeUsuario { get; set; }
public string Senha { get; set; }
}
Controller:
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WebApplication129.Model;
namespace WebApplication129.Controllers.V1
{
[Route("api/[controller]")]
[ApiController]
public class SegurancaController : Controller
{
private IConfiguration _config;
public SegurancaController(IConfiguration Configuration)
{
_config = Configuration;
}
[HttpPost]
[Route("login")]
public IActionResult Login([FromBody] Usuario loginDetalhes)
{
bool resultado = ValidarUsuario(loginDetalhes);
if (resultado)
{
var tokenString = GerarTokenJWT();
return Ok(new { token = tokenString });
}
else
{
return Unauthorized();
}
}
private string GerarTokenJWT()
{
var issuer = _config["Jwt:Issuer"];
var audience = _config["Jwt:Audience"];
var expiry = DateTime.Now.AddMinutes(120);
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(issuer: issuer, audience: audience,
expires: expiry, signingCredentials: credentials);
var tokenHandler = new JwtSecurityTokenHandler();
var stringToken = tokenHandler.WriteToken(token);
return stringToken;
}
private bool ValidarUsuario(Usuario loginDetalhes)
{
if (loginDetalhes.NomeUsuario == "TestName" && loginDetalhes.Senha == "TestPwd")
{
return true;
}
else
{
return false;
}
}
}
}
Test and verify API:
[ApiController]
[Route("api/v1/[controller]")]
public class HomeController : ControllerBase
{
/// <summary>
/// Add the description information you need
/// </summary>
///
///
[Route("test")]
[HttpGet]
public string Test( int userID)
{
return "v1 test";
}
[Route("list_data")]
[HttpGet]
[Authorize]
public Object Data()
{
User user = new User();
user.id = 1;
user.userName = "Test";
user.email = "test#xxx.com";
user.address = "testAddress";
return user;
}
}
The above shows two APIs, one requires authorization and the other does not require authorization to access.
Resutl:
Still If anyone can't figure out the error after #Tupac answer, check that you have included proper
app.UseAuthentication();
app.UseAuthorization();
in Program.cs file

URL Globalization in asp.net core 5

I am about to change my asp.net core Globalization by URL.
Just like Microsoft official site:
https://www.microsoft.com/zh-cn/ is for the Chinese version.
https://www.microsoft.com/en-us/ is for the English version.
What I want to do is something different:
https://www.aaa.com/zh-hans/index.html is for the Chinese version.
https://www.aaa.com/en/index.html is for the English version.
As you see above, I need to add a .html suffix also.
I found some tutorials on google.
Here is my code:
public void ConfigureServices(IServiceCollection services)
{
services.AddLocalization(options => options.ResourcesPath = "Resources");
services.AddMvc().AddViewLocalization(Microsoft.AspNetCore.Mvc.Razor.LanguageViewLocationExpanderFormat.Suffix,
options => options.ResourcesPath = "Resources").AddSessionStateTempDataProvider();
}
public void Configure(IApplicationBuilder app)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
//app.UseHsts();
}
app.UseRouting();
app.UseStaticFiles();
var SupportedCultures = new List<CultureInfo> {
new CultureInfo("en"),
new CultureInfo("zh-Hans"),
new CultureInfo("zh-Hant")
};
var options = new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en"),
SupportedCultures = SupportedCultures,
SupportedUICultures = SupportedCultures,
};
app.UseRequestLocalization(options);
var requestProvider = new RouteDataRequestCultureProvider();
options.RequestCultureProviders.Insert(0, requestProvider);
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(name: "default", pattern: "{culture}/{controller=Home}/{action=Index}.html", defaults: new { culture = "en", controller = "Home", action = "Index" });
});
}
However, after the website ran, it reports an error:
An unhandled exception occurred while processing the request.
AmbiguousMatchException: The request matched multiple endpoints. Matches:
Sample.Controllers.HomeController.Index (Sample)
Sample.Controllers.HomeController.Error (Sample)
Sample.Controllers.HomeController.Privacy (Sample)
Microsoft.AspNetCore.Routing.Matching.DefaultEndpointSelector.ReportAmbiguity(CandidateState[] candidateState)
It seems that's because I added a .html suffix in MapControllerRoute.
I am not quite sure whether my code is right for most of the tutorial is for asp.net core 2 or even for asp.net core 1. Most of the code now not work in asp.net core 5 anymore.
What's wrong with my code? Thank you.
HomeController:
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Sample.Models;
namespace Sample.Controllers
{
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}
public IActionResult Index()
{
return View();
}
public IActionResult Privacy()
{
return View();
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
}

Combine IdentityServer4 and ASP.NET Core Identity Framework

Hi everyone I have a problem with a combination of IdentityServer4 and Identity Framework.
I have 3 projects in my solution.
First, it is an OAuth project with IdentityServer4. This project has the next configuration:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using IdentityServer4;
using IdentityServer4.Models;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Platform.OAuth.Data;
using Platform.OAuth.Data.Models;
namespace Platform.OAuth
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationContext>(options =>
{
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection"),
x => x.MigrationsAssembly(typeof(ApplicationContext).Assembly.FullName));
});
services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddEntityFrameworkStores<ApplicationContext>();
services.AddControllers();
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryPersistedGrants()
.AddInMemoryClients(new List<Client>
{
new Client
{
ClientId = "api-client",
ClientName = "API Client",
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
RequireConsent = false,
ClientSecrets =
{
new Secret("secret".Sha256())
},
RedirectUris = { "http://localhost:5002/signin-oidc" },
PostLogoutRedirectUris = { "http://localhost:5002/signout-callback-oidc" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api1"
},
AllowOfflineAccess = true
}
})
.AddInMemoryApiScopes(new List<ApiScope>
{
new ApiScope("api1", "My API")
})
.AddAspNetIdentity<ApplicationUser>();
services.AddAuthentication();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
Second, it is API project that contains some API which I want to protect. This project has next configuration:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
namespace Api
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = "https://localhost:44392"; /// <-- OAuth project url
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false
};
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
Thrid, It is client project that makes calls for both API.
static async Task Main(string[] args)
{
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync("https://localhost:44392");
if (disco.IsError)
{
Console.WriteLine(disco.Error);
return;
}
var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = "api-client",
ClientSecret = "secret",
Scope = "api1"
});
if (tokenResponse.IsError)
{
Console.WriteLine(tokenResponse.Error);
return;
}
Console.WriteLine(tokenResponse.Json);
client.SetBearerToken(tokenResponse.AccessToken);
var testResponse = await client.GetAsync("https://localhost:44392/WeatherForecast");
if(testResponse.IsSuccessStatusCode)
{
var content = await testResponse.Content.ReadAsStringAsync();
Console.WriteLine(JArray.Parse(content));
}
var apiClient = new HttpClient();
apiClient.SetBearerToken(tokenResponse.AccessToken);
Console.WriteLine("==============================================================");
var response = await apiClient.GetAsync("https://localhost:44369/WeatherForecast");
if (!response.IsSuccessStatusCode)
{
Console.WriteLine(response.StatusCode);
}
else
{
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(JArray.Parse(content));
}
}
Both web API projects have default WeatherForecastController which I've protected by AuthorizeAttribute And when the client makes a request with a token to API, the action returns data, but when a request for OAuth, action returns 404 error. I think it is good for the OAuth project but not for the API because API and 'OAuth' projects don't have authorized users. But why the API return data?
Receiving 404 should not be related with any auth issue, since it is the HTTP "not found" code, retrieved precisely when the resource you tried to access is not found with the URL that was used.
Concerning the data received from the API endpoint, it is a successful response, right? I will assume it is.
Following your client code snippet, it seems to me that you get a token from the Identity Server and perform a GET request with the token set. Since the token is valid, the client is authenticated and the API recognizes it.
If you set the [Authorize] attribute without any specified policy, the default applies. The default is: if you are authenticated, you are also authorized; this means that providing a valid token is enough to be authorized - which you do.
I would say that this is why you get a successful response. More information here.

How do I reflect a dotnet web api endpoint that uses query string parameters in SwagggerUI?

I'm trying to implement a dotnet web api with API versioning that uses query strings and headers. Here im using swagger to document and test the endpoints. I successfully used path versioning and reflected the endpoints in swagger. But im struggling to understand how to reflect query string & header versioning in swagger. I tried to find a solution from this article https://swagger.io/docs/specification/describing-parameters/#query-parameters but was still confused how to implement this in my dotnet web api.
My project contains 2 main controller classes with the following API versions.
WeatherForecastController.cs
namespace QueryStringVersioning.Controllers
{
[ApiController]
[ApiVersion("1.0")]
[ApiVersion("1.1", Deprecated = true)]
[ApiVersion("3.0")]
[Route ("api")] //support query string & header versioning
// [Route("api/v{version:apiVersion}/[controller]")] //support path versioning
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering",
"Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
[HttpGet, MapToApiVersion("3.0")]
public IActionResult GetV3_0() => Ok(new string[] { "MapToApiVersion value 3.0" });
[HttpGet, MapToApiVersion("1.1")]
public IActionResult GetV1_1() => Ok(new string[] { "Depreceated MapToApiVersion value" });
}}
WeatherForecastController2.cs
namespace QueryStringVersioning.Controllers2
{
[ApiController]
[ApiVersion("2.0")]
[ApiVersion("2.1")]
[Route ("api")] //support query string & header versioning
// [Route("api/v{version:apiVersion}/[controller]")] //support path versioning
public class WeatherForecastController : ControllerBase
{
public IActionResult GetV2_0() => Ok(new string[] { "This is API Version 2.0" });
[HttpGet, MapToApiVersion("2.1")]
public IActionResult GetV2_1() => Ok(new string[] { "This is API Version 2.1" });
}}
And the startup.cs file
namespace QueryStringVersioning
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo
{
Version = "v1",
Title = "API_Versioning V1",
});
c.SwaggerDoc("v1.1", new Microsoft.OpenApi.Models.OpenApiInfo
{
Version = "v1.1",
Title = "API_Versioning V1.1",
});
c.SwaggerDoc("v2", new Microsoft.OpenApi.Models.OpenApiInfo
{
Version = "v2",
Title = "API_Versioning V2"
});
c.SwaggerDoc("v2.1", new Microsoft.OpenApi.Models.OpenApiInfo
{
Version = "v2.1",
Title = "API_Versioning V2.1"
});
c.SwaggerDoc("v3", new Microsoft.OpenApi.Models.OpenApiInfo
{
Version = "v3",
Title = "API_Versioning V3"
});
c.ResolveConflictingActions (apiDescriptions => apiDescriptions.First ());
// c.OperationFilter<RemoveVersionFromParameter>();
// c.DocumentFilter<ReplaceVersionWithExactValueInPath>();
});
services.AddControllers();
services.AddMvc();
services.AddApiVersioning(option =>
{
option.ReportApiVersions = true;
option.AssumeDefaultVersionWhenUnspecified = true;
option.DefaultApiVersion = new ApiVersion(1, 0);
// Supporting multiple versioning scheme
option.ApiVersionReader = ApiVersionReader.Combine(new HeaderApiVersionReader("X-version"), new QueryStringApiVersionReader("api-version"));
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
// Enable middleware to serve generated Swagger as a JSON endpoint.
app.UseSwagger();
// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
// specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "API_Versioning V1.0");
c.SwaggerEndpoint("/swagger/v1.1/swagger.json", "API_Versioning V1.1");
c.SwaggerEndpoint("/swagger/v2/swagger.json", "API_Versioning V2.0");
c.SwaggerEndpoint("/swagger/v2.1/swagger.json", "API_Versioning V2.1");
c.SwaggerEndpoint("/swagger/v3/swagger.json", "API_Versioning V3.0");
});
}
}
}
#michael-wang is correct. You need to also include the API Versioning API Explorer extensions. This extensions make API discovery API version aware. One of the many possible uses for this information is OpenAPI/Swagger integration.
Links to all of the applicable NuGet packages are listed on the API Versioning landing page. There is also an end-to-end example provided using Swashbuckle.