diff --git a/example/back-end/ApiBackEnd/ApiBackEnd.csproj b/example/back-end/ApiBackEnd/ApiBackEnd.csproj index 760f017..03109bf 100644 --- a/example/back-end/ApiBackEnd/ApiBackEnd.csproj +++ b/example/back-end/ApiBackEnd/ApiBackEnd.csproj @@ -120,6 +120,7 @@ + diff --git a/example/back-end/ApiBackEnd/Controllers/ApiUploadController.cs b/example/back-end/ApiBackEnd/Controllers/ApiUploadController.cs index 3fe54cf..e100ae4 100644 --- a/example/back-end/ApiBackEnd/Controllers/ApiUploadController.cs +++ b/example/back-end/ApiBackEnd/Controllers/ApiUploadController.cs @@ -34,6 +34,30 @@ public IHttpActionResult BasicUpload(BasicUploadViewModel info) return Ok(new ClientResponseViewModel(messages)); } + /// + /// Upload attachment to service end-point using streaming. + /// + /// + [Route("basic-upload-streamed")] + [HttpPost] + public IHttpActionResult BasicUploadStreamed(BasicUploadStreamedViewModel info) + { + if (info == null) + { + info = new BasicUploadStreamedViewModel(); + Validate(info); + } + + if (!ModelState.IsValid) + return BadRequest(ModelState); + + var messages = new List(); + messages.Add($"Author information: {info.Author.FullName}"); + messages.Add($"Attachment information: (Mime) {info.StreamedAttachment.MediaType} - (File name) {info.StreamedAttachment.Name}"); + + return Ok(new ClientResponseViewModel(messages)); + } + /// /// Upload a list of attachment with author information. /// diff --git a/example/back-end/ApiBackEnd/ViewModels/BasicUploadStreamedViewModel.cs b/example/back-end/ApiBackEnd/ViewModels/BasicUploadStreamedViewModel.cs new file mode 100644 index 0000000..9f84102 --- /dev/null +++ b/example/back-end/ApiBackEnd/ViewModels/BasicUploadStreamedViewModel.cs @@ -0,0 +1,28 @@ +using System; +using System.ComponentModel.DataAnnotations; +using ApiBackEnd.Models; +using ApiMultiPartFormData.Models; + +namespace ApiBackEnd.ViewModels +{ + public class BasicUploadStreamedViewModel + { + #region Properties + + public Guid Id { get; set; } + + /// + /// Author information. + /// + [Required] + public User Author { get; set; } + + /// + /// Attachment. + /// + [Required] + public StreamedHttpFile StreamedAttachment { get; set; } + + #endregion + } +} \ No newline at end of file diff --git a/lib/ApiMultipartFormDataFormatter/ApiMultiPartFormData.csproj b/lib/ApiMultipartFormDataFormatter/ApiMultiPartFormData.csproj index c34557c..50819fb 100644 --- a/lib/ApiMultipartFormDataFormatter/ApiMultiPartFormData.csproj +++ b/lib/ApiMultipartFormDataFormatter/ApiMultiPartFormData.csproj @@ -56,6 +56,8 @@ + + diff --git a/lib/ApiMultipartFormDataFormatter/Models/HttpFile.cs b/lib/ApiMultipartFormDataFormatter/Models/HttpFile.cs index bccd4c3..61ad0b9 100644 --- a/lib/ApiMultipartFormDataFormatter/Models/HttpFile.cs +++ b/lib/ApiMultipartFormDataFormatter/Models/HttpFile.cs @@ -1,19 +1,9 @@ namespace ApiMultiPartFormData.Models { - public class HttpFile + public class HttpFile : HttpFileBase { #region Properties - /// - /// Name of file. - /// - public string Name { get; set; } - - /// - /// Type of file. - /// - public string MediaType { get; set; } - /// /// Serialized byte stream of file. /// diff --git a/lib/ApiMultipartFormDataFormatter/Models/HttpFileBase.cs b/lib/ApiMultipartFormDataFormatter/Models/HttpFileBase.cs new file mode 100644 index 0000000..40ac7eb --- /dev/null +++ b/lib/ApiMultipartFormDataFormatter/Models/HttpFileBase.cs @@ -0,0 +1,19 @@ +namespace ApiMultiPartFormData.Models +{ + public abstract class HttpFileBase + { + #region Properties + + /// + /// Name of file. + /// + public string Name { get; set; } + + /// + /// Type of file. + /// + public string MediaType { get; set; } + + #endregion + } +} \ No newline at end of file diff --git a/lib/ApiMultipartFormDataFormatter/Models/StreamedHttpFile.cs b/lib/ApiMultipartFormDataFormatter/Models/StreamedHttpFile.cs new file mode 100644 index 0000000..555db24 --- /dev/null +++ b/lib/ApiMultipartFormDataFormatter/Models/StreamedHttpFile.cs @@ -0,0 +1,52 @@ +using System; +using System.IO; + +namespace ApiMultiPartFormData.Models +{ + public class StreamedHttpFile : HttpFileBase + { + #region Properties + + /// + /// Stream containing file contents. + /// + public Stream Stream { get; set; } + + /// + /// Length of file stream + /// + public long StreamLength + { + get + { + return Stream.Length; + } + } + + #endregion + + #region Constructors + + /// + /// Initialize an instance of StreamedHttpFile without any setting. + /// + public StreamedHttpFile() + { + } + + /// + /// Initialize an instance of StreamedHttpFile with parameters. + /// + /// + /// + /// + public StreamedHttpFile(string fileName, string mediaType, Stream stream) + { + Name = fileName; + MediaType = mediaType; + Stream = stream; + } + + #endregion + } +} \ No newline at end of file diff --git a/lib/ApiMultipartFormDataFormatter/MultipartFormDataFormatter.cs b/lib/ApiMultipartFormDataFormatter/MultipartFormDataFormatter.cs index ab6f76e..819df6f 100644 --- a/lib/ApiMultipartFormDataFormatter/MultipartFormDataFormatter.cs +++ b/lib/ApiMultipartFormDataFormatter/MultipartFormDataFormatter.cs @@ -125,17 +125,27 @@ public override async Task ReadFromStreamAsync(Type type, Stream stream, // Content is a file. // File retrieved from client-side. - HttpFile file; + + HttpFileBase file = null; // set null if no content was submitted to have support for [Required] if (httpContent.Headers.ContentLength.GetValueOrDefault() > 0) - file = new HttpFile( - httpContent.Headers.ContentDisposition.FileName.Trim('"'), - httpContent.Headers.ContentType.MediaType, - await httpContent.ReadAsByteArrayAsync() - ); - else - file = null; + { + if (IsStreamingRequested(instance, contentParameter)) + { + file = new StreamedHttpFile( + httpContent.Headers.ContentDisposition.FileName.Trim('"'), + httpContent.Headers.ContentType.MediaType, + await httpContent.ReadAsStreamAsync()); + } + else + { + file = new HttpFile( + httpContent.Headers.ContentDisposition.FileName.Trim('"'), + httpContent.Headers.ContentType.MediaType, + await httpContent.ReadAsByteArrayAsync()); + } + } await BuildRequestModelAsync(instance, parameterParts, file, dependencyScope); } @@ -380,6 +390,11 @@ private PropertyInfo FindPropertyInfoFromPointer(object instance, string name) .FirstOrDefault(x => name.Equals(x.Name, StringComparison.InvariantCultureIgnoreCase)); } + private bool IsStreamingRequested(object instance, string contentParameter) + { + return instance.GetType().GetProperty(contentParameter)?.PropertyType == typeof(StreamedHttpFile); + } + /// /// Check whether text is only numeric or not. ///