Swagger - hide api version parameters - c#

Is it possible to hide the 'api-version' and 'x-api-version' parameters?
services.AddApiVersioning(config =>
{
config.ReportApiVersions = true;
config.DefaultApiVersion = new ApiVersion(1, 0);
config.AssumeDefaultVersionWhenUnspecified = true;
config.ApiVersionReader = ApiVersionReader.Combine(
new QueryStringApiVersionReader(),
new HeaderApiVersionReader()
{
HeaderNames = { "x-api-version" }
});
});
services.AddVersionedApiExplorer(
options =>
{
// note: the specified format code will format the version as "'v'major[.minor][-status]"
options.GroupNameFormat = "'v'VVV";
options.DefaultApiVersionParameterDescription = "Do NOT modify api-version!";
});
I already checked how-to-set-up-swashbuckle-vs-microsoft-aspnetcore-mvc-versioning which implements a 'RemoveVersionFromParameter' method, but in that case the Swagger page would loose the api version and always uses the default v1.0. As shown in the code snippet, I am using the QueryStringApiVersionReader and HeaderApiVersionReader, but I don't want to support the url api versioning.
Note: The API does have multiple swagger json pages for all versions (e.g. V1, V1.1, V2.0)

You can try an operation filter. This is similar to Helder's solution, but the implementation doesn't have to be at the document level, so it seems simpler:
public void Configure(SwaggerGenOptions options)
{
// Filter out `api-version` parameters globally
options.OperationFilter<ApiVersionFilter>();
}
internal class ApiVersionFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var parametersToRemove = operation.Parameters.Where(x => x.Name == "api-version").ToList();
foreach (var parameter in parametersToRemove)
operation.Parameters.Remove(parameter);
}
}

Have you looked into IDocumentFilter with that you can remove stuff from the final swagger.json and that will remove it from the UI
Here is an example me removing some properties from the definitions:
private class HideStuffDocumentFilter : IDocumentFilter
{
public void Apply(SwaggerDocument swaggerDoc, SchemaRegistry s, IApiExplorer a)
{
foreach (var definition in swaggerDoc.definitions)
{
foreach (var prop in definition.Value.properties.ToList())
{
if (prop.Value.maxLength == 9999)
definition.Value.properties.Remove(prop);
}
}
}
}
I have a few more samples here:
https://github.com/heldersepu/Swagger-Net-Test/blob/e701b1d20d0b42c1287c3da2641ca521a0a7b592/Swagger_Test/App_Start/SwaggerConfig.cs#L766

You can add your own custom CSS
and use it to hide those elements (and do any other customisation you want).
app.UseSwaggerUI(c =>
{
...
c.InjectStylesheet("/swagger-ui/custom.css");
...
});
Edit - example:
Suppose you're trying to hide - in my example; you can easily adapt it to yours - the tenantId parameter in this "Remove Basket" operation:
This would do that:
div#operations-baskets-remove tr[data-param-name="tenantId"] {
display: none;
}

This can be done by setting ApiExplorerOption SubstituteApiVersionInUrl = true . In your case:
services.AddVersionedApiExplorer(
options =>
{
// note: the specified format code will format the version as "'v'major[.minor][-status]"
options.GroupNameFormat = "'v'VVV";
options.DefaultApiVersionParameterDescription = "Do NOT modify api-version!";
options.SubstituteApiVersionInUrl = true;
});

this worked for me.
public class SwaggerOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (operation?.Parameters == null
|| !operation.Parameters.Any())
{
return;
}
var parametersWithPropertiesToIgnore = context.ApiDescription
.ActionDescriptor.Parameters.Where(p =>
p.ParameterType.GetProperties()
.Any(t => t.GetCustomAttribute<IgnoreDataMemberAttribute>() != null));
foreach (var parameter in parametersWithPropertiesToIgnore)
{
var ignoreDataMemberProperties = parameter.ParameterType.GetProperties()
.Where(t => t.GetCustomAttribute<IgnoreDataMemberAttribute>() != null)
.Select(p => p.Name);
operation.Parameters = operation.Parameters.Where(p => !ignoreDataMemberProperties.Contains(p.Name))
.ToList();
}
}
}
On Startup
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Api", Version = "v1" });
c.SchemaFilter<SwaggerSchemaFilter>();
c.OperationFilter<SwaggerOperationFilter>();
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var filePath = Path.Combine(System.AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(filePath);
});
Just add the data annotation [IgnoreDataMember] in the property to hide it.
public class ExampleRequest
{
[IgnoreDataMember]
public string? HiddenProp { get; set; }
public string OtherProp { get; set; }
}
I have based on this article to create this solution.

Do you really want to hide the API version parameters or just make them read-only? The API version parameter is required when invoking the API so that should not be hidden from the documentation or user.
Your question implies that you're more concerned that the user not be able to change the API version default value for a particular documented API. That's fair, but has a different solution.
While you can mark a parameter as read-only, I have not been able to get the UI to honor that. It might be a bug or something else is misconfigured. You can definitely get read-only like behavior by using an enumeration with a single value. For example:
public class SwaggerDefaultValues : IOperationFilter
{
public void Apply( OpenApiOperation operation, OperationFilterContext context )
{
var apiDescription = context.ApiDescription;
operation.Deprecated |= apiDescription.IsDeprecated();
if ( operation.Parameters == null )
{
return;
}
foreach ( var parameter in operation.Parameters )
{
var description = apiDescription.ParameterDescriptions
.First( p => p.Name == parameter.Name );
parameter.Description ??= description.ModelMetadata?.Description;
if ( parameter.Schema.Default == null &&
description.DefaultValue != null &&
description.DefaultValue is not DBNull &&
description.ModelMetadata is ModelMetadata modelMetadata )
{
var json = JsonSerializer.Serialize(
description.DefaultValue,
modelMetadata.ModelType );
// this will set the API version, while also making it read-only
parameter.Schema.Enum = new[] { OpenApiAnyFactory.CreateFromJson( json ) };
}
parameter.Required |= description.IsRequired;
}
}

You can added startup.cs file.
services.AddApiVersioning(options =>
{
// reporting api versions will return the headers "api-supported-versions" and "api-deprecated-versions"
options.ReportApiVersions = true;
});
services.AddVersionedApiExplorer(options =>
{
// add the versioned api explorer, which also adds IApiVersionDescriptionProvider service
// note: the specified format code will format the version as "'v'major[.minor][-status]"
options.GroupNameFormat = "'v'VVV";
// note: this option is only necessary when versioning by url segment. the SubstitutionFormat
// can also be used to control the format of the API version in route templates
options.SubstituteApiVersionInUrl = true;
});
And then,you can added top on the controller. But I try to without this([ApiVersion("1.0")]),it could run.I had successfully hide version parametre.
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/Check")]
[ApiController]
[Authorize]
public class CheckController : ControllerBase {}

Related

List<Class> in Swagger UI where class contains IFormFile

In my code I use endpoint:
[Authorize]
[HttpPost("UploadAlbum")]
public async Task<ResponseHolderModel<UploadAlbumResponseModel>> UploadAlbum([FromForm]List<FileHolderModel> files)
This is FileHolderModel class:
public class FileHolderModel
{
public IFormFile? File { get; set; }
public float Price { get; set; }
}
My problem Is that I want to be abble to use this endpoint by swagger UI and post files with prices easily using UI. I tried to force swagger to recognize files to append filepicker by:
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var fileUploadMime = "multipart/form-data";
if (operation.RequestBody == null || !operation.RequestBody.Content.Any(x => x.Key.Equals(fileUploadMime, StringComparison.InvariantCultureIgnoreCase)))
return;
var fileParams = context.MethodInfo.GetParameters().Where(p => p.ParameterType == typeof(List<FileHolderModel>));
operation.RequestBody.Content[fileUploadMime].Schema.Properties =
fileParams.ToDictionary(k => k.Name, v => new OpenApiSchema()
{
Type = "string",
Format = "binary"
});
}
Unfortunately I am lost in OpenApiSchema. Could you help me how to write this piece of code to fit List<FoleHolderModel where File should be selectable by filepicker? If you know some other and better way how to do it write an answer please.

Hide parameter from Swagger (Swashbuckle)

I have a C# .NET 5.0 ASP.NET Core Web API application with "Enable OpenAPI support" selected. I want to hide the optional parameter in the below example from what shows up on the swagger page. I have found numerous posts about hiding a property or the controller but none of these solutions seem to work for just the parameter in the given code:
[HttpGet]
[Route("search")]
[Authorize]
public async Task<IActionResult> Search(string query, string optional = "")
{
return OK();
}
You can create a custom attibute and an operation filter inhering from Swashbuckle.AspNetCore.SwaggerGen.IOperationFilter to exclude the desired parameters from swagger.json generation
public class OpenApiParameterIgnoreAttribute : System.Attribute
{
}
public class OpenApiParameterIgnoreFilter : Swashbuckle.AspNetCore.SwaggerGen.IOperationFilter
{
public void Apply(Microsoft.OpenApi.Models.OpenApiOperation operation, Swashbuckle.AspNetCore.SwaggerGen.OperationFilterContext context)
{
if (operation == null || context == null || context.ApiDescription?.ParameterDescriptions == null)
return;
var parametersToHide = context.ApiDescription.ParameterDescriptions
.Where(parameterDescription => ParameterHasIgnoreAttribute(parameterDescription))
.ToList();
if (parametersToHide.Count == 0)
return;
foreach (var parameterToHide in parametersToHide)
{
var parameter = operation.Parameters.FirstOrDefault(parameter => string.Equals(parameter.Name, parameterToHide.Name, System.StringComparison.Ordinal));
if (parameter != null)
operation.Parameters.Remove(parameter);
}
}
private static bool ParameterHasIgnoreAttribute(Microsoft.AspNetCore.Mvc.ApiExplorer.ApiParameterDescription parameterDescription)
{
if (parameterDescription.ModelMetadata is Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DefaultModelMetadata metadata)
{
return metadata.Attributes.ParameterAttributes.Any(attribute => attribute.GetType() == typeof(OpenApiParameterIgnoreAttribute));
}
return false;
}
}
Put it in your controller's parameter
[HttpGet]
[Route("search")]
[Authorize]
public async Task<IActionResult> Search(string query, [OpenApiParameterIgnore] string optional = "")
{
return Ok();
}
Then configure it in Status.cs
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API Title", Version = "v1" });
c.OperationFilter<OpenApiParameterIgnoreFilter>();
});

Put a middleware-like between RazorPage and UrlHelper

I'm working on an multilingual website. earlier, i used to use QueryStringRequestProvider for RequestLocalizationOptions. Finally, based on some SEO-friendly best practices i decided to migrate to RouteDataRequestCultureProvider. I have many problems in this way. i've already checked more than 100 github issues and stackoverflow posts to find a working solution, but still nothing.
Currently i used these codes to partial reach the goal
Startup.cs
services.Configure<RequestLocalizationOptions>(options =>
{
options.ConfigureRequestLocalization();
options.RequestCultureProviders.Insert(0, new RouteDataRequestCultureProvider
{
RouteDataStringKey = LanguageRouteConstraint.Key,
Options = options
});
});
services.AddRazorPages()
.AddRazorPagesOptions(options =>
{
options.Conventions.Add(new LocalizedPageRouteModelConvention());
});
LanguageRouteConstraint.cs
public class LanguageRouteConstraint : IRouteConstraint
{
public const string Key = "culture";
public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
{
if (!values.ContainsKey(Key))
return false;
var lang = values[routeKey] as string;
if (string.IsNullOrWhiteSpace(lang))
return false;
return lang == "tr" || lang == "en" || lang == "fa";
}
}
Cultures.cs
public static class Cultures
{
public static readonly string[] Supported = { "tr", "en", "fa" };
public static readonly string Default = Supported[0];
public static RequestLocalizationOptions ConfigureRequestLocalization(this RequestLocalizationOptions options)
{
options ??= new RequestLocalizationOptions();
var supportedCultures = Supported
.Select(culture => new CultureInfo(culture))
.ToList();
options.DefaultRequestCulture = new RequestCulture(Default, Default);
options.SupportedCultures = supportedCultures;
options.SupportedUICultures = supportedCultures;
return options;
}
}
LocalizedPageRouteModelConvention.cs
public class LocalizedPageRouteModelConvention : IPageRouteModelConvention
{
public void Apply(PageRouteModel model)
{
foreach (var selector in model.Selectors.ToList())
{
model.Selectors.Add(new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel
{
Order = -1,
Template = AttributeRouteModel
.CombineTemplates("{" + LanguageRouteConstraint.Key + "?}",
selector.AttributeRouteModel.Template),
}
});
}
}
}
The major problem of mine, is currently i dont have a working way to put a middleware between IUrlHelper and RazorPage link generator
Now, i can use as below:
var currentCulture = Request.RouteValues[LanguageRouteConstraint.Key];
Url.Page("/Index/Contact", new {culture= currentCulture});
but it doesn't make sense to trace codes in project from the scratch to change code from Url.Page("") to Url.Page("", new{culture=ANY})
As i said before, i checked many sites, but still i got nothing.
The only thing i need for now, is a WORKING WAY to use standard Url.Page(), that to be translated to Url.Page("", new{culture=ANY}) in background.
Thanks in advance
My problem is only about RazorPages, not Controllers

Swagger Documentation for Web api2 when using route attributes and query paramaters

I have a simple web API2 project that uses swagger for it's documentations.
Given a simple GET endpoint that uses route parameters and query parameters such as:
[HttpGet]
[Route("api/v2/items/{itemid:int}")]
public IHttpActionResult Getv2(int itemId, [FromUri(Name ="")]DTOv1 request)
{
return Ok();
}
public class DTOv1
{
public DateTime? StartValue { get; set; }
}
This gives the following documentation:
However, I would like to be able to specify all the items in a POCO. Such as:
[HttpGet]
[Route("api/v3/items/{itemid:int}")]
public IHttpActionResult Getv3([FromUri(Name ="")]DTOv2 request)
{
return Ok();
}
public class DTOv2
{
public int ItemId { get; set; }
public DateTime? StartValue { get; set; }
}
This gives the following Incorrect documentation:
This GET endpoint works in the same way as the first example but as you can see the documentation does not, and trying to do an example will not work. Is it possible to configure swagger so that this is documented in the same way as the first example, ideally in a convention based way?
Swagger is just using the default setup:
GlobalConfiguration.Configuration
.EnableSwagger(c =>
{
c.SingleApiVersion("v1", "TestSwagger");
c.PrettyPrint();
})
.EnableSwaggerUi(c =>
{
});
EDIT:
Thanks to the response regarding adding filters by I wrote the following operation filter that works in our use case to manipulate the parameters:
private class OperationFilter : IOperationFilter
{
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
if (apiDescription.HttpMethod.Method == "GET")
{
var pathParams = operation.parameters.Where(x => x.#in == "path");
var toRemoveItems = new List<Parameter>();
foreach(var pathParam in pathParams)
{
toRemoveItems.AddRange(operation
.parameters
.Where(x => x.#in != "path" && x.name.EndsWith(pathParam.name)));
}
foreach(var toRemove in toRemoveItems)
{
operation.parameters.Remove(toRemove);
}
}
}
}
Following up with my suggestion from the comments about using an IDocumentFilter here is a starting point:
private class RouteTestDocumentFilter : IDocumentFilter
{
const string PATH = "/api/RouteTest/test/{itemid}";
public void Apply(SwaggerDocument swaggerDoc, SchemaRegistry s, IApiExplorer a)
{
if (swaggerDoc.paths != null && swaggerDoc.paths.ContainsKey(PATH))
{
var get = swaggerDoc.paths[PATH].get;
if (get != null)
{
get.parameters.RemoveAt(0);
get.parameters[0].#in = "path";
get.parameters[0].required = true;
foreach (var param in get.parameters)
{
int pos = param.name.IndexOf('.');
if (pos > 0)
param.name = param.name.Substring(pos + 1);
}
}
}
}
}
For more details see my commit:
https://github.com/heldersepu/SwashbuckleTest/commit/38a31e0ee700faf91cc38d005ae1c5f4bec3e1f3
Here is how it looks on the UI:
http://swashbuckletest.azurewebsites.net/swagger/ui/index?filter=RouteTest#/RouteTest/RouteTest_Get

Creating per-request controller/action based formatters in ASP.NET 5

I'm trying to implement HATEOAS in my ASP rest API, changing the ReferenceResolverProvider.
The problem is, that depending on which controller I use, I'd like to use different ReferenceResolvers, because I need to behave differently for each Controller.
Now I have universal options:
services.AddMvc()
.AddJsonOptions(option => option.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver())
.AddJsonOptions(options => options.SerializerSettings.ReferenceResolverProvider = () => new RoomsReferenceResolver<Room>())
.AddJsonOptions(options => options.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.Objects);
And I want to have something like this:
services.AddMvc()
.AddJsonOptions(option => option.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver())
.AddJsonOptions<RoomsController>(options => options.SerializerSettings.ReferenceResolverProvider = () => new RoomsReferenceResolver<Room>())
.AddJsonOptions(options => options.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.Objects);
You seem to be wanting to create a per-controller specific formatters. This can be achieved by using a filter called IResourceFilter. A quick example:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class CamelCaseJsonFormatterResourceFilter : Attribute, IResourceFilter
{
private readonly JsonSerializerSettings serializerSettings;
public CamelCaseJsonFormatterResourceFilter()
{
// Since the contract resolver creates the json contract for the types it needs to deserialize/serialize,
// cache it as its expensive
serializerSettings = new JsonSerializerSettings()
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
}
public void OnResourceExecuting(ResourceExecutingContext context)
{
// remove existing input formatter and add a new one
var camelcaseInputFormatter = new JsonInputFormatter(serializerSettings);
var inputFormatter = context.InputFormatters.FirstOrDefault(frmtr => frmtr is JsonInputFormatter);
if (inputFormatter != null)
{
context.InputFormatters.Remove(inputFormatter);
}
context.InputFormatters.Add(camelcaseInputFormatter);
// remove existing output formatter and add a new one
var camelcaseOutputFormatter = new JsonOutputFormatter(serializerSettings);
var outputFormatter = context.OutputFormatters.FirstOrDefault(frmtr => frmtr is JsonOutputFormatter);
if (outputFormatter != null)
{
context.OutputFormatters.Remove(outputFormatter);
}
context.OutputFormatters.Add(camelcaseOutputFormatter);
}
}
// Here I am using the filter to indicate that only the Index action should give back a camelCamse response
public class HomeController : Controller
{
[CamelCaseJsonFormatterResourceFilter]
public Person Index()
{
return new Person() { Id = 10, AddressInfo = "asdfsadfads" };
}
public Person Blah()
{
return new Person() { Id = 10, AddressInfo = "asdfsadfads" };
}
If you are curious about the filter execution order, following is an example of the sequence of them:
Inside TestAuthorizationFilter.OnAuthorization
Inside TestResourceFilter.OnResourceExecuting
Inside TestActionFilter.OnActionExecuting
Inside Home.Index
Inside TestActionFilter.OnActionExecuted
Inside TestResultFilter.OnResultExecuting
Inside TestResultFilter.OnResultExecuted
Inside TestResourceFilter.OnResourceExecuted
Interesting problem.
What about making the ReferenceResolver a facade:
class ControllerReferenceResolverFacade : IReferenceResolver
{
private IHttpContextAccessor _context;
public ControllerReferenceResolverFacade(IHttpContextAccessor context)
{
_context = context;
}
public void AddReference(object context, string reference, object value)
{
if ((string)_context.HttpContext.RequestServices.GetService<ActionContext>().RouteData.Values["Controller"] == "HomeController")
{
// pass off to HomeReferenceResolver
}
throw new NotImplementedException();
}
Then you should be able to do:
services.AddMvc()
.AddJsonOptions(options => options.SerializerSettings.ReferenceResolverProvider = () => {
return new ControllerReferenceResolverFacade(
services.BuildServiceProvider().GetService<IHttpContextAccessor>());
});
This might not be exactly what you need but it might help you get started?

Categories

Resources