ASP.net Core web API Swagger UI version field - Is it possible to set this value in code? - asp.net-core

I have a ASP.Net Core Web API with Swagger configured that shows the API End Points.Also API Versioning is enabled. However, the swagger UI is not populating the mandatory field Version when checking the End Point.See image below:
Is it possible to populate this field automatically by code given that the API Action already configures this value i.e. the MaptoApiVersion. In theory this field should be populated automatically??
[MapToApiVersion("2")]
[HttpGet("GetV2")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<IEnumerable<TodoDto>> GetV2()
{
var query = new AllTodosQuery(_context);
var todos = await query.ExecuteAsync();
return todos;
}

The issue is at least two-fold. First, the extensions to the API Explorer from API Versioning do provide the version parameter with a default value, but many Swagger/OpenAPI generators (such as Swashbuckle) still do not yet honor it. If you want to enable this behavior, you need a custom IOperationFilter which does something to the effect of:
var parameter = operation.Parameters.First(p => p.Name == "version");
var description = context.ApiDescription.ParameterDescriptions.First(p => p.Name == "version");
if (parameter.Schema.Default == null && description.DefaultValue != null)
{
parameter.Schema.Default = new OpenApiString(description.DefaultValue.ToString());
}
You can find a complete end-to-end example in the API Versioning repo in:
SwaggerDefaultValues.cs
Since you're versioning by URL segment, if you want that inlined into the route template without a corresponding parameter, you need only configure the API Explorer extensions to do so like this:
services.AddVersionedApiExplorer(options => options.SubstituteApiVersionInUrl = true);
This option only applies to the URL segment versioning method.
A complete end-to-end Swashbuckle example with API Versioning can be found inside Startup.cs inside the repo.

Related

How to effectively set the ServerUrl in the NSwag settings?

I have a working ASP .NET 5 application with a REST API and a Swagger interface using the NSwag library (so not Swashbuckle as many people use instead). I have found (or at least I have thought so) a way to set the server url. There is a property to set this. Here is my code:
app.UseSwaggerUi3(settings =>
{
settings.ServerUrl = "https://XXXXX.YYYYY.com/ZZZZ";
settings.TransformToExternalPath = (url, request) =>
{
// Get the UI to properly find the relative path of the swagger json instead of absolute path.
string outputUrl;
if (url.EndsWith(".json") || request.Path.ToString().EndsWith("/")) outputUrl = ".." + url;
else outputUrl = request.PathBase + "." + url;
return outputUrl;
};
});
However, when running my application and using the swagger interface, the set server url is not used at all... It simply refers to same host as it does without setting it. I know it is possible to change the routing behavior of swagger when needed (as explained here and I also used this code) but that basically solves the problem of not being able to find the swagger json. However, I did not find a way to effectively set the server url. When I use the code shown in this post, the request url when executing a request refers to the same url when not using it.
How can I fix this? How to effectively set the server url? This is so strange. I looks like the property does not do anything at all.
For me it seems to work well using the Host property in PostProcess. I set it to null to get the UI working for all incoming host names.
I'm on Owin version but same looks like it should work in 5/6 as well.
RouteTable.Routes.MapOwinPath("swagger", app =>
{
..
app.UseSwaggerUi3(typeof(Global).Assembly, c => {
// Fix to make UI work regardless of hostname serving
c.PostProcess = document => {
document.Host = null;
};
});
});
The ServerUrl property looks like it's only used as RedirectUrl in an eventual OAuth2 flow.

Get Swagger URL programmatically in ASP.NET Core

I'm using ASP.NET Core 5, and Swagger. I know how to use Swagger, and it works properly.
Swagger is served on foo:5001/swagger - but I need to determine that URL programmatically at runtime.
How can I do that?
I already tried:
Getting it by injecting IEnumerable<EndpointDataSource> into some helper/controller class, but that shows me all routes EXCEPT swagger's.
Getting it while setting up endpoint routing and inspecting IEndpointRouteBuilder, but once again it shows me all routes EXCEPT swagger's.
According to sources at https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/master/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIMiddleware.cs you can use an instance of class SwaggerUIOptions:
Register instance in DI container:
var options = new SwaggerUIOptions
{
RoutePrefix = "swagger"
};
options.SwaggerEndpoint("/swagger/v1/swagger.json", "waiting_list v1");
services.AddSingleton(options);
Use configured instance:
app.UseSwaggerUI(app.ApplicationServices.GetRequiredService<SwaggerUIOptions>());
Inject instance to any controller/class:
public WeatherForecastController(ILogger<WeatherForecastController> logger, SwaggerUIOptions swaggerOptions)
{
}
Property RoutePrefix contains swagger prefix (without leading '/')
This idea works only if options object passed to UseSwaggerUI method (available since version 6.0.0). If UseSwaggerUI invoked using callback (like a UseSwaggerUI(a => { a.RoutePrefix = string.Empty; })) it won't work.

Integrate MiniProfiler with .NetCore 3.1

I want to integrate MiniProfiler is a WebApi or View /XX/results-index.
The WebApi is authenticated with Bearer Tokens. I only want Group Users in Active Directory can see the results, but I don't get it.
I have this code in ServicesCollection:
services.AddMiniProfiler(options =>
{
options.RouteBasePath = "/profiler";
options.ResultsAuthorizeAsync = async request => await GetAuthorization(request); }).AddEntityFramework();
private static async Task<bool> GetAuthorization(HttpRequest request)
{
// var user = request.HttpContext.User.Identity.Name; --> Is null
return true;
}
In Configure Method in StartUp:
app.UseSwagger().UseSwaggerUI(options =>
{
options.SwaggerEndpoint($"/swagger/v1/swagger.json", $"{env.ApplicationName} V1");
options.OAuthClientId("TestApiswaggerui");
options.OAuthAppName("TestApi Swagger UI");
options.IndexStream = () => GetType().GetTypeInfo().Assembly.GetManifestResourceStream(
"TestApi.SwaggerMiniProfiler.html");
})
.UseMiniProfiler();
I want to see mini profiler information through some options:
http://localhost:5050/profiler/results-index --> Show the list methods called
http://localhost:5050/swagger/index.html --> Show the MiniProfiler in the same page
Environment:
.NET Core version: 3.1
MiniProfiler version: MiniProfiler.AspNetCore.Mvc v.4.2.1
Operative system: Windows 10
The piece you're probably missing here is that MiniProfiler shows your results. What's "you" is determined by the UserIdProvider option. When recording and viewing profiles, ensure that these are the same "user ID" (defaults to IP address). It looks like this in options:
services.AddMiniProfiler(options =>
{
options.UserIdProvider = request => ConsistentUserId(request);
});
If your swagger has zero server-side processing at all (e.g. it does not include the MiniProfiler <script> tag from .RenderInludes() or the <mini-profiler /> tag helper, then the issue isn't viewing the profiles so much as not even attempting to view. There are some ideas I have around a static tag without profiles to currently view, but I do not know how to get them into Swagger in it's generation phase (just not familiar enough). Note that it's a blatant hack, but you could work around the issue at the moment with a manual script tag. You'll want to follow https://github.com/MiniProfiler/dotnet/issues/326 for this.
I just want to leave the option of having the traces read for that group from the active directory:
services.AddMiniProfiler(options =>
{
// (Optional) Path to use for profiler URLs, default is /mini-profiler-resources
options.RouteBasePath = "/profiler";
options.ColorScheme = StackExchange.Profiling.ColorScheme.Light;
options.PopupRenderPosition = StackExchange.Profiling.RenderPosition.BottomLeft;
options.PopupShowTimeWithChildren = true;
options.PopupShowTrivial = true;
options.ShouldProfile = ShowProfile;
options.SqlFormatter = new StackExchange.Profiling.SqlFormatters.InlineFormatter();
options.ResultsAuthorize = request => request.HttpContext.User.IsInRole("S-INFORMATICA");
})
.AddEntityFramework();

Netcore how to remove endpoints/routes at runtime

Is there a way to remove registered routes in net core web api project?
So I'm dynamically adding controllers in a net core web api project, the controller class code is not part of the project but dynamically loaded, compiled and add to the project at runtime
//code that compiles the c# class(controller)
var compiledAssembly= CompileHelper.Compile(csharpCode)
using (var controllerAssemblyMs = new MemoryStream(compiledAssembly))
{
var assemblyLoadContext = new SimpleAssemblyLoadContext();//inherits AssemblyLoadContext
var dynamicControllers = new MvcAssemblyPart(controllerAssemblyMs);
Services.AddControllersWithViews().ConfigureApplicationPartManager(apm =>
apm.ApplicationParts.Add(dynamicControllers));
}
so any new Endpoints/Routes are registered.
the problem is that because routes have been registered every time it compiles the code, if I change Get action to Post action, compile before and after, the endpoints end up in an erroneous state,
AmbiguousMatchException: The request matched multiple endpoints. Matches:
DynamicCodeProject.Controllers.DynamicallyAddedController.Post (string)
DynamicCodeProject.Controllers.DynamicallyAddedController.Get (string)
in which case I have to restart the application,
is it possible to remove routes/endpoints at runtime so I don't have to restart the application?
I has a same problem. My solution was remove previous controller from apm.ApplicationParts
var parts = _partManager.ApplicationParts.Where((x) => ((AssemblyPart)x).Assembly.GetName().Name == compiledAssembly.GetName().Name).ToList();
foreach(var part in parts)
{
apm.ApplicationParts.Remove(part);
}
apm.AddApplicationPart(compiledAssembly);
this work in asp.net core 5.0

AssumeDefaultVersionWhenUnspecified is not working as expected

I have been using asp net core versioning component for my WebAPI. Need your help in understanding how AssumeDefaultVersionWhenUnspecified is working. (tried searching for documentation, but couldn't find one)
My startup looks like below
services.AddApiVersioning(o => {
o.ReportApiVersions = true;
o.AssumeDefaultVersionWhenUnspecified = true;
o.DefaultApiVersion = new ApiVersion(2, 0);
o.ApiVersionReader = new UrlSegmentApiVersionReader();
});
When the route attribute is something like below
[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/values")]
[ApiController]
public class ValuesV2Controller : ControllerBase
{
...
}
The above route works only when the api version is specified. ie: http://localhost:55401/api/v2/values
If I call like http://localhost:55401/api/values, getting 404 error
My question is this... How AssumeDefaultVersionWhenUnspecified works. Wouldn't it ignore the version in Route? Looks like Route attribute takes precedence over AssumeDefaultVersionWhenUnspecified. If I choose QueryString or Header versioning and when the Route looks like
[ApiVersion("2.0")]
[Route("api/values")]
the default routing reaches the API
Am I missing anything or is my understanding wrong? How shall I achieve default routing to the latest version API using url versioning?
I am also trying to achieve the same functionality. By looking into [https://github.com/Microsoft/aspnet-api-versioning/issues/351#issuecomment-425106940]
I am assuming that we can't achieve default API version AssumeDefaultVersionWhenUnspecified functionality with only a single style of versioning uses a URL segment [Route("api/v{version:apiVersion}/[controller]")]
We have to define two routes separately as follow
[Route("api/[controller]")]
[Route("api/v{version:apiVersion}/[controller]")]
and to hide the two implementations from swagger you can achieve using this link
Summarizing the the solution from the github issue linked by Athi S, here's what you need to do :
In ConfigureServices inside Startup.cs file :
services.AddApiVersioning(o =>
{
o.AssumeDefaultVersionWhenUnspecified = true;
o.ApiVersionSelector = new CurrentImplementationApiVersionSelector(o);
// o.DefaultApiVersion = new ApiVersion(1, 0);
});
You can optionally set ApiVersionSelector to a new instance of CurrentImplementationApiVersionSelector. What this does is, it automatically selects the highest api version registered in controllers. E.g. A controller decorated with [ApiVersion("1.2")] takes precedence over [ApiVersion("1.1")].
If you want to specify default api version explicitly, you can do so by leaving ApiVersionSelector to DefaultApiVersionSelector and setting DefaultApiVersion to your required api version.
In your controllers :
Register the required routes by decorating your controllers with the given Route attributes
[Route("api/[controller]")]
Or if you want the api to work both with and without the api version number specified, you can do so by declaring two routes for the controller.
[Route("api/[controller]")]
[Route("api/v{version:apiVersion}/[controller]")]