3

I want to have a custom attribute to parse data as stream and be testable with Swagger.

So I created controller which reads from POST body:

[SwaggerOperation("Create")] [SwaggerResponse(HttpStatusCode.Created)] public async Task<string> Post([FromContent]Stream contentStream) { using (StreamReader reader = new StreamReader(contentStream, Encoding.UTF8)) { var str = reader.ReadToEnd(); Console.WriteLine(str); } return "OK"; } 

How to define stream so it is visible in Swagger UI?

Here is my implementation of FromContent attribute and ContentParameterBinding binding:

public class ContentParameterBinding : HttpParameterBinding { private struct AsyncVoid{} public ContentParameterBinding(HttpParameterDescriptor descriptor) : base(descriptor) { } public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext, CancellationToken cancellationToken) { var binding = actionContext.ActionDescriptor.ActionBinding; if (binding.ParameterBindings.Length > 1 || actionContext.Request.Method == HttpMethod.Get) { var taskSource = new TaskCompletionSource<AsyncVoid>(); taskSource.SetResult(default(AsyncVoid)); return taskSource.Task as Task; } var type = binding.ParameterBindings[0].Descriptor.ParameterType; if (type == typeof(HttpContent)) { SetValue(actionContext, actionContext.Request.Content); var tcs = new TaskCompletionSource<object>(); tcs.SetResult(actionContext.Request.Content); return tcs.Task; } if (type == typeof(Stream)) { return actionContext.Request.Content .ReadAsStreamAsync() .ContinueWith((task) => { SetValue(actionContext, task.Result); }); } throw new InvalidOperationException("Only HttpContent and Stream are supported for [FromContent] parameters"); } } [AttributeUsage(AttributeTargets.Class | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] public sealed class FromContentAttribute : ParameterBindingAttribute { public override HttpParameterBinding GetBinding(HttpParameterDescriptor parameter) { if (parameter == null) throw new ArgumentException("Invalid parameter"); return new ContentParameterBinding(parameter); } } 

Update

When I create Stream using [FromBody] is shows correctly in Swagger, however Stream is not initiated and ==null

[SwaggerOperation("Create")] [SwaggerResponse(HttpStatusCode.Created)] public async Task<string> Post([FromBody]Stream contentStream) { using (StreamReader reader = new StreamReader(contentStream, Encoding.UTF8)) { var str = reader.ReadToEnd(); Console.WriteLine(str); } return "OK"; } 

Good Post

So I want to have the same UI but with my custom attribute which let's me have Stream from content.

With my custom attribute it shows without TextArea for the parameter but could be tested using Postman and work correctly and Stream is available Bad Post

6
  • How do you want to show it? Where is the problem? you should clarify what output you are expecting Commented Jun 6, 2017 at 14:10
  • @MegaTron I'm trying to add Test for post, where stream has been created from content body, I'll add examples and pictures Commented Jun 6, 2017 at 15:50
  • That whole parameter binding stuff seems like a whole lot of work to avoid doing var stream = await this.Request.Content.ReadAsStreamAsync() in the controller method. Commented Jun 19, 2017 at 22:15
  • @DarrelMiller, Am I missing the right way to show var stream = await this.Request.Content.ReadAsStreamAsync(); in swagger UI? Commented Jun 20, 2017 at 1:09
  • I don't know what needs to be done to tell Swashbuckle that you are accepting a stream. However, when we have to make our code more complex to help drive tooling that generates meta data to autogenerate documentation, if feels like we are doing something wrong. Commented Jun 21, 2017 at 21:12

2 Answers 2

3
+50

Inherit your binding from FormatterParameterBinding class:

public class ContentParameterBinding : FormatterParameterBinding { public ContentParameterBinding(HttpParameterDescriptor descriptor) : base(descriptor, descriptor.Configuration.Formatters, descriptor.Configuration.Services.GetBodyModelValidator()) { } //your code } 
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you for help!
1

Try implementing the interface IValueProviderParameterBinding:

public class ContentParameterBinding : HttpParameterBinding, IValueProviderParameterBinding { public IEnumerable<ValueProviderFactory> ValueProviderFactories { get { return this.Descriptor.Configuration.Services.GetValueProviderFactories(); } } } 

In my case it helped. Also it's generally cleaner as it doesn't inherit FormatterParameterBinding logic, which may not be required.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.