GET requests can use model binding to complex types, and for the most part Swashbuckle seems to handle this... as long as you are explicit in flagging properties as FromQuery (which is how ASP.NET binds them anyway... cause, you know... it's a get request). But it isn't pulling XML comments from the complex type correctly.
Consider a complex type in a GET request such as:
/// <summary>
/// Gets a thing, using optional query string criteria
/// </summary>
[HttpGet]
public IActionResult Get(SearchRequest request)
{
}
Where SearchRequest looks like:
public class SearchRequest
{
/// <summary>
/// The item identifier.
/// </summary>
/// <value>The item identifier. Default is "toast".</value>
[FromQuery]
public string ThingId { get; set; } = "toast";
// ... other properties
}
Here, the FromQuery is needed to let swashbuckle/swagger know how to make the call (which I can live with), but the output documentation here will completely ignore the xml comments. There will be no description for the parameter, and so far I've found no good way around this short of a custom filter.
What does work, though Resharper HATES it, is if I add params to the action method like this:
/// <summary>
/// Gets a thing, using optional query string criteria
/// </summary>
/// <param name="ThingId ">The item identifier. The default is "toast".</param>
[HttpGet]
public IActionResult Get(SearchRequest request)
{
}
It will populate the description in swagger doc and the UI, but of course it's a bit sub-optimal. My action method has a bunch of param comments that don't actually map correctly to the method signature... causing my tooling to underline everything and strongly suggest that I fix it... and I'm sure someone on my team will eventually fix it, breaking swagger without even realizing it.
Is there some easier way to tell swagger to map the complex type's comments as parameters? I suppose I could go down the custom filter path, but this seems like a pretty standard usage that the framework should either handle, or provide via a simple SwaggerParamAttribute or something.
Additionally, the parameters generated aren't camelcased, and don't seem to honor JsonProperty either... basically, using a complex type as a parameter for API GET seems pretty well broken all around.
Same problem here, It seems that "modelbinding" parameters are not managed at all.
In addition the "Try it out!" function of swagger ui is not working with modebinding parameters for GET operations because they are not appended as query string parameters, so they are useless.
I managed to fix the problem with a custom OperationFilter that substitutes the type of modelbinder parameters of GET operations to "query" type.
You can partially work around the try it out problem by adding [FromQuery] attributes to all the properties in your model class (assuming you own the code for it anyway).
Thanks for your suggestion, it works.
I noticed the same problem with POST operations and modelbinding parameters.
In this case the "Try it out" doesn't work at all, even if I specify each involved input parameter as "FromForm". Parameters are never appended to request body and Content-Type header is still "application/json" when it should be "application/x-www-form-urlencoded". Is there a way to make it work with form parameters?
@StephenRedd you can use [FromQuery] to parameter itself and not to it's properties like this:
``` C#
///
/// Gets a thing, using optional query string criteria
///
[HttpGet]
public IActionResult Get([FromQuery]SearchRequest request)
{
}
And it will work partially correct (no XML comments for properties unfortunately) but "Try it" works. While `SearchRequest` contains only primitives. For example:
``` C#
/// <summary>
/// Model for searching.
/// </summary>
public class SearchParameters
{
...
/// <summary>
/// Some class.
/// </summary>
public AdditionalParameters Additional{ get; set; }
...
}
Will create valid swagger doc and, as I remember, "Try it" works. May be even some code generator tools will work as designed, but those parameters in swagger will be defined in such way:
{
"name": "Additional.SomeAdditionalParameter",
"in": "query",
"description": "Model for searching.",
"required": false,
"type": "string"
}
because of the way ASP Core uses for serializing and binding query parameters. Such parameter name breaks majority of generators and, of course, XML comments applied incorrectly.
In order to fix this, I've created OperationFilter:
``` C#
public class UseOpenAPI3OperationId : IOperationFilter
{
public void Apply(Operation operation, OperationFilterContext context)
{
var actionDescriptor = ((ControllerActionDescriptor)context.ApiDescription.ActionDescriptor);
var apiParameters = context.ApiDescription.ParameterDescriptions
.Where(paramDesc => paramDesc.Source.IsFromRequest)
.Select(paramDesc => paramDesc)
.ToList();
var controllerParameters = actionDescriptor.Parameters;
// Next if clause discovers operations with complex query parameters
// because they have more API parameters then method parameters in action
if (apiParameters.Count != controllerParameters.Count)
{
operation.Parameters = controllerParameters
.Where(paramDesc => paramDesc.BindingInfo != null)
.Where(paramDesc => paramDesc.BindingInfo.BindingSource.IsFromRequest)
.Select(paramDesc => CreateParameter(paramDesc, context.SchemaRegistry))
.ToList();
operation.Parameters = (operation.Parameters).Any() ? operation.Parameters : null;
}
}
// Actually the same method used in Swashbuckle with very small difference
private IParameter CreateParameter(ParameterDescriptor paramDesc, ISchemaRegistry schemaRegistry)
{
var source = paramDesc.BindingInfo.BindingSource.Id.ToLower();
var schema = (paramDesc.ParameterType == null) ? null : schemaRegistry.GetOrRegister(paramDesc.ParameterType);
// Actually next line is a difference between default CreateParameter method and this override
if (source == "body" || (source == "query" && paramDesc.ParameterType.IsClass))
{
return new BodyParameter
{
Name = paramDesc.Name,
In = source,
Schema = schema
};
}
else
{
var nonBodyParam = new NonBodyParameter
{
Name = paramDesc.Name,
In = source,
Required = (source == "path")
};
if (schema == null)
nonBodyParam.Type = "string";
else
nonBodyParam.PopulateFrom(schema);
if (nonBodyParam.Type == "array")
nonBodyParam.CollectionFormat = "multi";
return nonBodyParam;
}
}
}
Extensions methods for `JsonProperty`, `Schema` and `PartialSchema` was copied without changes from Swashbuckle. This `OperationFilter` will generate invalid for Swagger 2.0 file, because here query parameters may have `shema.$ref` like this:
``` JSON
{
"name": "parameters",
"in": "query",
"description": "Parameters for searching.",
"required": false,
"schema": { "$ref": "#/definitions/SearchParametersModel" }
}
But in OpenAPI 3.0 (next swagger spec) it will be valid (see https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.md#parameterObject) cause all parameters (path, query, body, etc.) will have schema property. I'm using my own client generator from swagger, so I've added support for shema.$ref in query params and with OperationFilter above it works perfect. All XML comments applied correctly, more usable client request (methods with one complex parameter against several primitive params).
Conclusion
@domaindrivendev Is it real to add in Swachbuckle experimental support for OpenAPI 3.0 in such way for example:
``` C#
services.AddSwaggerGen(options =>
{
options.UseOpenAPI3 = true;
});
``` C#
services.AddSwaggerGen(options =>
{
options.UseOpenAPI3Feature.Add(Feature.PathParameters);
});
It will solve many problems.
If yes and this doesn't conflict with your plans/roadmap I would happy to add PR with implementation of this OpenAPI's feature at least. Of course, in more appropriate way than it done by my filter (cause parameters actually won't have type, format etc. and all will be with schema).
I've been looking into this as well. For the case where you want to format properties of complex action parameters in this way (camelcased name, use <summary> tag for parameter description), it seems like you could accomplish it by doing the following:
However, since OperationFilters execute BEFORE ModelFilters, the type you need from the SchemaRegistry has not yet been built.
This controller will reproduce the xml doc and camel case problems described in this thread.
using Microsoft.AspNetCore.Mvc;
namespace Sample.Controllers
{
[Route("sample")]
public class SampleController : Controller
{
[HttpGet]
public void Get([FromQuery] SampleItem item)
{
}
[HttpPost]
public void Post([FromBody] SampleItem item)
{
}
}
public class SampleItem
{
/// <summary>
/// Foo
/// </summary>
public string Foo { get; set; }
/// <summary>
/// Bar
/// </summary>
public string Bar { get; set; }
}
}

@StephenRedd - I've just merged the fix for this. Would be great if you validate through the following preview package on myget: https://www.myget.org/feed/domaindrivendev/package/nuget/Swashbuckle.AspNetCore/1.0.0-preview-0090
Re the camelcasing, although I don't consider this to be a bug (ASP.NET Core will accept the parameters in either case so describing in Pascal isn't wrong), I've added a config option to _DescribeAllParametersInCamelCase_.
It's also worth noting though - if you want to be really explicit about your API contract and make parameters case sensitive at runtime, you can use the _FromUriAttribute.Name_ property. Swashbuckle will automatically honor this when describing the parameter.
public class QueryParams
{
[FromUri("myParam")]
public string MyParam { get; set; }
}
I'll dust off the project I was working on at the time, and update it to the new preview package... probably be a couple of days though.
I appreciate the hard work everyone has put into this. I think the new config option is a great idea; even though there are explicit ways of describing the API, it is helpful when the framework can be configured to just "do the right thing", even when developers are being a little lazy about explicitly defining the contract.
This kind of thing comes up more with internal and app specific APIs where neither the development nor code reviews are as rigorous as it would be for published public APIs.
I can confirm that, with the original project that prompted this issue, this change along with several other updates since the original report have fully resolved the problems I was facing. Do keep in mind though, my particular API is about 95% GET requests, so I cannot fully vouch for the behavior with other cases -- though I cannot see any particular problems with the few POST, PUT and DELETE cases I do have.
Thanks again for all your hard work on this project :)
Was this released to NuGet? Using the latest RC3 release, I still get the output that @moander has put together in his screenshots.
This still not fixed in latest release : v1.1.0 ?
Still not fixed?
I can confirm that, with the original project that prompted this issue, this change along with several other updates since the original report have fully resolved the problems I was facing. Do keep in mind though, my particular API is about 95% GET requests, so I cannot fully vouch for the behavior with other cases -- though I cannot see any particular problems with the few POST, PUT and DELETE cases I do have.
Thanks again for all your hard work on this project :)
This works even for POST but only for [FromQuery]. However, still no description displayed for [FromForm] (POST) though.
Hey guys, can anyone share an example on this ? I still have same issue.
/// <summary>
/// Description
/// </summary>
/// <param name="key">Project key</param>
/// <param name="apiInfo">Information needed to access the public API.</param>
/// <returns>Status of the operation</returns>
[HttpPost]
public async Task<ActionResult<string>> Update(string key, [FromQuery]ApiInfo apiInfo)
public sealed class ApiInfo
{
/// <summary>
/// Workspace id - can be found on the main workspace page.
/// </summary>
public long WorkspaceId { get; set; }
/// <summary>
/// PI key
/// </summary>
public string ApiKey { get; set; }
/// <summary>
/// API Secret
/// </summary>
public string ApiSecret { get; set; }
}
In Swagger only first parameter gets a proper description, all other are blank.
@alexey-aurea Is class ApiInfo in the same project as method Update()? Not sure if this is the cause of your issue, but I saw this StackOverflow question with a similar problem and the noted solution fixed it for me.
Would be nice if XML documentations would be processed even if they originate from another project though. :/
Most helpful comment
Still not fixed?