How to force NSwag to include custom response codes from xml comments at the auto-generated swagger json of a web API call - asp.net-web-api

This is the definition added in clean1.csproj file based on NSwag's documentation
<Target Name="AfterBuild">
<Exec Command="$(NSwagExe) webapi2swagger /assembly:$(OutDir)/Clean1.dll /referencepath: $(ProjectDir) /output:$(ProjectDir)/clean1swagger.json" />
The problem is that only 200 response code is being generated like:
],
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/Product"
},
"x-nullable": true
}
}
Here are the XML comments at the controller's demo call.
/// <summary>
/// Gets a product by Id
/// </summary>
/// <remarks>
/// Remarks-description text.
/// </remarks>
/// <response code="200">test 200</response>
/// <response code="201">test 201/response>
/// <response code="400">test 400</response></response>
[HttpGet]
[ResponseType(typeof(Product))]
public IHttpActionResult GetProduct(int id)
{
var product = products.FirstOrDefault((p) => p.Id == id);
if (product == null)
{
return NotFound();
}
return Ok(product);
}
The json should include and generate automatically the other responses.

This is currently not possible. We considered adding this feature but in most cases you need to specify the type and this cannot be done via xml comments. For now you have to use the SwaggerResponseAttribute for that. But please create an issue on Github so that the feature is considered to be added in the future...

Related

How to declaritively specify authorization policy based on HTTP verb and other attributes in ASP.Net Core (WebAPI)

I've got a couple of authorization polcies registered:
ConfigureServices()
{
services.AddAuthorization(authorisationOptions =>
{
authorisationOptions.AddPolicy(StandardAuthorizationPolicy.Name, StandardAuthorizationPolicy.Value);
authorisationOptions.AddPolicy(MutatingActionAuthorizationPolicy.Name, MutatingActionAuthorizationPolicy.Value);
});
}
& then I set a default authorization policy across all endpoints:
Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseEndpoints(endpoints =>
{
endpoints
.MapControllers()
.RequireAuthorization(StandardAuthorizationPolicy.Name); // Declaratively require the standard authorization policy on all controller endpoints
});
}
On the endpoints where I want to specify the mutating policy, I currently do the following:
[HttpPut]
[Authorize(MutatingActionAuthorizationPolicy.Name)] // Because 'PUT'. NOT DECLARATIVE! :-(
public async Task<IActionResult> AddOrUpdateOverride(SourceOverride sourceOverride, CancellationToken cancellationToken)
{
// ..
}
What I really want is a bit more control to declaritively apply the mutating policy based on the HttpVerb (i.e. POST, PUT, PATCH, DELETE).
Any idea on how to achieve that? Bonus points for allowing me to use other attributes on the controller method/class, not just [HttpPost] etc.
NB: I've seen solutions floating around that involve casting the content (and seem to revolve around a single access policy). I'd really rather stick with multiple access policies.
If I get stuck, I might end up writing a convention test for it.
You can implement a custom RequireAuthorization extension that takes HTTP verb filtering function as an argument and checks each endpoint metadata for HttpMethodAttribute
public static class AuthorizationEndpointConventionBuilderExtensions
{
/// <summary>
/// Adds authorization policies with the specified <see cref="IAuthorizeData"/> to the endpoint(s) filtered by supplied filter function
/// </summary>
/// <param name="builder">The endpoint convention builder.</param>
/// <param name="filterOnHttpMethods">Filters http methods that we applying specific policies to</param>
/// <param name="authorizeData">
/// A collection of <paramref name="authorizeData"/>. If empty, the default authorization policy will be used.
/// </param>
/// <returns>The original convention builder parameter.</returns>
public static TBuilder RequireAuthorizationForHttpMethods<TBuilder>(this TBuilder builder, Func<IEnumerable<HttpMethod>, bool> filterOnHttpMethods, params IAuthorizeData[] authorizeData)
where TBuilder : IEndpointConventionBuilder
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
if (authorizeData == null)
{
throw new ArgumentNullException(nameof(authorizeData));
}
if (authorizeData.Length == 0)
{
authorizeData = new IAuthorizeData[] { new AuthorizeAttribute(), };
}
builder.Add(endpointBuilder =>
{
var appliedHttpMethodAttributes = endpointBuilder.Metadata
.Where(x => x is HttpMethodAttribute)
.Cast<HttpMethodAttribute>();
if (appliedHttpMethodAttributes.Any(x => filterOnHttpMethods(x.HttpMethods
.Select(method => new HttpMethod(method)))))
{
foreach (var data in authorizeData)
{
endpointBuilder.Metadata.Add(data);
}
}
});
return builder;
}
/// <summary>
/// Adds authorization policies with the specified names to the endpoint(s) for filtered endpoints that return for filterOnHttpMethod
/// </summary>
/// <param name="builder">The endpoint convention builder.</param>
/// <param name="filterOnHttpMethods">Filters http methods that we applying specific policies to</param>
/// <param name="policyNames">A collection of policy names. If empty, the default authorization policy will be used.</param>
/// <returns>The original convention builder parameter.</returns>
public static TBuilder RequireAuthorizationForHttpMethods<TBuilder>(this TBuilder builder, Func<IEnumerable<HttpMethod>, bool> filterOnHttpMethods, params string[] policyNames)
where TBuilder : IEndpointConventionBuilder
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
if (policyNames == null)
{
throw new ArgumentNullException(nameof(policyNames));
}
return builder.RequireAuthorizationForHttpMethods(filterOnHttpMethods, policyNames.Select(n => new AuthorizeAttribute(n)).ToArray());
}
}
And then use this extension next to the original one:
app.UseEndpoints(endpoints =>
{
var mutatingHttpMethods = new HashSet<HttpMethod>()
{
HttpMethod.Post,
HttpMethod.Put,
HttpMethod.Delete
};
endpoints
.MapControllers()
.RequireAuthorization(StandardAuthorizationPolicy.Name)
.RequireAuthorizationForHttpMethods(httpMethods =>
httpMethods.Any(httpMethod => mutatingHttpMethods.Contains(httpMethod)),
MutatingActionAuthorizationPolicy.Name);
});
}

One controller's actions not appearing in help docs

I have an MVC Web API project which is all working fine, but for some reason the entries in the help docs for just one of the controllers does not appear.
This was all fine until just recently, unfortunately though I don't know at what point it disappeared but it definitely used to be there.
The XML comments all look ok.
The XmlDocument.xml file looks correct.
Is there a way to specify which controllers and methods feature in the help docs?
How can I make sure the actions for this controller appear?
In case it helps, this is a snip with the first action:
public class UserController : ApiController
{
/// <summary>
/// Get details of a user or all users, including accounts and group memberships
/// </summary>
/// <param name="username">The name of the user, if a single result is required</param>
/// <param name="account">The account id, if multiple results are required</param>
/// <param name="offset">The first row to return</param>
/// <param name="limit">The maximum number of rows to return</param>
/// <param name="sortby">The column to sort by, if required</param>
/// <param name="order">The sort order; asc[ending] (default) or desc[ending]</param>
/// <returns>Response structure including status and error message (if appropriate), as well as User structure including account and group details</returns>
[Route("user")]
[CombinedAuthentication(AuthLevel = "2")]
[HttpGet]
[AcceptVerbs("GET")]
public UserGetResponse Get(string username = "", int account = 0, int offset = 0, int limit = 0, string sortby = "", string order = "")
{
if (!string.IsNullOrEmpty(username))
{
return new UserGetResponse(username);
}
else
{
return new UserGetResponse(account, offset, limit, sortby, order);
}
}
}
This bit stops the help docs entry being generated:
[AcceptVerbs("GET")]
I removed that and all was fine!

Net Core NLog.Web "aspnet-request:header" property usage?

Is there any way to log all headers using "aspnet-request:header" property with one parameter? Or should I get headers one by one like "aspnet-request:header=MyHeader" and combine them into one parameter before insert? I have lots of headers and don't want to add them seperately, I need a quick way to log them if its possible.
Currently only one at once header is supported, as it calls
string header = httpRequest.Headers[this.Header]; see source
edit: you could plug it in NLog like this:
using System;
using System.Text;
using Microsoft.AspNetCore.Http;
using NLog.Config;
using NLog.LayoutRenderers;
using NLog.Web.Internal;
namespace NLog.Web.LayoutRenderers
{
/// <summary>
/// Render all headers for ASP.NET Core
/// </summary>
/// <example>
/// <code lang="NLog Layout Renderer">
/// ${aspnet-request-all-headers}
/// </code>
/// </example>
[LayoutRenderer("aspnet-request-all-headers")]
public class AspNetRequestAllHeadersLayoutRenderer : AspNetLayoutRendererBase
{
protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent)
{
var httpRequest = HttpContextAccessor.HttpContext.TryGetRequest();
if (httpRequest == null || httpRequest.Headers == null)
{
return;
}
foreach(var kpv in httpRequest.Headers)
{
if (header != null)
{
builder.Append(kpv.Key);
builder.Append(=);
builder.Append(kpv.Value);
}
}
}
}
}
Register it (startup.cs)
ConfigurationItemFactory.Default.LayoutRenderers
.RegisterDefinition("aspnet-request-all-headers", typeof(AspNetRequestAllHeadersLayoutRenderer ));
See also Extending NLog
usage
${aspnet-request-all-headers}

ResourceAuthorize("Read","UsersList") not working, ResourceAuthorizationManager

I am using IdentityServer3 to issue tokens and trying to use Thinktecture.IdentityModel.Owin.ResourceAuthorization.WebApi to authorize resource access of the web api.
I am using below code to Authorize an action of the controller.
[ResourceAuthorize("Read","UsersList")]
ResourceAuthorizationManager looks like below.
public class MyAuthorizationManager : ResourceAuthorizationManager
{
/// <summary>
/// Verify Access Rights
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public override Task<bool> CheckAccessAsync(ResourceAuthorizationContext context)
{
switch (context.Resource.First().Value)
{
case "UsersList":
return AuthorizeUsersList(context);
default:
return Nok();
}
}
private Task<bool> AuthorizeUsersList(ResourceAuthorizationContext context)
{
switch (context.Action.First().Value)
{
case "Read":
return Eval(context.Principal.HasClaim("role", "User"));
case "Write":
return Eval(context.Principal.HasClaim("role", "Owner"));
default:
return Nok();
}
}
}
However, when control comes to AuhtorizeUsersList, the context.Principal has no role claims. I do not store the user claims when I register a user. How can I add claims for an authenticated user on the go ?
Maybe it will be helpful for others.
Basically, I was missing 'role' claim inside scope-claim mapping while defining the API as scope. You just have to list all the claims that you want as part of the scope, and IdentityServer will handle the rest.
On the identity server side:
new Scope
{
Enabled = true,
Name = "ScopeName",
Type = ScopeType.Identity,
Claims = new List<ScopeClaim>
{
new ScopeClaim("role")
}
}

Telerik MVC Grid : Create custom FilterDescriptor based on lambda expression

Currently I'm using this code to create a custom filter:
var fName = new FilterDescriptor
{
Member = "Name",
MemberType = typeof(string),
Operator = FilterOperator.Contains,
Value = name
};
Which will be added to the GridCommand like this:
gridCommand.FilterDescriptors.Add(fName);
However, would like to create filters based on Linq lambda expression like:
IQueryable<CD> query = ...
if (!string.IsNullOrWhiteSpace(Artist))
{
query = query.Where(cd => cd.Artist.Contains(Artist));
}
if (!string.IsNullOrWhiteSpace(Name))
{
query = query.Where(cd => cd.Name.Contains(Name));
}
How to do this ?
In the QueryableExtensions.cs file from Telerik, this extension method will do the job:
/// <summary>
/// Filters a sequence of values based on a collection of <see cref="IFilterDescriptor"/>.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="filterDescriptors">The filter descriptors.</param>
/// <returns>
/// An <see cref="IQueryable" /> that contains elements from the input sequence
/// that satisfy the conditions specified by each filter descriptor in <paramref name="filterDescriptors" />.
/// </returns>
public static IQueryable Where(this IQueryable source, IEnumerable<IFilterDescriptor> filterDescriptors) { }
Ok so there isnt much wrong with the way you are doing it currently. Except if you were determined to add as linq expression then use the following example.
var query = from t in query where t.Artist.Contains(Artist) && t.Name.Contains(Name) select t;
If both Artist and Name are empty or null then it will return all anyway.

Resources