Swagger UI setup in Web Api for endpoint and class documentation - c#

I have a solution in VS2013 with several class libraries and a Web API project. I am running into a few problems when setting up Swagger UI. First, when I setup swashbuckle for my Web API project, I can only point to one documentation XML file. Is there a way to point to include multiple XML files so that Swagger not only picks up documentation for my routes in controller but also the domain objects from my other projects as well? Here is my code from SwaggerConfig.cs
SwaggerSpecConfig.Customize
(
c =>
{
c.IncludeXmlComments(Path.Combine(dirPath, projName + ".xml"));
}
);
If I add multiple XML files, it only picks up the last file from IncludeXmlComments.
Second, I am using camel case for my DTOs when returning in JSON
formats.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
However when I look at the response model and model schema in Response Class in Swagger UI, I see the exact class property names instead of the JSON schema which is returned when an endpoint is hit. Is there a way to show the exact JSON schema in the Swagger UI documentation page?

I am using version 5.6.0 and multiple XML docs works for me:
var dir = new DirectoryInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin"));
foreach (var fi in dir.EnumerateFiles("*.xml"))
{
c.IncludeXmlComments(fi.FullName);
}

to add multiple XML File configure swagger like that.
SwaggerSpecConfig.Customize(c =>
{
// single XML Comment Files
//c.IncludeXmlComments(GetXmlCommentsPath());
// Multiple XML Comment Files
string[] paths = GetXmlCommentsPaths();
foreach (string xmlCommentsPath in paths)
{
c.OperationFilter(new ApplyActionXmlComments(xmlCommentsPath))
.ModelFilter(new ApplyTypeXmlComments(xmlCommentsPath));
}
});

After version 4.1 you can write this:
c.IncludeXmlComments("File1_Path");
c.IncludeXmlComments("File2_Path"));
See also here.

Related

Get Swashbuckle/Swagger route programmatically

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.

HealthReport object structure produced by health checks endpoint freezes Swagger docs page

I enabled health checks for my .Net 5 Web API project
public sealed class Startup
{
public void ConfigureServices(IServiceCollection serviceCollection)
{
serviceCollection.AddHealthChecks();
// ...
}
public void Configure(IApplicationBuilder applicationBuilder, IWebHostEnvironment webHostEnvironment)
{
// ...
applicationBuilder.UseHealthChecks("/health");
// ...
}
}
Unfortunately the health endpoint does not appear in the swagger docs. That's why I created an additional controller
[ApiController]
[Route("[controller]")]
public sealed class HealthController : ControllerBase
{
private readonly HealthCheckService _healthCheckService;
public HealthController(HealthCheckService healthCheckService)
{
_healthCheckService = healthCheckService;
}
[HttpGet]
public async Task<ActionResult<HealthReport>> GetHealthIndication()
{
HealthReport healthReport = await _healthCheckService.CheckHealthAsync();
if (healthReport.Status == HealthStatus.Healthy)
{
return Ok(healthReport);
}
int serviceUnavailableStatusCode = (int) HttpStatusCode.ServiceUnavailable;
return StatusCode(serviceUnavailableStatusCode, healthReport);
}
}
When running the application Swagger generated a lot of models. When opening the health endpoint the page freezes for multiple seconds because it has to load the HealthReport object structure.
I think it is not fine that an API with a single endpoint freezes because of this ... any suggestions for this?
Do I even have to create my own controller, aren't there any integrations yet? I'm looking for a solution like .UseHealthChecks(HealthController.GetHealthIndication)
TLDR;
Add schema mapping for the Exception class in Swagger configuration.
services.AddSwaggerGen(c =>
{
c.MapType<Exception>(() => new OpenApiSchema { Type = "object" });
});
This code would convert Schema with Exception class as an object, thus the behavior you are facing at the client-side would minimize.
Explanation:
Root Cause:
Swagger freezes because the swagger javascript client is trying to create a sample model based on schema fetched from swagger json.
If you look at the Exception class, it contains a large number of nested properties and types. If we instruct the swagger generator to treat the Exception class as a simple object in a created JSON file, the client would not have to spend time creating nested objects. (or you can include TimeSpan class in this conversion as well).
There are multiple ways to solve this issue:
As mentioned above simply instruct the Swagger generator to map type at config level itself.
If you want to create schema based on specific Action (Operation in Swagger context) or Controller (Document in Swagger context), then you can implement respective filters and modify Swagger Document content on the fly. https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/master/README.md#override-schema-for-specific-types
Create your own Response model: This makes sense because over time schema of HealthReport could change and thus the output of service could change without realizing it over longer time.
Do I even have to create my own controller, aren't there any integrations yet? I'm looking for a solution.UseHealthChecks(HealthController.GetHealthIndication)
Swashbuckle (Swagger codegen and UI) uses Reflection and Assembly documentation (.xml files) to create swagger documents, thus it is won't scan other assemblies for controllers.

Multiple API endpoints based on Modules

I am using ASP.NET Boilerplate v2.0.1 with .NET Core and Angular 2 project template.
I have multiple modules in my project that are currently served using same API endpoint. Please note, in the future I will have separate API endpoint for an individual module.
As the API endpoint is same for every module, the service proxy which is generated through NSwag will have all the modules' services.
I am having a requirement to have proxy generated based on an individual module, so for this, I thought of accomplishing using API versioning. So instead of v1, v2 etc., I will append the module name.
I have the following code:
Configuration.Modules.AbpAspNetCore()
.CreateControllersForAppServices(
typeof(Module).GetAssembly(),
"modulename"
);
services.AddSwaggerGen(options =>
{
options.SwaggerDoc("module1", new Info { Title = "Module 1", Version = "module1" });
options.SwaggerDoc("module2", new Info { Title = "Module 2", Version = "module2" });
}
app.UseSwagger(options =>
{
options.RouteTemplate = "swagger/{documentName}/swagger.json";
});
// Enable middleware to serve swagger-ui assets (HTML, JS, CSS etc.)
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/module1/swagger.json", "Module 1");
options.SwaggerEndpoint("/swagger/module2/swagger.json", "Module 2");
});
This way, I am able to generate two endpoints as follows:
http://localhost:5000/swagger/module1/swagger.json
http://localhost:5000/swagger/module2/swagger.json
But both are having all the modules' information.
Please correct me or suggest a way to achieve the functionality.
It looks like you are using swashbuckle and not NSwag to generate your swagger docs. There are multiple ways to separate your docs by version, see swashbuckle documentation https://github.com/domaindrivendev/Swashbuckle.AspNetCore. The default method is to use your startup configs that you have above and decorate your methods with the corresponding ApiExplorer group name. The group name needs to match the first argument specified in the swaggerdoc config.
[ApiExplorerSettings(GroupName = "module1")] //Module 1 Method
You are also missing an 'e' in module for the second swagger endpoint.
see example: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/4bbf5cacd6ad0817b9015d699c559fd5c1cedf0d/test/WebSites/MultipleVersions/Startup.cs
To add multiple source edit the SwaggerUIOptions.
For example:
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("http://<ip1>:<port>/swagger/v1/swagger.json", "ServiceName1");
c.SwaggerEndpoint("http://<ip2>:<port>/swagger/v1/swagger.json", "ServiceName2");
});

WebApi & Odata - suitable for file management?

I am moving most / all of my API in a project over to Odata from "pure" WCF and using an OWIN hosted Odata enpoint for this.
The one element I am stuck with at the moment are files. I have 2 areas where I need to upload a ZIP file to the server for processing. In one case that is attached to an Entity (called a "Repository") and contains binary content that is not exposed via Odata (it is just uploaded). In the other hand this is for an unbound action and the ZIP file contains configuration files which will crate/change a number of entities.
Is that feasible with OData, or should I ignore Odata for this and go with "Manually configured" standard endpoints? I really would love to keep this in Odata due to the exposed metadata.
Before anyone comments - I have been trying to find documentation via google, but I keep getting no relevant answers. The answers I do get indicate this is possible, but all have code examples that point to the old WCF level API, while I use WebApi. The documentation at http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/odata-v4/create-an-odata-v4-endpoint does not go into too many details. It does not show the allowed types for a Parameter configuration for an action and how to configure it to accept the file via http post from a web form (and a client because I will need both).
Here is a useful link with documentation about Media Resource Support for OData in Web API: https://blogs.msdn.microsoft.com/mrtechnocal/2013/10/31/media-resource-support-for-odata-in-web-api/
You can simplify a little bit the implementation proposed in the link, but for sure, you will need to:
Create an OData Media Resource controller. It can be simpler than the one proposed in the document. See below.
Create a custom EntityRoutingConvention to route properly to the Get methods that will return the ZIP files (in case you have that use case, if you only need to POST them, you may not need the custom routing convention).
So, for the controller, you can have:
public abstract class YourMediaResourceODataController<TEntity, TKey>
: ODataController where TEntity : class
{
}
And then the real controller:
public class YourController : YourMediaResourceODataController<YourZIPObjectEntity, string>
{
// This would be the post
public async Task<IHttpActionResult> Post()
{
var stream = await Request.Content.ReadAsStreamAsync();
// Manage the stream
}
// The get (if you want it, you will need to code the custom EntityRoutingConvention).
[HttpGet]
public HttpResponseMessage GetMediaResource(string key)
{
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
var theZIPFile = yourZIPFileService.GetZIPFileByKey(key);
StreamContent contentResult;
using(var ms = new MemoryStream(theZIPFile.theByteArray)
{
contentResult = new StreamContent(ms);
}
result.Content = contentResult;
return result;
}
}
You will need to have an entity YourZIPObjectEntity with a Stream/byte[]/string property, depending on how you manage the binary file. (In the documentation example this is the Image class). And for that entity you will need to specify that it has a stream in the ODataConfig (see the "Setting Up the Web API Configuration" section in the documentation).
I think that is pretty much all.
Then, you can, from code, POST your ZIP files as a StreamContent:
using(var requestContent = new MemoryStream(yourByteArray))
using(var request = new HttpRequestMessage(HttpMethod.POST, yourPOSTUri)
{
request.Content = new StreamContent(requestContent);
// Set headers and send the request...
}
I hope this is the solution you are looking for, or at least an approach to it.
For files with simple binary content you could use WebApi rather than OData. Unless there's a repository of files that you want to serve to the consumer.
If you must upload zip files and processing them manually in order to modify entities you don't need to use OData either. However WebApi OData does provide batch transactions support. You could follow this tutorial: https://blogs.msdn.microsoft.com/webdev/2013/11/01/introducing-batch-support-in-web-api-and-web-api-odata/
Again if you have large batches rather than sending zip files use gZip compression. Here's a neat post about WebApi gZip support: https://damienbod.com/2014/07/16/web-api-using-gzip-compression/

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.

Categories

Resources