Browse Source

1. allow the usage of IRemoteContentStream or RemoteContentStream when the incoming stream is not a form content format

2. remove all rewinds related to streams; the user is the one who must ensure his streams are at the correct position when calling the api; stream rewind would exclude the ability of sending partial streams (at least skippable streams, because the tail cannot be limitted in .net core)
3. RemoteStreamContent now must get the content type associated with the stream in its constructor; it may also receive the length of the stream in a read-only form (this is because some stream classes cannot provide the length and it is provided via http headers)
4. IRemoteStreamContent should implement IDisposable to allow the auto clean up of streams (example: FileStream)
pull/9180/head
Alexandru Bagu 5 years ago
parent
commit
e4077d5569
  1. 9
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/AbpRemoteStreamContentModelBinder.cs
  2. 10
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentOutputFormatter.cs
  3. 5
      framework/src/Volo.Abp.Core/Volo/Abp/Content/IRemoteStreamContent.cs
  4. 23
      framework/src/Volo.Abp.Core/Volo/Abp/Content/RemoteStreamContent.cs
  5. 29
      framework/src/Volo.Abp.Core/Volo/Abp/Extensions/StreamExtensions.cs
  6. 5
      framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs
  7. 14
      framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/RequestPayloadBuilder.cs
  8. 17
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentTestController.cs
  9. 26
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentTestController_Tests.cs
  10. 72
      framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs
  11. 6
      framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleAppService.cs

9
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/AbpRemoteStreamContentModelBinder.cs

@ -108,13 +108,14 @@ namespace Volo.Abp.AspNetCore.Mvc.ContentFormatters
if (file.Name.Equals(modelName, StringComparison.OrdinalIgnoreCase))
{
postedFiles.Add(new RemoteStreamContent(file.OpenReadStream())
{
ContentType = file.ContentType
});
postedFiles.Add(new RemoteStreamContent(file.OpenReadStream(), file.ContentType, file.Length));
}
}
}
else if (bindingContext.IsTopLevelObject)
{
postedFiles.Add(new RemoteStreamContent(request.Body, request.ContentType, request.ContentLength));
}
}
}
}

10
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentOutputFormatter.cs

@ -1,4 +1,5 @@
using System;
using System.Buffers;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Net.Http.Headers;
@ -27,13 +28,8 @@ namespace Volo.Abp.AspNetCore.Mvc.ContentFormatters
context.HttpContext.Response.ContentType = remoteStream.ContentType;
using (var stream = remoteStream.GetStream())
{
if (stream.CanSeek)
{
stream.Position = 0;
}
await stream.CopyToAsync(context.HttpContext.Response.Body);
{
await stream.CopyToAsync(context.HttpContext.Response.Body);
}
}
}

5
framework/src/Volo.Abp.Core/Volo/Abp/Content/IRemoteStreamContent.cs

@ -1,8 +1,9 @@
using System.IO;
using System;
using System.IO;
namespace Volo.Abp.Content
{
public interface IRemoteStreamContent
public interface IRemoteStreamContent : IDisposable
{
string ContentType { get; }

23
framework/src/Volo.Abp.Core/Volo/Abp/Content/RemoteStreamContent.cs

@ -4,20 +4,31 @@ namespace Volo.Abp.Content
{
public class RemoteStreamContent : IRemoteStreamContent
{
private readonly Stream _stream;
public RemoteStreamContent(Stream stream)
private readonly Stream _stream;
private readonly string _contentType;
private readonly long? _length;
private readonly bool _leaveOpen;
public RemoteStreamContent(Stream stream, string contentType, long? readOnlylength = null, bool leaveOpen = false)
{
_stream = stream;
_contentType = contentType;
_length = readOnlylength ?? (stream.GetNullableLength() - stream.GetNullablePosition());
_leaveOpen = leaveOpen;
}
public virtual string ContentType { get; set; }
public virtual long? ContentLength => _stream.Length;
public virtual string ContentType => _contentType;
public virtual long? ContentLength => _length;
public virtual Stream GetStream()
{
return _stream;
}
public virtual void Dispose()
{
if (!_leaveOpen)
_stream?.Dispose();
}
}
}

29
framework/src/Volo.Abp.Core/Volo/Abp/Extensions/StreamExtensions.cs

@ -0,0 +1,29 @@
using System.IO;
public static class StreamExtensions
{
public static long? GetNullableLength(this Stream stream)
{
try
{
return stream?.Length;
}
catch
{
/*some stream classes throw exceptions when accessing Length because they do not have access to such information */
return null;
}
}
public static long? GetNullablePosition(this Stream stream)
{
try
{
return stream?.Position;
}
catch
{
/*some stream classes throw exceptions when accessing Position because they do not have access to such information */
return null;
}
}
}

5
framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs

@ -112,10 +112,7 @@ namespace Volo.Abp.Http.Client.DynamicProxying
/* returning a class that holds a reference to response
* content just to be sure that GC does not dispose of
* it before we finish doing our work with the stream */
return (T)(object)new RemoteStreamContent(await responseContent.ReadAsStreamAsync())
{
ContentType = responseContent.Headers.ContentType?.ToString()
};
return (T)(object)new RemoteStreamContent(await responseContent.ReadAsStreamAsync(), responseContent.Headers.ContentType?.ToString(), responseContent.Headers.ContentLength);
}
var stringContent = await responseContent.ReadAsStringAsync();

14
framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/RequestPayloadBuilder.cs

@ -82,15 +82,12 @@ namespace Volo.Abp.Http.Client.DynamicProxying
if (value is IRemoteStreamContent remoteStreamContent)
{
var stream = remoteStreamContent.GetStream();
if (stream.CanSeek)
{
stream.Position = 0;
}
var streamContent = new StreamContent(stream);
if (!remoteStreamContent.ContentType.IsNullOrWhiteSpace())
{
streamContent.Headers.ContentType = new MediaTypeHeaderValue(remoteStreamContent.ContentType);
}
}
streamContent.Headers.ContentLength = stream.GetNullableLength() - stream.GetNullablePosition();
formData.Add(streamContent, parameter.Name, parameter.Name);
}
else if (value is IEnumerable<IRemoteStreamContent> remoteStreamContents)
@ -98,15 +95,12 @@ namespace Volo.Abp.Http.Client.DynamicProxying
foreach (var content in remoteStreamContents)
{
var stream = content.GetStream();
if (stream.CanSeek)
{
stream.Position = 0;
}
var streamContent = new StreamContent(stream);
if (!content.ContentType.IsNullOrWhiteSpace())
{
streamContent.Headers.ContentType = new MediaTypeHeaderValue(content.ContentType);
}
}
streamContent.Headers.ContentLength = stream.GetNullableLength() - stream.GetNullablePosition();
formData.Add(streamContent, parameter.Name, parameter.Name);
}
}

17
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentTestController.cs

@ -16,11 +16,8 @@ namespace Volo.Abp.AspNetCore.Mvc.ContentFormatters
{
var memoryStream = new MemoryStream();
await memoryStream.WriteAsync(Encoding.UTF8.GetBytes("DownloadAsync"));
return new RemoteStreamContent(memoryStream)
{
ContentType = "application/rtf"
};
memoryStream.Position = 0;
return new RemoteStreamContent(memoryStream, "application/rtf");
}
[HttpPost]
@ -32,5 +29,15 @@ namespace Volo.Abp.AspNetCore.Mvc.ContentFormatters
return await reader.ReadToEndAsync() + ":" + file.ContentType;
}
}
[HttpPost]
[Route("Upload-Raw")]
public async Task<string> UploadRawAsync(IRemoteStreamContent file)
{
using (var reader = new StreamReader(file.GetStream()))
{
return await reader.ReadToEndAsync() + ":" + file.ContentType;
}
}
}
}

26
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentTestController_Tests.cs

@ -16,8 +16,8 @@ namespace Volo.Abp.AspNetCore.Mvc.ContentFormatters
var result = await GetResponseAsync("/api/remote-stream-content-test/download");
result.Content.Headers.ContentType?.ToString().ShouldBe("application/rtf");
(await result.Content.ReadAsStringAsync()).ShouldBe("DownloadAsync");
}
}
[Fact]
public async Task UploadAsync()
{
@ -30,12 +30,32 @@ namespace Volo.Abp.AspNetCore.Mvc.ContentFormatters
var streamContent = new StreamContent(memoryStream);
streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/rtf");
requestMessage.Content = new MultipartFormDataContent {{streamContent, "file", "file"}};
requestMessage.Content = new MultipartFormDataContent { { streamContent, "file", "file" } };
var response = await Client.SendAsync(requestMessage);
(await response.Content.ReadAsStringAsync()).ShouldBe("UploadAsync:application/rtf");
}
}
[Fact]
public async Task UploadRawAsync()
{
using (var requestMessage = new HttpRequestMessage(HttpMethod.Post, "/api/remote-stream-content-test/upload-raw"))
{
var memoryStream = new MemoryStream();
var text = @"{ ""hello"": ""world"" }";
await memoryStream.WriteAsync(Encoding.UTF8.GetBytes(text));
memoryStream.Position = 0;
var streamContent = new StreamContent(memoryStream);
streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
requestMessage.Content = streamContent;
var response = await Client.SendAsync(requestMessage);
(await response.Content.ReadAsStringAsync()).ShouldBe($"{text}:application/json");
}
}
}
}

72
framework/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs

@ -49,7 +49,7 @@ namespace Volo.Abp.Http.DynamicProxying
{
var people = await _peopleAppService.GetListAsync(new PagedAndSortedResultRequestDto());
people.TotalCount.ShouldBeGreaterThan(0);
people.Items.Count.ShouldBe((int) people.TotalCount);
people.Items.Count.ShouldBe((int)people.TotalCount);
}
[Fact]
@ -62,7 +62,7 @@ namespace Volo.Abp.Http.DynamicProxying
{
id1,
id2
}, new[] {"name1", "name2"});
}, new[] { "name1", "name2" });
@params.ShouldContain(id1.ToString("N"));
@params.ShouldContain(id2.ToString("N"));
@ -86,11 +86,11 @@ namespace Volo.Abp.Http.DynamicProxying
{
var uniquePersonName = Guid.NewGuid().ToString();
var person = await _peopleAppService.CreateAsync(new PersonDto
{
Name = uniquePersonName,
Age = 42
}
var person = await _peopleAppService.CreateAsync(new PersonDto
{
Name = uniquePersonName,
Age = 42
}
);
person.ShouldNotBeNull();
@ -107,10 +107,10 @@ namespace Volo.Abp.Http.DynamicProxying
{
await Assert.ThrowsAsync<AbpValidationException>(async () =>
{
var person = await _peopleAppService.CreateAsync(new PersonDto
{
Age = 42
}
var person = await _peopleAppService.CreateAsync(new PersonDto
{
Age = 42
}
);
});
}
@ -194,10 +194,20 @@ namespace Volo.Abp.Http.DynamicProxying
var memoryStream = new MemoryStream();
await memoryStream.WriteAsync(Encoding.UTF8.GetBytes("UploadAsync"));
memoryStream.Position = 0;
var result = await _peopleAppService.UploadAsync(new RemoteStreamContent(memoryStream)
{
ContentType = "application/rtf"
});
var result = await _peopleAppService.UploadAsync(new RemoteStreamContent(memoryStream, "application/rtf"));
result.ShouldBe("UploadAsync:application/rtf");
}
[Fact]
public async Task UploadPartialAsync()
{
var memoryStream = new MemoryStream();
var rawData = new byte[16];
var text = Encoding.UTF8.GetBytes("UploadAsync");
await memoryStream.WriteAsync(rawData);
await memoryStream.WriteAsync(text);
memoryStream.Position = rawData.Length;
var result = await _peopleAppService.UploadAsync(new RemoteStreamContent(memoryStream, "application/rtf"));
result.ShouldBe("UploadAsync:application/rtf");
}
@ -214,15 +224,8 @@ namespace Volo.Abp.Http.DynamicProxying
var result = await _peopleAppService.UploadMultipleAsync(new List<IRemoteStreamContent>()
{
new RemoteStreamContent(memoryStream)
{
ContentType = "application/rtf"
},
new RemoteStreamContent(memoryStream2)
{
ContentType = "application/rtf2"
}
new RemoteStreamContent(memoryStream, "application/rtf"),
new RemoteStreamContent(memoryStream2, "application/rtf2")
});
result.ShouldBe("File1:application/rtfFile2:application/rtf2");
}
@ -236,10 +239,7 @@ namespace Volo.Abp.Http.DynamicProxying
var result = await _peopleAppService.CreateFileAsync(new CreateFileInput()
{
Name = "123.rtf",
Content = new RemoteStreamContent(memoryStream)
{
ContentType = "application/rtf"
}
Content = new RemoteStreamContent(memoryStream, "application/rtf")
});
result.ShouldBe("123.rtf:CreateFileAsync:application/rtf");
}
@ -264,23 +264,13 @@ namespace Volo.Abp.Http.DynamicProxying
Name = "123.rtf",
Contents = new List<IRemoteStreamContent>()
{
new RemoteStreamContent(memoryStream)
{
ContentType = "application/rtf"
},
new RemoteStreamContent(memoryStream2)
{
ContentType = "application/rtf2"
}
new RemoteStreamContent(memoryStream, "application/rtf"),
new RemoteStreamContent(memoryStream2, "application/rtf2"),
},
Inner = new CreateFileInput()
{
Name = "789.rtf",
Content = new RemoteStreamContent(memoryStream3)
{
ContentType = "application/rtf3"
}
Content = new RemoteStreamContent(memoryStream3, "application/rtf3")
}
});
result.ShouldBe("123.rtf:File1:application/rtf123.rtf:File2:application/rtf2789.rtf:File3:application/rtf3");

6
framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Application/PeopleAppService.cs

@ -72,11 +72,9 @@ namespace Volo.Abp.TestApp.Application
{
var memoryStream = new MemoryStream();
await memoryStream.WriteAsync(Encoding.UTF8.GetBytes("DownloadAsync"));
memoryStream.Position = 0;
return new RemoteStreamContent(memoryStream)
{
ContentType = "application/rtf"
};
return new RemoteStreamContent(memoryStream, "application/rtf");
}
public async Task<string> UploadAsync(IRemoteStreamContent streamContent)

Loading…
Cancel
Save