Get Swashbuckle/Swagger route programmatically - c#

I'm using Swashbuckle with ASP.NET Core 6. It serves Swagger at /swagger.
For diagnostic purposes I need to detect that path programmatically at runtime - i.e. assuming swashbuckle is serving swagger at /swagger, then I want to be able to get that path.
I've tried various approaches, but they just give me my own routes. I can't find a way to determine routes added by swashbuckle.
I tried:
// inject IEnumerable<EndpointDataSource>
// ...
var routes = _endpointDataSources.SelectMany(source => source.Endpoints);
and:
var routes = app.Services.GetRequiredService<IActionDescriptorCollectionProvider>()
.ActionDescriptors
.Items.Where(x => x.AttributeRouteInfo != null)
.Select(x => x.AttributeRouteInfo?.Template);
In both cases it enumerates all of my routes - whether they are from RazorPages or WebApi. But the swagger route is not included, even though it is running.
How do I get that path?

Based on #lonix comment, Converting to Ans.
In case UseSwagger(), it does not create any endpoint but a middleware so when you call "/swagger", it checks request for swagger document and returns swagger document if found true. but in case of MapSwagger() it actually creates endpoint.
and yes you can swap it without breaking anything.

Related

Is there a way to use the 'AllowAnyOrigin' property on a CorsPolicy within .NET Core?

I'm currently working on integrated Cors within a .Net Core app. I have previously dealt with Cors within the full .NET Framework (4.6.1) which had the ability to set the AllowAnyOrigin property within a given CorsPolicy.
Like mentioned before, I have written a .NET Core WebApi, which I am trying to implement Cors into. I have it reading from an AppSetting stored in my appSettings.json, from which it can be either three things:
If the appsetting is set to an asterisk, then I would like to see the AllowAnyOrigin property like I have done in the full .NET framework. (This is where my problem lies.)
If the appsetting is set to a comma seperated string, such as https://example.com, https://test.com, then this is applied onto the policy as needed.
If the appsetting has been commented out or left as empty, then I am reading a list of rows from Azure Table Storage to supply a list of given origins.
Below is an example of the full .NET framework that I have dealt with before.
var origins = ConfigurationManager.AppSettings[KeyCorsAllowOrigin];
switch (origins)
{
case null:
_corsPolicy.Origins.Clear();
foreach (var item in new StorageConfigurationManager().GetRowKeys())
{
_corsPolicy.Origins.Add("https://" + item);
}
break;
case "*":
_corsPolicy.AllowAnyOrigin = true;
break;
default:
_corsPolicy.AllowAnyOrigin = false;
if (!string.IsNullOrWhiteSpace(origins)) AddCommaSeparatedValuesToCollection(origins, _corsPolicy.Origins);
break;
}
I thought that I could replicate this functionality within .NET Core and the Microsoft.AspNetCore.Cors.Infrastructure package, but it seems that Microsoft has restricted access to setting the property, and can only be read from.
Does anyone know of any way to set this?
I know you can build the CorsPolicy within the pipeline, which then uses the .AllowAnyOrigin(), but I am currently using custom Cors middleware to help with my custom policy.
There is a great article called Enabling CORS in ASP.NET Core I'll sum the interesting part for you:
to only allow GET methods on your resource, you can use the WithMethods method when you define the CORS policy:
services.AddCors(options =>
{
options.AddPolicy("AllowOrigin",
builder => builder.WithOrigins("http://localhost:55294")
.WithMethods("GET"));
});
If you need to allow any origin to access the resource, you will use AllowAnyOrigin instead of WithOrigins:
services.AddCors(options =>
{
options.AddPolicy("AllowOrigin",
builder => builder.AllowAnyOrigin());
});
Just as an answer to all this, and so that perhaps someone can be helped by this question. I looked at the source code for the AllowAnyOrigin method within the CorsPolicyBuilder class and saw how this was handled. I nearly had the solution, just forgot to clear the Origins list beforehand.
_policy.Origins.Clear();
_policy.Origins.Add(CorsConstants.AnyOrigin);

How can I make url path in Swashbuckle/Swaggerwork when api is served from inside another project?

all. I am trying to document a WebApi 2 using Swashbuckle package.
All works great if the API is running by itself i.e. localhost/api/swagger brings me to ui and localhost/api/swagger/docs/v1 to json.
However the producation app initializes this same Webapi project by running webapiconfig method of this project from global.asax.cs in another - now web project (the main application one). So the api url looks like localhost/web/api instead of localhost/api.
Now swashbuckle doesn't work like that at all.
localhost/api/swagger generates error cannot load
'API.WebApiApplication', well of course
localhost/web/swagger = 404
localhost/web/api/swagger = 404
I tried to look everywhere, but all I found is workaround.
c.RootUrl(req => req.RequestUri.GetLeftPart(UriPartial.Authority) + VirtualPathUtility.ToAbsolute("~/").TrimEnd('/'));
Unfortunately it doesn't work, now maybe it should and I just need to change something but I don't even know what exactly this property expects and what it should be set to.
May be it's not even applicable - maybe setup we have requires something else or some swashbuckle code changes.
I will appreciate any help you can provide. I really starting to like swagger (and swashbuckle) for rest documentation.
For Swashbuckle 5.x:
This appears to be set by an extension method of httpConfiguration called EnableSwagger. Swashbuckle 5.x migration readme notes that this replaces SwaggerSpecConfig. SwaggerDocConfig RootUrl() specifically replaces ResolveBasePathUsing() from 4.x.
This practically works the same as it did before, looks like the biggest change was that it was renamed and moved into SwaggerDocConfig:
public void RootUrl(Func<HttpRequestMessage, string> rootUrlResolver)
An example from the readme, tweaked for brevity:
string myCustomBasePath = #"http://mycustombasepath.com";
httpConfiguration
.EnableSwagger(c =>
{
c.RootUrl(req => myCustomBasePath);
// The rest of your additional metadata goes here
});
For Swashbuckle 4.x:
Use SwaggerSpecConfig ResolveBasePathUsing and have your lambda read your known endpoint.
ResolveBasePathUsing:
public SwaggerSpecConfig ResolveBasePathUsing(Func<HttpRequestMessage, string> basePathResolver);
My API is behind a load balancer and this was a helpful workaround to providing a base address. Here's a dumb example to use ResolveBasePathUsing to resolve the path with a known base path.
string myCustomBasePath = #"http://mycustombasepath.com";
SwaggerSpecConfig.Customize(c =>
{
c.ResolveBasePathUsing((req) => myCustomBasePath);
}
I hardcoded the endpoint for clarity, but you can define it anywhere. You can even use the request object to attempt to cleanup your request uri to point to /web/api instead of /api.
The developer commented on this workaround on GitHub last year:
The lambda takes the current HttpRequest (i.e. the request for a given
Swagger ApiDeclaration) and should return a string to be used as the
baseUrl for your Api. For load-balanced apps, this should return the load-balancer path.
The default implementation is as follows:
(req) => req.RequestUri.GetLeftPart(UriPartial.Authority) + req.GetConfiguration().VirtualPathRoot.TrimEnd('/');
...
Re relative paths, the Swagger spec requires absolute paths because
the URL at which the Swagger is being served need not be the URL of
the actual API.
...
The lambda is passed a HttpRequestMessage instance ... you should be able to use this to get at the RequestUri etc. Another option, you could just place the host name in your web.config and have the lambda just read it from there.

Attribute based webapi2 routing returns 404 for some methods

I'm presently working on a project that has been upgraded to Webapi2 from Webapi. Part of the conversion includes the switch to using attribute based routing.
I've appropriately setup my routes in the Global.asax (as follows)
GlobalConfiguration.Configure(config => config.MapHttpAttributeRoutes());
and removed the previous routing configuration.
I have decorated all of my API controllers with the appropriate System.Web.Http.RouteAttribute and System.Web.Http.RoutePrefixAttribute attributes.
If I inspect System.Web.Http.GlobalConfiguration.Configuration.Routes with the debugger I can see that all my expected routes are registered in the collection. Likewise the appropriate routes are available within the included generated Webapi Help Page documentation as expected.
Even though all appears to be setup properly a good number of my REST calls result in a 404 not found response from the server.
I've found some notable similarities specific to GET methods (this is all I've tested so far)
If a method accepts 0 parameters it will fail
If a route overrides the prefix it will fail
If a method takes a string parameter it is likely to succeed
return type seems to have no affect
Naming a route seems to have no affect
Ordering a route seems to have no affect
Renaming the underlying method seems to have no affect
Worth noting is that my API controllers appear in a separate area, but given that some routes do work I don't expect this to be the issue at hand.
Example of non-functional method call
[RoutePrefix("api/postman")]
public class PostmanApiController : ApiController
{
...
[HttpGet]
[Route("all", Name = "GetPostmanCollection")]
[ResponseType(typeof (PostmanCollectionGet))]
public IHttpActionResult GetPostmanCollection()
{
return Ok(...);
}
...
}
I expect this to be available via http://[application-root]/api/postman/all
Interestingly enough a call to
Url.Link("GetPostmanCollection", null)
will return the above expected url
A very similar example of method calls within the same controller where some work and some do not.
[RoutePrefix("api/machine")]
public class MachineApiController : ApiController
{
...
[HttpGet]
[Route("byowner/{owner}", Name = "GetPostmanCollection")]
public IEnumerable<string> GetByOwner([FromUri] string owner)
{
...
}
...
[HttpGet]
[Route("~/api/oses/{osType}")]
public IEnumerable<OsAndVersionGet> GetOSes([FromUri] string osType)
{
...
}
...
}
Where a call to http://[application-root]/api/machineby/ownername succeeds and http://[application-root]/api/oses/osType does not.
I've been poking at this far too long, any idea as to what the issue may be?
Check that you configure your HttpConfiguration via the MapHttpAttributeRoutes method before any ASP.NET MVC routing registration.
In accordance to Microsoft's CodePlex entry on Attribute Routing in MVC and Web API the Design section states:
In most cases, MapHttpAttributeRoutes or MapMvcAttributeRoutes will be
called first so that attribute routes are registered before the global
routes (and therefore get a chance to supersede global routes).
Requests to attribute routed controllers would also be filtered to
only those that originated from an attribute route.
Therefore, within the Global.asax (or where registering routes) it is appropriate to call:
GlobalConfiguration.Configure(c => c.MapHttpAttributeRoutes()); // http routes
RouteTable.Routes.MapRoute(...); // mvc routes
In my case it was a stupid mistake, I am posting this so people behind me making the same mistake may read this before they check everything else at quantum level.
My mistake was, my controller's name did not end with the word Controller.
Happy new year

Remove Route from HttpRouteCollection

I am using the attribute routing included in WebAPI 2.0, but cannot figure out how to remove a route based on certain conditions. I map all routes using the MapHttpAttributeRoutes, and then I would like to remove a specific route using the next line of code.
// enable attribute routing support
httpConfiguration.MapHttpAttributeRoutes();
// expose the flag routes only if required
if (DisableFlagEndpoint)
{
httpConfiguration.Routes.Remove(FlagsController.RouteName);
}
But this throws a NotSupportedException. How does one remove a route ? If not, is there another way to achieve this ?
Looks like WebAPI 2.1 introduces the ability to do this with IgnoreRoute(). http://www.asp.net/web-api/overview/releases/whats-new-in-aspnet-web-api-21#ignoreroute
// disable the flag routes if required
if (DisableFlagEndpoint)
{
httpConfiguration.Routes.IgnoreRoute("Flags", "api/flags/{*paths}");
}
// enable attribute routing support
httpConfiguration.MapHttpAttributeRoutes();

customize data annotation in webapi?

right now I am doing WebAPI and as we know each controller will have its own url. Do have option to change those url what ever we like. Per example below urls are current Web API.
Do we really want to use mixed case in urls?
- http://localhost:8282/api/encode/
- http://localhost:8282/api/techdisciplines/
- http://localhost:8282/api/memstatus/
- http://localhost:8282/api/isstaff/
Want to change them to below url with changing major code effect. Just changing data annotation.
- http://localhost:8282/api/cus/encode/<customer id>
- http://localhost:8282/api/cus/disciplines/<token>
- http://localhost:8282/api/cus/mem_status/<token>
- http://localhost:8282/api/cus/is_staff/<token>
Like Java has which automatically find the method.
#Path("customer/{i_Constit}/subscriptions")
public Response getSubscriptions(#PathParam("i_Constit") String customerId)
{
....
...
You can use Attribute Routing in Web API 2: http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2

Categories

Resources