Swashbuckle should use my controller action names as default summary - swagger

I am using Swashbuckle to generate my API definitions in a .NET 5 project.
To add a summary and remarks to my documentation, I am currently putting a comment on some of my actions like this:
/// <summary>
/// CreateSite
/// </summary>
/// <remarks>
/// Options:
/// * Enterprise = 0,
/// * Site = 1
/// * Order = 2
/// * Line = 3
/// * Product = 4
///
/// </remarks>
[HttpPost]
[Route("sites")]
public async Task<IActionResult> CreateSiteAsync([FromBody] SiteCreateRequest createRequest)
{ // My controller stuff }
This generates a nice documentation and is very helpful.
Howevery, my "summary" field has always the same value like my controller action name - I already put efford in a very good naming of the actions:
You can see above that the summary contains "CreateSite" and my controller name is "CreateSiteAsync".
Is there a way to automatize this?
So could I set some option in the service to use the controller name as a "default" summary option used in the json file?
Then I can just avoid this cumbersome comments in the all simple requests without the need of any docu.

I also use Swashbuckle and to properly document my APIs I use Swagger tags. Attached is an example of my actual use. For your specific controller name tag, in my example it would be [SwaggerOperation("In-Transit Shipments")]
/// <summary>
/// In-transit shipments
/// </summary>
/// <remarks>
/// Get in-transit shipments for a client
/// </remarks>
/// <returns></returns>
[SwaggerTag("GroundTransportation")]
[SwaggerOperation("In-Transit Shipments")]
[SwaggerResponse(200, typeof(List<LoadSummaryDto>), Description = "OK")]
[SwaggerResponse(400, typeof(ErrorMessageDto), Description = "Bad Request")]
[SwaggerResponse(404, typeof(ErrorMessageDto), Description = "Not Found")]
[SwaggerOperationProcessor(typeof(ReDocCodeSampleAppender), "Curl,CSharp,Java")]
[HttpGet("ShipmentInformation/In-Transit")]
[TraceAction(message: "Controller: Retrieving in-transit shipments for client", level: LogLevel.Information, externalErrorMessage: "In-transit shipments could not be found")]
public async Task<IActionResult> GetClientInTransitShipments(uint? page = GroundTransportationConstants.DefaultPage, uint? pageSize = GroundTransportationConstants.DefaultPageSize)
{
// ... a bunch of api code :-)
}

Related

PlannerTaskDetail's GetETag returns null. If-Match header not working

I'm trying to update task details:
var t = await graphClient.Planner.Tasks[task.Id].Details.Request().GetAsync();
var ETag = t.GetETag();
However GetETag() returns null.
The ETag is part of the AdditionalData dictionary, so I can extract it writing my own method.
For example:
var e = etag = (taskDetails.AdditionalData["#odata.etag"] as JsonElement?)?.GetString();
But why does the built-in method not work? My code is almost exactly the same as Microsoft's test code.
Thanks.
Please debug and check GetEtag() method that is exposed here. Also validate the entity passed onto this method is not null.
namespace Microsoft.Graph
{
using System;
/// <summary>
/// Helper class to extract #odata.etag property and to specify If-Match headers for requests.
/// </summary>
public static class EtagHelper
{
/// <summary>
/// Name of the OData etag property.
/// </summary>
public const string ODataEtagPropertyName = "#odata.etag";
/// <summary>
/// Returns the etag of an entity.
/// </summary>
/// <param name="entity">The entity that contains an etag.</param>
/// <returns>Etag value if present, null otherwise.</returns>
public static string GetEtag(this Entity entity)
{
if (entity == null)
{
throw new ArgumentNullException(nameof(entity));
}
entity.AdditionalData.TryGetValue(ODataEtagPropertyName, out object etag);
return etag as string;
}
}
}

ASP.net Core WebAPI Routing order parameter condition

I've got have the following controller:
[Route("xapi/statements")] << -- NOTICE THE ROUTE
[Produces("application/json")]
public class StatementsController : ApiControllerBase
With he following actions
/// <summary>
/// Stores a single Statement with the given id.
/// </summary>
/// <param name="statementId"></param>
/// <param name="statement"></param>
/// <returns></returns>
[AcceptVerbs("PUT", "POST", Order = 1)]
public async Task<IActionResult> PutStatement([FromQuery]Guid statementId, [ModelBinder(typeof(StatementPutModelBinder))]Statement statement)
{
await _mediator.Send(PutStatementCommand.Create(statementId, statement));
return NoContent();
}
/// <summary>
/// Create statement(s) with attachment(s)
/// </summary>
/// <param name="model"></param>
/// <returns>Array of Statement id(s) (UUID) in the same order as the corresponding stored Statements.</returns>
[HttpPost(Order = 2)]
[Produces("application/json")]
public async Task<ActionResult<ICollection<Guid>>> PostStatements(StatementsPostModelBinder model)
{
ICollection<Guid> guids = await _mediator.Send(CreateStatementsCommand.Create(model.Statements));
return Ok(guids);
}
The actions are executed in the following order:
1. PutStatement
2. PostStatements
But PutStatement should only be triggered if the statementId parameter is provided. This is not the case.
I'm using 2 model binders to parse the content of the streams as either application/json or multipart/form-data if the statements have any attachments.
1. StatementPutModelBinder
2. StatementsPostModelBinder
How do i prevent the action from being excuted if the statementId parameter is not provided?
Eg. /xapi/statements/ => Hits PutStatement
I did not find a answer for my own question, but i made a mistake and was under the impression that the xAPI statements resource should allow statementId as a parameter for POST requests. Therefore i do not have the issue any more, which started my question.

Swashbuckle ASP.NET Core consume application/x-www-form-urlencoded

I have an Action that consumes application/x-www-form-urlencoded:
[HttpPost("~/connect/token"), Consumes("application/x-www-form-urlencoded")]
public async Task<IActionResult> Exchange([FromBody]OpenIdConnectRequest request)
{
..
}
But Swashbuckle generates empty array for Consumes property. If I change it to application/json, consumes array is generated properly.
Is it a bug related to application/x-www-form-urlencoded or I need to configure Swashbuckle additionally to support this application type?
That 'consumes' is not catered for out-of-the-box for Swashbuckle, a custom extension is required like the ones in this part of #domaindrivendev's GitHub project
There are three steps:
Create Parameter Attribute
Create Extension
Add instruction to Swashbuckle to process the new extension
Add an attribute to the parameter in an Controller method
I'll add more instructions in my fork of the repo, but here is the code:
1. FromFormDataBodyAttribute.cs
using System;
using System.Collections.Generic;
using System.Net.Http.Formatting;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Validation;
/// <summary>
/// FromFormDataBody Attribute
/// This attribute is used on action parameters to indicate
/// they come only from the content body of the incoming HttpRequestMessage.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter, Inherited = true, AllowMultiple = false)]
public sealed class FromFormDataBodyAttribute : ParameterBindingAttribute
{
/// <summary>
/// GetBinding
/// </summary>
/// <param name="parameter">HttpParameterDescriptor</param>
/// <returns>HttpParameterBinding</returns>
public override HttpParameterBinding GetBinding(HttpParameterDescriptor parameter)
{
if (parameter == null)
throw new ArgumentNullException("parameter");
IEnumerable<MediaTypeFormatter> formatters = parameter.Configuration.Formatters;
IBodyModelValidator validator = parameter.Configuration.Services.GetBodyModelValidator();
return parameter.BindWithFormatter(formatters, validator);
}
}
2 AddUrlFormDataParams.cs
using Swashbuckle.Swagger;
using System.Linq;
using System.Web.Http.Description;
/// <summary>
/// Add UrlEncoded form data support for Controller Actions that have FromFormDataBody attribute in a parameter
/// usage: c.OperationFilter<AddUrlFormDataParams>();
/// </summary>
public class AddUrlFormDataParams : IOperationFilter
{
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
var fromBodyAttributes = apiDescription.ActionDescriptor.GetParameters()
.Where(param => param.GetCustomAttributes<FromFormDataBodyAttribute>().Any())
.ToArray();
if (fromBodyAttributes.Any())
operation.consumes.Add("application/x-www-form-urlencoded");
foreach (var headerParam in fromBodyAttributes)
{
if (operation.parameters != null)
{
// Select the capitalized parameter names
var parameter = operation.parameters.Where(p => p.name == headerParam.ParameterName).FirstOrDefault();
if (parameter != null)
{
parameter.#in = "formData";//NB. ONLY for this 'complex' object example, as it will be passed as body JSON.
//TODO add logic to change to "query" for string/int etc. as they are passed via query string.
}
}
}
}
}
3 Update Swagger.config
//Add UrlEncoded form data support for Controller Actions that have FromBody attribute in a parameter
c.OperationFilter<AddUrlFormDataParams>();
4. Add an attribute to the parameter in an Controller method
[FromFormDataBody]OpenIdConnectRequest request

Validating Configuration in MVC Core

I have a section in appsettings.json which contains a list of libraries and their dependencies, and how to configure them in different execution environments.I'd like to be able to validate that the library collection includes all the dependencies.
That's easy enough to do with a little recursion. But I can't figure out how to override the configuration binding process so that I can do the validation.
The only way I've come up with is to create a raw collection of the libraries, based on appconfig.json, and then create a service which validates the collection and makes it available. Something like:
public class RawLibraries : List<Library>
{
}
public class LibraryResolver
{
public LibraryResolver( IOptions<RawLibraries> rawLibs, ILogger logger )
{
// validate rawLibs and log errors
}
// ...implementation
}
services.Configure<RawLibraries>(Configuration.GetSection("Libraries"));
services.AddSingleton<LibraryResolver, LibraryResolver>();
But this seems convoluted. Thoughts on a better approach?
Why not to follow the authors and write your own extension method with additional validation?
Take a look here. This is the source code of services.Configure<> method:
namespace Microsoft.Extensions.DependencyInjection
{
/// <summary>
/// Extension methods for adding options services to the DI container.
/// </summary>
public static class OptionsServiceCollectionExtensions
{
...
/// <summary>
/// Registers an action used to configure a particular type of options.
/// </summary>
/// <typeparam name="TOptions">The options type to be configured.</typeparam>
/// <param name="services">The <see cref="T:Microsoft.Extensions.DependencyInjection.IServiceCollection" /> to add the services to.</param>
/// <param name="configureOptions">The action used to configure the options.</param>
/// <returns>The <see cref="T:Microsoft.Extensions.DependencyInjection.IServiceCollection" /> so that additional calls can be chained.</returns>
public static IServiceCollection Configure<TOptions>(this IServiceCollection services, Action<TOptions> configureOptions) where TOptions : class
{
if (services == null)
throw new ArgumentNullException("services");
if (configureOptions == null)
throw new ArgumentNullException("configureOptions");
services.AddSingleton<IConfigureOptions<TOptions>>((IConfigureOptions<TOptions>) new ConfigureOptions<TOptions>(configureOptions));
return services;
}
}
}
As you can see Configure<TOptions> method is an extension method. Simply write your own let say ConfigureAndValidate<TOptions>() extension method which will do proper validation before services.AddSingleton... line.

How to create a .qwc file for QuickBooks Web Connector?

I am integrating QuickBooks (desktop version) with an ASP.NET application. For that I am using QuickBooks Web Connector. How can I create a .qwc file for my custom web service?
The Web Connector is really just a proxy or a relay that sits between QuickBooks and your own application.
As an overview - basically, you build a SOAP server / Web Service which speaks a specific set of methods. The Web Connector then is installed on the machine running QuickBooks, and polls your web service asking “Hey, got anything for me to do?” Your web service can then respond with qbXML requests (examples of qbXML here) which tell the Web Connector “Add this customer: …” or “Send me invoices which match: …” or etc. etc. etc. The Web Connector then relays those requests to QuickBooks, QuickBooks processes them, and the response is relayed back to your web service. Your web service might then process the response somehow, and then send the next request over to the Web Connector.
There's a bigger overview of the Web Connector here or, if you download the QuickBooks SDK it has a 100+ page PDF that goes over this in detail.
You probably also want to look at this example after installing the QuickBooks SDK:
C:\Program Files (x86)\Intuit\IDN\QBSDK12.0\samples\qbdt\c-sharp\qbXML\WCWebService
Which is a complete working examples of a Web Connector SOAP implementation.
At it's most basic form, it looks something like this:
[WebMethod]
/// <summary>
/// WebMethod - authenticate()
/// To verify username and password for the web connector that is trying to connect
/// Signature: public string[] authenticate(string strUserName, string strPassword)
///
/// IN:
/// string strUserName
/// string strPassword
///
/// OUT:
/// string[] authReturn
/// Possible values:
/// string[0] = ticket
/// string[1]
/// - empty string = use current company file
/// - "none" = no further request/no further action required
/// - "nvu" = not valid user
/// - any other string value = use this company file
/// </summary>
public string[] authenticate(string strUserName, string strPassword)
{
string[] authReturn = new string[2];
// Generate a random session ticket
authReturn[0]= System.Guid.NewGuid().ToString();
// For simplicity of sample, a hardcoded username/password is used.
string pwd="password";
if (strUserName.Trim().Equals("username") && strPassword.Trim().Equals(pwd))
{
// An empty string for authReturn[1] means asking QBWebConnector
// to connect to the company file that is currently openned in QB
authReturn[1]="";
}
else
{
authReturn[1]="nvu";
}
return authReturn;
}
[ WebMethod(Description="This web method facilitates web service to send request XML to QuickBooks via QBWebConnector",EnableSession=true) ]
/// <summary>
/// WebMethod - sendRequestXML()
/// Signature: public string sendRequestXML(string ticket, string strHCPResponse, string strCompanyFileName,
/// string Country, int qbXMLMajorVers, int qbXMLMinorVers)
///
/// IN:
/// int qbXMLMajorVers
/// int qbXMLMinorVers
/// string ticket
/// string strHCPResponse
/// string strCompanyFileName
/// string Country
/// int qbXMLMajorVers
/// int qbXMLMinorVers
///
/// OUT:
/// string request
/// Possible values:
/// - “any_string” = Request XML for QBWebConnector to process
/// - "" = No more request XML
/// </summary>
public string sendRequestXML(string ticket, string strHCPResponse, string strCompanyFileName,
string qbXMLCountry, int qbXMLMajorVers, int qbXMLMinorVers)
{
// QuickBooks has asked for your next request
... return a qbXML request here ...
}
[ WebMethod(Description="This web method facilitates web service to receive response XML from QuickBooks via QBWebConnector",EnableSession=true) ]
/// <summary>
/// WebMethod - receiveResponseXML()
/// Signature: public int receiveResponseXML(string ticket, string response, string hresult, string message)
///
/// IN:
/// string ticket
/// string response
/// string hresult
/// string message
///
/// OUT:
/// int retVal
/// Greater than zero = There are more request to send
/// 100 = Done. no more request to send
/// Less than zero = Custom Error codes
/// </summary>
public int receiveResponseXML(string ticket, string response, string hresult, string message)
{
// QuickBooks has sent you a qbXML response to your request
... do something with 'response' here ...
}
That example also includes an example .QWC file. Here's some .QWC file documentation and here's a basic example:
<?xml version="1.0"?>
<QBWCXML>
<AppName>QuickBooks Integrator</AppName>
<AppID></AppID>
<AppURL>https://secure.domain.com/quickbooks/server.php</AppURL>
<AppDescription></AppDescription>
<AppSupport>http://www.domain.com/quickbooks/support.php</AppSupport>
<UserName>username</UserName>
<OwnerID>{90A44FB7-33D9-4815-AC85-AC86A7E7D1EB}</OwnerID>
<FileID>{57F3B9B6-86F1-4FCC-B1FF-967DE1813D20}</FileID>
<QBType>QBFS</QBType>
<Scheduler>
<RunEveryNMinutes>2</RunEveryNMinutes>
</Scheduler>
<IsReadOnly>false</IsReadOnly>
</QBWCXML>

Resources