Automatically generate calls to SwaggerDoc in Swagger - asp.net-core

public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(); //this replaces these services.AddMvcCore().AddApiExplorer();
...
services.AddSwaggerGen(options =>
{
// make this automatic ???
options.SwaggerDoc("v1", new Info { Version = "v1", Title = "v1 API", Description = "v1 API Description" });
options.SwaggerDoc("v2", new Info { Version = "v2", Title = "v2 API", Description = "v2 API Description" });
...
options.DocInclusionPredicate((version, desc) =>
{
var versions = desc.CustomAttributes().OfType<ApiVersionAttribute>().SelectMany(attr => attr.Versions).ToArray();
var maps = desc.CustomAttributes().OfType<MapToApiVersionAttribute>().SelectMany(attr => attr.Versions).ToArray();
return versions.Any(v => $"v{v.ToString()}" == version) && (!maps.Any() || maps.Any(v => $"v{v.ToString()}" == version));
});
});
}
This code works as expected. But can the calls to SwaggerDoc be automated, in order to make the code more generic? In DocInclusionPredicate from the desc parameter the versions can be gathered.

As you are using the ApiVersionAttribute, I assume you are using the Microsoft.AspNetCore.Mvc.Versioning nuget package. The package provides a service named IApiVersionDescriptionProvider. This service provides an enumeration of all detected API-Versions. You can then automatically add them as a swagger-doc.
services.AddSwaggerGen(options =>
{
// you can use the IApiVersionDescriptionProvider
var provider = services.BuildServiceProvider()
.GetRequiredService<IApiVersionDescriptionProvider>();
foreach (var description in provider.ApiVersionDescriptions)
{
var info = new Info
{
Title = $"My API {description.ApiVersion}",
Version = description.ApiVersion.ToString(),
Contact = new Contact
{
Email = "info#mydomain.com",
Name = "Foo Bar",
Url = "https://thecatapi.com/"
}
};
options.SwaggerDoc(description.GroupName, info);
}
// instead of manually adding your versions
//options.SwaggerDoc("v1", new Info { Version = "v1", Title = "v1 API", Description = "v1 API Description" });
//options.SwaggerDoc("v2", new Info { Version = "v2", Title = "v2 API", Description = "v2 API Description" });
options.DocInclusionPredicate((version, desc) =>
{
var versions = desc.CustomAttributes().OfType<ApiVersionAttribute>().SelectMany(attr => attr.Versions).ToArray();
var maps = desc.CustomAttributes().OfType<MapToApiVersionAttribute>().SelectMany(attr => attr.Versions).ToArray();
return versions.Any(v => $"v{v.ToString()}" == version) && (!maps.Any() || maps.Any(v => $"v{v.ToString()}" == version));
});
});

Related

How to add default value to authorization header value

I am using asp.netcore 3.1 and openapi 3.0.1
I have added authorization to my apis using the following code:
services.AddSwaggerGen(setupAction =>
{
setupAction.SwaggerDoc("APIs_Documentation", new OpenApiInfo
{
Title = "Project APIs",
Version = "1"
});
setupAction.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme()
{
Type = SecuritySchemeType.Http,
Scheme = "bearer",
Description = "Enter token here",
Name = "Authorization",
#In = ParameterLocation.Header,
});
setupAction.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "oauth2",
}
},new List<string>()}
});
});
Is there any way to set a default value to the value field of the popup authorization dialogue in the next image?
If you are looking for a way to add a default header you can do it from C# like this:
app.UseSwagger()
.UseSwaggerUI(c =>
{
c.Interceptors = new InterceptorFunctions
{
RequestInterceptorFunction = "function (req) { req.headers['MyCustomHeader'] = 'CustomValue'; return req; }"
};
}
);
or want to add it to Swagger UI: related question

NSwag and multiple api versions

Consider controllers below:
namespace Web.Controllers
{
[ApiVersioning("1.0")
[Route("api/v{version:apiVersion}/[controller]")]
public class Product : ApiController
{
[HttpGet("id")]
public IHttpActionResult<bool> GetProduct(Guid id)
{ /* rest of the code */ }
}
}
namespace Web.Controllers
{
[ApiVersioning("2.0")
[Route("api/v{version:apiVersion}/[controller]")]
public class Product2 : ApiController
{
[HttpGet("id")]
public IHttpActionResult<bool> GetProduct(Guid id)
{ /* rest of the code */ }
}
}
And Swagger documents in Startup class:
services.AddSwaggerDocument(config =>
{
config.DocumentName = "v1.0";
config.PostProcess = document =>
{
document.Info.Version = "v1.0";
};
});
services.AddSwaggerDocument(config =>
{
config.DocumentName = "v2.0";
config.PostProcess = document =>
{
document.Info.Version = "v2.0";
};
});
Now after testing the API browser with NSwag it ignores the versions and shows all the APIs in both v1 and v2 document.
How to tell NSwag to separate them?
I think you are missing ApiGroupNames property which is used to select Api version. Please add ApiGroupNames property like below code and let us know.
services.AddSwaggerDocument(config =>
{
config.DocumentName = "v1.0";
config.PostProcess = document =>
{
document.Info.Version = "v1.0";
};
config.ApiGroupNames = new[] { "1.0" };
});
services.AddSwaggerDocument(config =>
{
config.DocumentName = "v2.0";
config.PostProcess = document =>
{
document.Info.Version = "v2.0";
};
config.ApiGroupNames = new[] { "2.0" };
});

Web API 2, Swagger & IdentityServer3

I am trying to setup a Web API with Swagger and an IdentityServer and can't figure out how to make Swagger works correctly.
My React app is working with the IdentityServer and I managed to get the ui working but when I try to activate authentication, I always get a "insufficient_scope" error.
Here's my config :
Client
public static IEnumerable<Client> Get()
{
return new[]
{
new Client
{
ClientId = "ipassportimplicit",
ClientName = "iPassport (Implicit)",
Flow = Flows.Implicit,
AllowAccessToAllScopes = true,
//redirect = URI of the React application callback page
RedirectUris = new List<string>
{
Constants.iPassportReact + "callback.html"
}
},
new Client
{
ClientId = "swaggerui",
ClientName = "Swagger (Implicit)",
Flow = Flows.Implicit,
AllowAccessTokensViaBrowser = true,
PostLogoutRedirectUris = new List<string>
{
"http://localhost:53633/swagger/"
},
AllowAccessToAllScopes = true,
RedirectUris = new List<string>
{
"http://localhost:53633/swagger/ui/o2c-html"
}
}
};
}
Scope
public static IEnumerable<Scope> Get()
{
return new List<Scope>
{
new Scope
{
Name = "passportmanagement",
DisplayName = "Passport Management",
Description = "Allow the application to manage passports on your behalf.",
Type = ScopeType.Resource
},
new Scope
{
Name = "swagger",
DisplayName = "Swagger UI",
Description = "Display Swagger UI",
Type = ScopeType.Resource
}
};
}
SwaggerConfig
public static void Register(HttpConfiguration config)
{
var thisAssembly = typeof(SwaggerConfig).Assembly;
config
.EnableSwagger(c =>
{
c.SingleApiVersion("v2", "api_iPassport");
c.OAuth2("oauth2")
.Description("OAuth2 Implicit Grant")
.Flow("implicit")
.AuthorizationUrl(Constants.iPassportSTSAuthorizationEndpoint)
.TokenUrl(Constants.iPassportSTSTokenEndpoint)
.Scopes(scopes =>
{
scopes.Add("swagger", "Swagger UI");
});
c.OperationFilter<AssignOAuth2SecurityRequirements>();
})
.EnableSwaggerUi(c =>
{
c.EnableOAuth2Support("swaggerui", "swaggerrealm", "Swagger UI");
});
}
Operation Filter
public class AssignOAuth2SecurityRequirements : IOperationFilter
{
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
var actFilters = apiDescription.ActionDescriptor.GetFilterPipeline();
var allowsAnonymous = actFilters.Select(f => f.Instance).OfType<OverrideAuthorizationAttribute>().Any();
if (allowsAnonymous)
return; // must be an anonymous method
//var scopes = apiDescription.ActionDescriptor.GetFilterPipeline()
// .Select(filterInfo => filterInfo.Instance)
// .OfType<AllowAnonymousAttribute>()
// .SelectMany(attr => attr.Roles.Split(','))
// .Distinct();
if (operation.security == null)
operation.security = new List<IDictionary<string, IEnumerable<string>>>();
var oAuthRequirements = new Dictionary<string, IEnumerable<string>>
{
{"oauth2", new List<string> {"swagger"}}
};
operation.security.Add(oAuthRequirements);
}
}
Response Headers
{
"date": "Fri, 12 May 2017 03:37:08 GMT",
"www-authenticate": "Bearer error=\"insufficient_scope\"",
"x-sourcefiles": "=?UTF-8?B?TzpcTG9jYWwgV29ya3NwYWNlXFZTVFMgSUJNXFJlcG9zXFdlYkFQSVxhcGlfaVBhc3Nwb3J0XGFwaV9pUGFzc3BvcnRcYXBpXFVzZXJcR2V0?=",
"server": "Microsoft-IIS/10.0",
"x-powered-by": "ASP.NET",
"content-length": "0",
"content-type": null
}
Anything I can't see? All help appreciated!
Thanks
My problem was in my Startup.cs class of the Web API in which I didn't add the required scope to the
public void ConfigureAuth(IAppBuilder app)
{
var options = new IdentityServerBearerTokenAuthenticationOptions()
{
Authority = Constants.iPassportSTS,
RequiredScopes = new[] { "passportmanagement", "swagger" }
};
app.UseIdentityServerBearerTokenAuthentication(options);
}

ASP.NET Core permament redirect when no path specified

Is this a right way of doing permanent redirect when no path specified for request?
app.Use(next => context =>
{
if (string.IsNullOrWhiteSpace(context.Request.Path))
{
var builder = new UriBuilder(context.Request.Scheme, "site to redirect");
context.Response.Redirect(builder.ToString(), true);
}
return next(context);
});
Update 1
It appears that context.Request.Path includes /
app.Use(next => context =>
{
if (context.Request.Path.Value.Length <= 1)
{
var builder = new UriBuilder(context.Request.Scheme, "www.plaMobi.com");
context.Response.Redirect(builder.ToString(), true);
}
return next(context);
});
Accordingly to UriHelper implementation, both HttpRequest.PathBase abd HttpRequest.Path should be used:
var combinedPath = (pathBase.HasValue || path.HasValue)
? (pathBase + path).ToString() : "/";
The same logic in ProxyMiddleware class:
var uriString = $"{_options.Scheme}://{_options.Host}:{_options.Port}{context.Request.PathBase}{context.Request.Path}{context.Request.QueryString}";

Data not binding to Kendo dropdown list in mvc4

Data is not binding to Kendo dropdown list list is returning form my method
My dropdown
#(Html.Kendo().DropDownListFor(model => model.ParentAssetID)
.OptionLabel(" ")
.Name("ParentAssetID")
.DataTextField("AssetName")
.DataValueField("AssetId")
.SelectedIndex(0)
.Text(string.Empty)
.DataSource(source =>
{
source.Read(read =>
{
read.Url("../Asset/GetAllAssetsByCompanyId");
});
}))
my Action Result
public IEnumerable<AssetDetails> GetAllAssetsByCompanyId()
{
IList<AssetDetails> _assetSearchlist;
using (var client = new HttpClient())
{
AssetRepository assetrep = new AssetRepository();
Guid cp = new Guid(Session["CurrentCompanyId"].ToString());
_assetSearchlist = assetrep.GetAllAssetsByCompanyId(cp, "", "", "");
return _assetSearchlist;
}
}
public JsonResult GetOpportunityListByAccount(string Id)
{
Guid ID = new Guid(Id);
List<OpportunityViewModel> cpvm = new List<OpportunityViewModel>();
List<CrmOpportunity> crmOppList = new List<CrmOpportunity>();
cpvm = srv.OpportunitySet.Where(z => z.CustomerId.Id == ID).ToList();
foreach (var crm in cpvm )
{
CrmOpportunity crmOpp = new CrmOpportunity();
crmOpp.Id = crm.Id;
crmOpp.Name = crm.Name;
crmOppList.Add(crmOpp);
}
return Json(crmOppList, JsonRequestBehavior.AllowGet);
}
#(Html.Kendo().DropDownListFor(x => x.FromOpportunity)
.Name("OpportunityDDL")
.DataTextField("Name")
.DataValueField("Id")
.DataSource(source => {
source.Read(read =>
{
read.Action("GetOpportunityListByAccount", "CrmIntegration");
})
. ServerFiltering(true);
})
.HtmlAttributes( new { style = "margin-left:13px; width: 275px;" })
)
Data access is a little truncated but this is what you'll need to do
#(Html.Kendo().DropDownListFor(model => model.ParentAssetID)
.OptionLabel(" ")
.Name("ParentAssetID")
.DataTextField("AssetName")
.DataValueField("AssetId")
.SelectedIndex(0)
.Text(string.Empty)
.DataSource(source =>
{
source.Read(read =>
{
read.Action("GetAllAssetsByCompanyId", "Asset");
});
}))
Only a minor change but have you tried read.Action? Also is maybe try removing the following;
DropDownListFor(model => model.ParentAssetID)
and replace with
DropDownListFor<ClassName>()
Only a thought.