LetMeSleepAlready
LetMeSleepAlready

Reputation: 158

swagger parameter on method with parameters from body but no model binding

I am looking into adding an OpenAPI spec to my web API project, but I am running into various obstacles that I am not able to resolve.

API endpoint: /api/some_controller/some_method/id

The content body needs to come from the http body, but I do not want automatic binding using [FromBody] as I need to stream and process the data as-is (auditing, etc).

I added swagger to my project but as expected it does not show a body parameter.

The following DOES generate a proper swagger API definition:

public void some_method([FromBody]MyType mytype);

But as stated before, I need the raw data without model binding.

I am at a loss on how to solve this. Do I need to augment the API explorer somehow? Do I need to add options to swagger? Is there some way to have the [FromBody] attribute that does not actually bind? How can I do this?

Upvotes: 2

Views: 2455

Answers (1)

LetMeSleepAlready
LetMeSleepAlready

Reputation: 158

I managed to get this to work using an extra custom attribute and an IOperationFilter

[AttributeUsage(AttributeTargets.Method)]
public class OpenApiRequestBodyType: Attribute
{
    public Type BodyType { get; }
    public string [] ContentTypes { get; }
    public OpenApiRequestBodyType(Type type, string[] contentTypes = null)
    {
        BodyType = type;
        ContentTypes = contentTypes;
    }
}

public class SwaggerBodyTypeOperationFilter : IOperationFilter
{
    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {
        var bodyTypeAttribute = context.ApiDescription.CustomAttributes().OfType<OpenApiRequestBodyType>().FirstOrDefault();

        if (bodyTypeAttribute != null)
        {
            var schema = context.SchemaGenerator.GenerateSchema(bodyTypeAttribute.BodyType, context.SchemaRepository);

            operation.RequestBody = new OpenApiRequestBody();

            string[] contentTypes;
            if (bodyTypeAttribute.ContentTypes != null)
                contentTypes = bodyTypeAttribute.ContentTypes;
            else
                contentTypes = operation.Responses.Where(x => x.Key =="200").SelectMany(x=>x.Value.Content).Select(x=>x.Key).ToArray();

            foreach (var contentType in contentTypes)
            {
                operation.RequestBody.Content.Add(KeyValuePair.Create(contentType, new OpenApiMediaType { Schema = schema }));
            }
        }
    }
}

Then I simply tag the method:

[OpenApiRequestBodyType(typeof(my_custom_type))]

and in the Startup:

services.AddSwaggerGen(c =>
{
    c.OperationFilter<SwaggerBodyTypeOperationFilter>();
}

I am still not sure if there is no better way to do this.... but at least it works for me...

Upvotes: 4

Related Questions