.Net 6 Web Api Swagger Versioning problem - swagger

As the title suggests, i have a .net 6 web api that I'm trying to add versioning to but swagger (swashbuckle) does not seem to understand whats going on.
Program.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Versioning;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddApiVersioning(setup =>
{
setup.DefaultApiVersion = new ApiVersion(1, 0);
setup.AssumeDefaultVersionWhenUnspecified = true;
setup.ReportApiVersions = true;
});
ConfigureServices(builder.Services);
var app = builder.Build();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint($"/swagger/v1/swagger.json", $"v1");
c.SwaggerEndpoint($"/swagger/v2/swagger.json", $"v2");
});
app.Run();
void ConfigureServices(IServiceCollection services)
{
services.AddMvcCore();
services.AddApiVersioning(options =>
{
options.ReportApiVersions = true;
options.AssumeDefaultVersionWhenUnspecified = false;
options.ApiVersionReader = new UrlSegmentApiVersionReader();
});
services.AddSwaggerGen();
}
I have annotated my controllers like so:
[ApiVersion("1.0")]
[Route("api/v1/[controller]")]
[ApiController]
public class MessageController : ControllerBase
[ApiVersion("2.0")]
[Route("api/v2/[controller]")]
[ApiController]
public class MessageController : ControllerBase
The swagger document that is generated looks like this:
And if i select v2 from the drop down, I get this:
Nuget packages and versions installed are:
Can anyone tell me where I'm going wrong.

You'll need to add the swagger documents themselves, not just the UI for them. In your AddSwaggerGen method, add something like:
services.AddSwaggerGen(c => {
c.SwaggerDoc("v1", new OpenApiInfo { Version = "v1", Title = "My API" });
c.SwaggerDoc("v2", new OpenApiInfo { Version = "v2", Title = "My API" });
});

This article seems to have covered all the aspect what you are looking for.
Please have a look.
https://referbruv.com/blog/integrating-aspnet-core-api-versions-with-swagger-ui/

Besides passing the configuration here:
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API - V1", Version = "v1" });
c.SwaggerDoc("v2", new OpenApiInfo { Title = "My API - V2", Version = "v2" });
});
According to the docs you have to specify the GroupName of the controller:
[ApiVersion("2.0")]
[Route("api/v2/[controller]")]
[ApiController]
[ApiExplorerSettings(GroupName = "v2")]
public class MessageController : ControllerBase
Also, the docs show ways to customize and add conventions.

Related

fHow to have separate login/logout screen with Azure AD authentication

I have previously integrated with Okta and they have a sign in widget you can put into a page to log in with and call a controller to initiate the authentication/claims process. However, Azure AD does not seem to have anything like this so I wanted to use my razor page with our logo on it located in /pages/login/index to be the only folder for anonymous viewing. Basically I would like it to be the first screen anyone sees when they come to the site when not logged in and it will have a button which calls a controller post action to take you to Microsofts Azure AD Login screen. I have not seen any examples of this online so I am curious if this is even possible?
My current solution takes you straight to microsofts login:
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using Project.Models;
using DocumentFormat.OpenXml.Office2016.Drawing.ChartDrawing;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.UI;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
//authentication pipeline
var initialScopes = builder.Configuration.GetValue<string>("DownstreamApi:Scopes")?.Split(' ');
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
.AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApi"))
.AddInMemoryTokenCaches();
builder.Services.AddControllers(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
builder.Services.AddRazorPages()
.AddMicrosoftIdentityUI();
var RootPath = builder.Environment.ContentRootPath;
var WebPath = builder.Environment.WebRootPath;
var fileDirectory = Path.Combine(Directory.GetParent(RootPath).Parent.ToString(), "armsfiles");
IFileProvider physicalProvider = new PhysicalFileProvider(fileDirectory);
builder.Services.AddSingleton<IFileProvider>(physicalProvider);
//Not needed. We are not using this level of abstraction but may move towards it one day so possibly keep.
var connectionString = builder.Configuration.GetConnectionString("DBContext");
builder.Services.AddDbContext<DBContext>(options => options.UseSqlServer(connectionString));
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/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();
}
else
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
//We are making it so armsfiles are not accessible outside of arms so if we move to box or onedrive then the parameter may need to be removed.
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = physicalProvider,
RequestPath = "/files"
});
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorPages();
app.MapControllerRoute(
name: "default",
pattern: "{controller}/{action}/{id?}",
defaults: new { controller = "Home", action = "Index" });
app.Run();
I was thinking I could add this:
// Add services to the container.
builder.Services.AddRazorPages().AddRazorPagesOptions(options =>
{
options.Conventions.AllowAnonymousToFolder("/Login");
options.Conventions.AuthorizeFolder("/");
options.Conventions.AuthorizeFolder("/files");
});
But since there is no redirect for non logged in users that I can find I do not know if this option even makes a difference. So basically it is unclear how to setup the Program file to ensure not logged in users are redirected to this razor page (/login/index) and also what a controller for a sign in and sign out button would even look like! I am surprised I have struggled to find an example of this for ASP.NET Core 6.
**EDIT 11/22/2022
I have since got every component of my question solved except when a user goes to the site, if they are not authenticated I want to redirect them to "/login/" which hosts a page with a button that OnPost() does the following challenge:
public class IndexModel : PageModel
{
private string SignedOutRedirectUri;
public IndexModel(IConfiguration configuration)
{
SignedInRedirectUri = configuration["AzureAd:SignedInRedirectUri"];
}
public void OnGet()
{
}
public IActionResult OnPost()
{
return Challenge(
new AuthenticationProperties { RedirectUri = SignedInRedirectUri },
OpenIdConnectDefaults.AuthenticationScheme);
}
}
Is there a way to redirect users to a login page similar to how ASP.NET Core allows a redirect for their cookie options? ie,
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(cookieOptions =>
{
cookieOptions.Cookie.Name = "UserLoginCookie";
cookieOptions.LoginPath = "/Login/";
cookieOptions.ExpireTimeSpan = TimeSpan.FromMinutes(30);
cookieOptions.SlidingExpiration = true;
});
Create HomePageRouteModelConvention.
using Microsoft.AspNetCore.Mvc.ApplicationModels;
namespace WebApplication3
{
public class HomePageRouteModelConvention : IPageRouteModelConvention
{
public void Apply(PageRouteModel model)
{
if (model.RelativePath == "/Pages/Index.cshtml")
{
var currentHomePage = model.Selectors.Single(s => s.AttributeRouteModel.Template == string.Empty);
model.Selectors.Remove(currentHomePage);
}
if (model.RelativePath == "/Pages/Login/Index.cshtml")
{
model.Selectors.Add(new SelectorModel()
{
AttributeRouteModel = new AttributeRouteModel
{
Template = string.Empty
}
});
}
}
}
}
Refer my startup.cs file.
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.UI;
namespace WebApplication3
{
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"));
builder.Services.AddAuthorization(options =>
{
// By default, all incoming requests will be authorized according to the default policy.
options.FallbackPolicy = options.DefaultPolicy;
});
builder.Services.AddRazorPages()
.AddMicrosoftIdentityUI().AddRazorPagesOptions(options =>
{
options.Conventions.Add(new HomePageRouteModelConvention());
options.Conventions.AllowAnonymousToFolder("/Login");
options.Conventions.AuthorizeFolder("/");
options.Conventions.AuthorizeFolder("/files");
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/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.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorPages();
app.MapControllers();
app.Run();
}
}
}
Others
Test Result:

Collaps all Swagger UI groups

Hi I create an API in C# and use SwaggerUI to test my api.
I woud know if it's possible to automatly collaps all endpoint groups in the interface
SwaggerUI Picture
here is my code to generate my interface
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
AddCORS(builder);
AddDatabase(builder);
AddServices(builder);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
var API_NAME = Assembly.GetExecutingAssembly().GetName().Name;
var xmlPath = $"{AppContext.BaseDirectory}{API_NAME}.xml";
c.SwaggerDoc("v1", new OpenApiInfo
{
Version = "v1",
Title = API_NAME,
Description = "API for Guanajuato RolePlay"
});
c.IncludeXmlComments(xmlPath);
});
var app = builder.Build();
Add this code.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI(o =>
{
o.DocExpansion(Swashbuckle.AspNetCore.SwaggerUI.DocExpansion.None);
});
}
it will give you result like this.

Swashbukle doesn't show the Odata routes in the Swagger UI

I am using Net6 web api with odata support. I am not using any apicontroller in the code and instead i am inheriting from ODataController and swagger UI is not showing the routes in the UI and event i am not able to browse those endpoints separately. Below is my samplecode
public class ValuesController : ODataController
{
[EnableQuery(PageSize = 5)]
public IQueryable<Note> Get()
{
return _context.Notes.AsQueryable();
}
}
Middleware configuration
builder.Services.AddControllers()
.AddOData(opt =>
{
opt.Conventions.Remove(opt.Conventions.OfType<MetadataRoutingConvention>()
.First());
opt.AddRouteComponents(GetEdmModel())
.Select()
.Expand()
.Count()
.Filter()
.OrderBy().SetMaxTop(100).TimeZone = TimeZoneInfo.Utc;
}).AddNewtonsoftJson(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("Notes",
new Microsoft.OpenApi.Models.OpenApiInfo { Title = "Notes API", Version = "v1", });
});
Please note I have one controller with same config and it is showing in the swagger UI, if I add new controllers inheriting from ODataController it is not working. any help appreciated.
Thanks,
Suresh

How to create Web API that can be imported into Azure API Management Portal

so I have fiddled around a bit with Azure API Management Portal. I have followed the tutorial on how the import the conference api and managed to get it to work.
Then I created a WebApi app that uses swagger. My configuration is as follows:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "My API", Version = "v1" });
});
...
}
public void Configure(IApplicationBuilder app,
IServiceProvider services,
IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Address Service API");
});
app.UseHttpsRedirection();
app.UseMvc();
}
If I run this and navigate to https://my-api/swagger, I can see the swagger UI and I can also see the specification when I click on the link on the swagger UI or visit the url https://my-api.azurewebsites.net/swagger/v1/swagger.json
So my problem is, I have no idea on how to actually import this into AAMP. I can publish it to a app service and it works from there, but if I try to import the url https://my-api.azurewebsites.net/swagger/v1/swagger.json into the AAMP, I get an error:
So I wait an hour and try again, only the be greeted by the same error and I think I am missing something because when I imported the conference api specification, it had a different url than mine, yet I cannot find anything or I am searching for the wrong things. Can anybody please give me a heads up here?
I have also tried searching for the sources of the conference API so I can deduct what I am doing wrong but I didn't have any luck on finding those.
Importing Swagger document into APIM is pretty straight forward by following this Azure document. There’s no issue when you import Swagger 1.2 documents. However, if you’re intending to import Swagger 2.0 ones, you might be facing these kind of issue
If you’re building an API app with .NET Framework 4.5+, using Swashbuckle library, it would be fine. However, if you’re building the app with ASP.NET Core, it does bring you a headache. Firstly, look at your Startup.cs file. The ConfigureService method looks like:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
...
services.AddSwaggerGen();
services.ConfigureSwaggerDocument(
options =>
{
options.SingleApiVersion(new Info() { Version = "v1", Title = "Swagger UI" });
options.IgnoreObsoleteActions = true;
options.OperationFilter(new ApplyXmlActionComments(GetXmlPath(appEnv)));
});
services.ConfigureSwaggerSchema(
options =>
{
options.DescribeAllEnumsAsStrings = true;
options.IgnoreObsoleteProperties = true;
options.CustomSchemaIds(type => type.FriendlyId(true));
options.ModelFilter(new ApplyXmlTypeComments(GetXmlPath(appEnv)));
});
...
}
private static string GetXmlPath(IApplicationEnvironment appEnv)
{
var assembly = typeof(Startup).GetTypeInfo().Assembly;
var assemblyName = assembly.GetName().Name;
var path = $#"{appEnv.ApplicationBasePath}\{assemblyName}.xml";
if (File.Exists(path))
{
return path;
}
var config = appEnv.Configuration;
var runtime = $"{appEnv.RuntimeFramework.Identifier.ToLower()}{appEnv.RuntimeFramework.Version.ToString().Replace(".", string.Empty)}";
path = $#"{appEnv.ApplicationBasePath}\..\..\artifacts\bin\{assemblyName}\{config}\{runtime}\{assemblyName}.xml";
return path;
}
In addition to this, the Configure method might look like:
public void Configure(IApplicationBuilder app)
{
...
app.UseSwaggerGen();
app.UseSwaggerUi();
...
}
Wen need to include two additional properties – host and schemes. Swagger specification clearly declares that both are NOT required. However, APIM DOES require both properties to be included in the swagger.json document.
So, how can we sort this out?
For your app in .NET 4.5+, just make sure that your SwaggerConfig.cs has activated those options with proper settings:
SwaggerDocsConfig.Schemes(new[] { “http”, “https” });
SwaggerDocsConfig.RootUrl(req => GetRootUrlFromAppConfig());
In your ASP.NET Core app, it might be tricky as you should implement the IDocumentFilter interface. Here’s a sample code:
public class SchemaDocumentFilter : IDocumentFilter
{
public void Apply(SwaggerDocument swaggerDoc, DocumentFilterContext context)
{
swaggerDoc.Host = "localhost:44321";
swaggerDoc.BasePath = "/";
swaggerDoc.Schemes = new List<string>() { "https" };
}
}
And this SchemaDocumentFilter should be added into your ConfigureService method in Startup.cs:
public static void ConfigureServices(this IServiceCollection services)
{
...
services.AddSwaggerGen();
services.ConfigureSwaggerDocument(
options =>
{
options.SingleApiVersion(new Info() { Version = "v1", Title = "Swagger UI" });
options.IgnoreObsoleteActions = true;
options.OperationFilter(new ApplyXmlActionComments(GetXmlPath(appEnv)));
options.DocumentFilter<SchemaDocumentFilter>();
});
services.ConfigureSwaggerSchema(
options =>
{
options.DescribeAllEnumsAsStrings = true;
options.IgnoreObsoleteProperties = true;
options.CustomSchemaIds(type => type.FriendlyId(true));
options.ModelFilter(new ApplyXmlTypeComments(GetXmlPath(appEnv)));
});
...
}
Once you complete this, then import your swagger.json to APIM then it should work.
Reference:
Hope it helps.

Swagger UI showing "Fetch error Not Found"

in my asp.net core 2.1 api I have added static files serving and Swashbuckle, but apparently can't find the generated .json
services.AddSwaggerGen(x =>
x.SwaggerDoc("Swagger", new Info { Title = "Asp.Net Core 2 Api", Description = "Swagger Core Api" }));
----
app.UseCors("AllowAll");
app.UseAuthentication();
app.UseStaticFiles();
app.UseSwagger();
app.UseSwaggerUI(x => x.SwaggerEndpoint("/swagger/swagger.json", "Core Api"));
any idea? thanks
Your swagger end-point has the wrong Url, hence the 'Not Found' Error.
Swagger documents constructed via the default way have their url in the format /swagger/<document-name>/swagger.json where is the name (first string) parameter supplied in the call to x.SwaggerDoc("Swagger", new Info {...});
In your case you supplied "Swagger", hence your endpoint url should be "/swagger/Swagger/swagger.json".
and in your ConfigureServices your call should be like
app.UseSwaggerUI(x => x.SwaggerEndpoint("/swagger/Swagger/swagger.json", "Core Api"));
After publishing the application on IIS,
I had this same problem, here is how I solved it.
In the Startup.cs file Add in the public void ConfigureServices(IServiceCollection services)... method
services.AddSwaggerGen();
services.AddSwaggerGen(options =>
{
options.SwaggerDoc("OpenAPISpec",
new OpenApiInfo()
{
Title = "Your Title here",
Version = "1",
Description = "A simple example ASP.NET Core Web API",
TermsOfService = new Uri("https://example.com/terms"),
Contact = new OpenApiContact()
{
Email = "youremail#gmail.com",
Name = "Jean Fritz DUVERSEAU",
Url = new Uri("https://www.rezo509.com")
}
});
var xmlCommentFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlCommentsFullPath = Path.Combine(AppContext.BaseDirectory, xmlCommentFile);
options.IncludeXmlComments(xmlCommentsFullPath);
});
and then in the public void Configure(... method
app.UseSwagger();
app.UseSwaggerUI(s =>
{
string swaggerPath = string.IsNullOrWhiteSpace(s.RoutePrefix) ? "." : "..";
s.SwaggerEndpoint($"{swaggerPath}/swagger/OpenAPISpec/swagger.json", "Demo API");
});
After that execute the project, then in the browser modify the url as follows:
http (s): //yourserver.com:[port]/swagger/index.html
if you dont add the correct url you will have HTTP ERROR 404
And after publication on the IIS server
Long story short: you must check the path prefix difference on your environments and reflect this difference in SwaggerEndpoint([different path]) option.
One of convenient ways is to automate this difference is to put this selection to #if DEBUG section
this worked for me
`
public void ConfigureServices(IServiceCollection services)
{
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo
{
Title = "Test API",
Version = "v1",
Description = "List of Api's.",
Contact = new OpenApiContact
{
Name = "Test site",
Email = string.Empty
},
});
});
services.AddCors();
services.AddHttpContextAccessor();
services.AddSingleton<Microsoft.AspNetCore.Http.IHttpContextAccessor, HttpContextAccessor>();
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("v1/swagger.json", " API V1");
c.RoutePrefix = "swagger";
});
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
`
Try copy pasting your application link om a browser
e.g. http://localhost/testsite/swagger/v1/swagger.json
compare it with the link in the error.
Tweak the url in c.SwaggerEndpoint accordingly

Resources