Considerations for Multipart support off of HttpRequest
daniel-white opened this issue · 6 comments
I'm experimenting with adding Multipart support to HttpRequest. It will be done similarly to how forms are handled, using the same patterns and MultipartReader. The end goal is to allow MVC and Web API to define models as multipart.
I'm hoping for some feedback on the direction.
- How should
MultipartSectionbe handled? ShouldMicrosoft.AspnetCore.Http.Abstractionsdefine its ownMultipartSection? Or should we move it fromMicrosoft.AspnetCore.WebUtilities? There seems to be no clear reference graph for this. - Should there be a
MultipartValueswhich would work likeStringValues? - Should the
MultipartSectionCollectionbe able to looked up by key and as a list? Not all parts have names.
Thoughts?
Are you planning to move to existing API? My high order bit would be to avoid synchronous APIs that donthr IO. That’s one mistake we made with the Form API.
Can you propose an API shape and the usage pattern you’d expect? That will help determine if this makes sense to make a first class API
I'm not sure at this point about moving APIs. I'm assuming you are referring to HttpRequest.Form as the offending synchronous API. I've omitted it from my example
Here's a concept API shape
class HttpRequest
{
public virtual bool HasMultipartContent { get; }
public virtual Task<IMultipartSectionCollection> ReadMultipartContent(CancellationToken);
}
interface IMultipartSectionCollection : IList<MultipartSection>
{
MultipartValues this[string key];
bool TryGetValues(string key, out MultipartValues values);
}
class MultipartSection
{
string ContentType { get; }
string ContentDisposition { get; }
IHeaderDictionary Headers { get; }
Stream Body { get; }
}
Using it:
HttpRequest request = ...
if (request.HasMultipartContent)
{
IMultipartCollection collection = await request.ReadMultipartContent();
ReadAsList(collection);
// or
ReadByName(collection);
}
void ReadAsList(IMultipartCollection collection)
{
foreach (MultipartSection section in collection)
{
// do work
}
}
void ReadByName(IMultipartCollection collection)
{
if (collection.TryGetValues("name", out MultipartValues values)
{
// do work
}
}
This isn't much of an improvement over HttpRequest.Form.File collection. Rather than adding another buffered collection that you can't even index, let's work to expose the unbuffered consumption pattern in a more first class way. This can be done with with new extension methods in Http.Extensions that call the existing WebUtilities APIs. That way you don't have to move anything around or create any new types.
Let's start with this sample:
https://github.com/aspnet/Entropy/blob/15ed884dbf4d7d7ffdb6fee905ddb0ecb831cbd9/samples/Content.Upload.Multipart/Startup.cs
IsMultipartContentType becomes an HttpRequest extension method bool IsMultipart().
The setup code (https://github.com/aspnet/Entropy/blob/15ed884dbf4d7d7ffdb6fee905ddb0ecb831cbd9/samples/Content.Upload.Multipart/Startup.cs#L29-L32) can all be hidden behind a new extension Task<MultipartSection> GetNextMultipartSectionAsync(); The MultipartReader can be created internally on the first call and cached in the feature collection.
I'm not worried about the nested multipart yet, though a similar approach could be applied.
Hm @Tratcher... Extension method would be nice. I was going for consistency. I'm not a fan of extension methods as properties. Where would the MultipartReader live since the extension method is in a static context?
It can be cached in HttpContext.Features
This issue was moved to dotnet/aspnetcore#2682