List<Class> in Swagger UI where class contains IFormFile - c#

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.

Related

MassTransit.Kafka batching

I'm using MassTransit.Kafka for produce and consume messages in batches. When I try to consume message one by one everything works fine, but when I try to consume messages in batches I get an error:
Confluent.Kafka.ConsumeException: Local: Value deserialization error
---> System.InvalidOperationException: Exception creating proxy (GreenPipes.DynamicInternal.MassTransit.Batch<Aiforfit.WSW.DataStructures.Events.UserEvent>) for MassTransit.Batch<Aiforfit.WSW.DataStructures.Events.UserEvent>
---> System.TypeLoadException: Method 'get_Item' in type 'GreenPipes.DynamicInternal.MassTransit.Batch<Aiforfit.WSW.DataStructures.Events.UserEvent>' from assembly 'MassTransitGreenPipes.DynamicInternal3c37dde6a7c744b796f7ac1cf544383b, Version=0.0.0.0, Cul
ture=neutral, PublicKeyToken=null' does not have an implementation.
Looks like it's NewtonSoft deserealization error, but everything done according to MassTransit documentation. I've tried to convert UserEvent to Interface because every model in documentation is interface, but it didn't help.
Configuration:
public static IServiceCollection AddKafka(this IServiceCollection services, IConfigurationSection section)
{
var config = section.Get<EventMessagingOptions>().Kafka;
services.AddMassTransitHostedService();
services.AddMassTransit(x =>
{
x.UsingInMemory((context, cfg) =>
{
cfg.ConfigureEndpoints(context);
cfg.UseRawJsonSerializer();
});
x.AddRider(rider =>
{
rider.AddConsumer<UserEventConsumer>(typeof(UserEventConsumerDefinition));
rider.UsingKafka((ctx, k) =>
{
k.SecurityProtocol = config.SecurityProtocol;
k.Host(config.Host, configurator =>
{
configurator.UseSasl(saslConfigurator =>
{
saslConfigurator.Username = config.Username;
saslConfigurator.Password = config.Password;
saslConfigurator.Mechanism = config.SaslMechanism;
});
});
k.TopicEndpoint<Batch<UserEvent>>(config.Topics.UserEvent, config.Topics.UserEventGroupId, e =>
{
e.AutoOffsetReset = AutoOffsetReset.Earliest;
e.ConfigureConsumer<UserEventConsumer>(ctx);
});
});
});
});
return services;
}
public class UserEventConsumerDefinition : ConsumerDefinition<UserEventConsumer>
{
public UserEventConsumerDefinition()
=> Endpoint(x => x.PrefetchCount = 500);
protected override void ConfigureConsumer(
IReceiveEndpointConfigurator endpointConfigurator,
IConsumerConfigurator<UserEventConsumer> consumerConfigurator)
{
consumerConfigurator.Options<BatchOptions>(options => options
.SetMessageLimit(500)
.SetConcurrencyLimit(25));
}
}
public class UserEventConsumer : IConsumer<Batch<UserEvent>>
{
private readonly ICluster _cluster;
public UserEventConsumer(ICluster cluster)
=> _cluster = cluster;
public async Task Consume(ConsumeContext<Batch<UserEvent>> context)
{
Console.WriteLine(context.Message.Length);
}
}
public class UserEvent
{
public Guid EventId { get; set; } = Guid.NewGuid();
public Guid UserId { get; set; }
public string Test { get; set; }
}
Looks like it's NewtonSoft deserealization error, but everything done according to MassTransit documentation. I've tried to convert UserEvent to Interface because every model in documentation is interface, but it didn't help.
In the case of a topic endpoint, you still specify TopicEndpoint<UserEvent> for the TopicEndpoint, and consume Batch<UserEvent> in your consumer. When configured on the topic endpoint, using ConfigureConsumer<UserEventConsumer>(ctx) it will properly handle the mapping of a batch of events to your consumer.
Assuming you are on the latest version of MassTransit, it should increase the ConcurrentMessageLimit on the topic endpoint to match the batch message capacity.

Overwrite request object in ASP .NET Core

I have base class for every request in my app:
public abstract class BaseDto
{
public string Uid { get; set; }
}
public class RequestDto : BaseDto
{
public string SomeData { get; set; }
}
Im using my ReuqestDto class in my controller actions:
[HttpGet]
public IEnumerable<string> Get(RequestDto req)
{
// some logic on request
if (req.Uid != null)
{
// perform action
}
}
The user passing only SomeData property to me. In my JWT Token i have saved some information about Uid for BaseDto. What is the best way to write data to Uid using middleware/filter to have that information in my Get() method? I Tried to serialized HttpContext.Request.Body but not success because i cant find, how to do it properly. Or maybe there are better solutions for this problem? How to write data to my incoming objects in app?
This is probably what you want.
You should to create own interface for models like that
public interface IMyRequestType { }
Your model should implement it for finding model in FilterAttribute
public class MyModel : IMyRequestType
{
public string ID { get; set; }
}
And create your filter attribute with OnActionExecuting implentation
public class MyFilterAttribute : TypeFilterAttribute
{
public MyFilterAttribute() : base(typeof(MyFilterImpl)) { }
private class MyFilterImpl : IActionFilter
{
private readonly ILogger _logger;
public MyFilterAttributeImpl(ILoggerFactory loggerFactory)
{
// get something from DI
_logger = loggerFactory.CreateLogger<MyFilterAttributeImpl>();
}
public void OnActionExecuting(ActionExecutingContext context)
{
// get your request model
var model = context.ActionArguments.Values.OfType<IMyRequestType>().Single();
// get your key
//context.HttpContext.User or whatever
// do something with model
}
public void OnActionExecuted(ActionExecutedContext context)
{
// perform some logic work
}
}
}
I often created a filter which implements Attribute and IAsyncActionFilter to get the information before go inside the Controller's action.
Here is an example,
using System.IdentityModel.Tokens.Jwt;
public class UserProfileFilter : Attribute, IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
string uid = string.Empty;
StringValues authHeaderVal = default(StringValues);
// Get UID from JWT
if (context.HttpContext.Request.Headers.TryGetValue("Authorization", out authHeaderVal))
{
string bearerTokenPrefix = "Bearer";
string accessToken = string.Empty;
string authHeaderStr = authHeaderVal.ToString();
if (!string.IsNullOrEmpty(authHeaderStr) && authHeaderStr.StartsWith(bearerTokenPrefix, StringComparison.OrdinalIgnoreCase))
{
accessToken = authHeaderStr.Replace(bearerTokenPrefix, string.Empty, StringComparison.OrdinalIgnoreCase).Trim();
}
var handler = new JwtSecurityTokenHandler();
var token = handler.ReadJwtToken(accessToken);
uid = token.Claims.FirstOrDefault(c => c.Type.Equals("sub", StringComparison.OrdinalIgnoreCase))?.Value;
}
// Or Get UID from ActionExecutingContext
var user = context.HttpContext.User;
if (user.Identity.IsAuthenticated)
{
uid = user.Claims.FirstOrDefault(c => c.Type.Equals("sub", StringComparison.OrdinalIgnoreCase))?.Value;
}
// Get payload
RequestDto payload = (RequestDto)context.ActionArguments?.Values.FirstOrDefault(v => v is RequestDto);
payload.Uid = uid;
await next();
}
}
And then you can put the filter on any action.
[HttpPost]
[Authorize]
[TypeFilter(typeof(UserProfileFilter))]
public ActionResult<IActionResult> AdminGet(RequestDto request)
{
Debug.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(request));
return this.Ok();
}
The above filter will use the sub claim's value to overwrite the value of the incoming payload.
For example, if I post the payload as following,
{
"uid" : "",
"someData": "Test"
}
The action will finally output {"Uid":"MyID","SomeData":"Test"}.

Swagger - hide api version parameters

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 {}

How to add Bing Spellcheck to LuisRecognizerMiddleware?

Ok so this is how my LUIS app is configured in my bot.
On the LUIS website I can add Bing Spell Check to correct common spelling mistakes and have a better intent and entity match.
All that is required is that a BING API key needs to be added to the LUIS query string. But where do I configure that in the LuisRecognizerMiddleware?
I'm not even sure if that's the right place. But I guess it does put together the URI.
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddBot<MyBot>(options =>
{
options.CredentialProvider = new ConfigurationCredentialProvider(_configuration);
options.Middleware.Add(new CatchExceptionMiddleware<Exception>(async (context, exception) =>
{
await context.TraceActivity("MyBotException", exception);
await context.SendActivity("Sorry, it looks like something went wrong!");
}));
IStorage dataStore = new MemoryStorage();
options.Middleware.Add(new ConversationState<MyBotConversationState>(dataStore));
// Add LUIS recognizer as middleware
// see https://learn.microsoft.com/en-us/azure/bot-service/bot-builder-howto-v4-luis?view=azure-bot-service-4.0&tabs=cs
(string modelId, string subscriptionKey, Uri url) = GetLuisConfiguration(_configuration);
LuisModel luisModel = new LuisModel(modelId, subscriptionKey, url);
options.Middleware.Add(new LuisRecognizerMiddleware(luisModel));
});
}
private static (string modelId, string subscriptionKey, Uri url) GetLuisConfiguration(IConfiguration configuration)
{
string modelId = configuration.GetSection("Luis-ModelId")?.Value;
string subscriptionKey = configuration.GetSection("Luis-SubscriptionId")?.Value;
string url = configuration.GetSection("Luis-Url")?.Value;
Uri baseUri = new Uri(url);
return (modelId, subscriptionKey, baseUri);
}
All I get so far is...
GET https://westeurope.api.cognitive.microsoft.com/luis/v2.0/apps/?subscription-key=&q=test234&log=True HTTP/1.1
What I expect is something among those lines (copied from the LUIS web portal)
GET https://westeurope.api.cognitive.microsoft.com/luis/v2.0/apps/?subscription-key=&spellCheck=true&bing-spell-check-subscription-key=&verbose=true&timezoneOffset=0&q=test234
I just had a quick glimpse at the source code and figured ILuisOptions is what I am looking for. There was no concrete implementation to that. It's "roll your own" I guess...
public class MyLuisOptions : ILuisOptions
{
public bool? Log { get; set; }
public bool? SpellCheck { get; set; }
public bool? Staging { get; set; }
public double? TimezoneOffset { get; set; }
public bool? Verbose { get; set; }
public string BingSpellCheckSubscriptionKey { get; set; }
}
...and of course you have to pass this along to the LuisRecognizerMiddleware.
options.Middleware.Add(new LuisRecognizerMiddleware(luisModel, new LuisRecognizerOptions { Verbose = true }, new MyLuisOptions { SpellCheck = true, BingSpellCheckSubscriptionKey = "test123" }));

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

Categories

Resources