Browse Source

added streaming support

pull/5733/head
Alexandru Bagu 5 years ago
parent
commit
7b9966fa4d
  1. 3
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs
  2. 26
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs
  3. 25
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Content/InternalRemoteStreamContent.cs
  4. 27
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Content/RemoteStreamContentInputFormatter.cs
  5. 29
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Content/RemoteStreamContentOutputFormatter.cs
  6. 12
      framework/src/Volo.Abp.Core/Volo/Abp/Content/IRemoteStreamContent.cs
  7. 24
      framework/src/Volo.Abp.Core/Volo/Abp/Content/RemoteStreamContent.cs
  8. 15
      framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/Content/ReferencedRemoteStreamContent.cs
  9. 28
      framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs
  10. 26
      framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/RequestPayloadBuilder.cs

3
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs

@ -37,6 +37,7 @@ using Volo.Abp.Http.Modeling;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.UI;
using Volo.Abp.AspNetCore.Mvc.Content;
namespace Volo.Abp.AspNetCore.Mvc
{
@ -172,6 +173,8 @@ namespace Volo.Abp.AspNetCore.Mvc
Configure<MvcOptions>(mvcOptions =>
{
mvcOptions.AddAbp(context.Services);
mvcOptions.InputFormatters.Insert(0, new RemoteStreamContentInputFormatter());
mvcOptions.OutputFormatters.Insert(0, new RemoteStreamContentOutputFormatter());
});
Configure<AbpEndpointRouterOptions>(options =>

26
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
@ -282,10 +283,21 @@ namespace Volo.Abp.AspNetCore.Mvc
return;
}
var matchedMethodParamNames = ArrayMatcher.Match(
apiDescription.ParameterDescriptions.Select(p => p.Name).ToArray(),
method.GetParameters().Select(GetMethodParamName).ToArray()
);
/*
* --- bug report ---
ArrayMatcher.Match has the following bug:
sourceArray: [ "test1", "test2", "test3" ],
destinationArray: [ "test2", "test3" ],
expectedResult: [ "test2", "test3" ],
result: [ "test1", "test1" ]
*/
/*Because of ArrayMatcher.Match bug parameters that are
* service provided by FromServicesAttribute must be excluded
* because Microsoft's API Explorer does not include them
*/
var parameterDescriptionNames = apiDescription.ParameterDescriptions.Select(p => p.Name).ToArray();
var methodParameterNames = method.GetParameters().Where(NotServiceProvidedParam).Select(GetMethodParamName).ToArray();
var matchedMethodParamNames = ArrayMatcher.Match(parameterDescriptionNames, methodParameterNames);
for (var i = 0; i < apiDescription.ParameterDescriptions.Count; i++)
{
@ -310,6 +322,12 @@ namespace Volo.Abp.AspNetCore.Mvc
}
}
private bool NotServiceProvidedParam(ParameterInfo parameterInfo)
{
var fromServicesAttribute = parameterInfo.GetCustomAttribute<FromServicesAttribute>();
return fromServicesAttribute == null;
}
public string GetMethodParamName(ParameterInfo parameterInfo)
{
var modelNameProvider = parameterInfo.GetCustomAttributes()

25
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Content/InternalRemoteStreamContent.cs

@ -0,0 +1,25 @@
using System.IO;
using Microsoft.AspNetCore.Http;
using Volo.Abp.Content;
namespace Volo.Abp.AspNetCore.Mvc.Content
{
internal class InternalRemoteStreamContent : IRemoteStreamContent
{
private readonly HttpContext _httpContext;
public InternalRemoteStreamContent(HttpContext httpContext)
{
_httpContext = httpContext;
}
public string ContentType => _httpContext.Request.ContentType;
public long? ContentLength => _httpContext.Request.ContentLength;
public Stream GetStream()
{
return _httpContext.Request.Body;
}
}
}

27
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Content/RemoteStreamContentInputFormatter.cs

@ -0,0 +1,27 @@
using System;
using System.Threading.Tasks;
using Microsoft.Net.Http.Headers;
using Microsoft.AspNetCore.Mvc.Formatters;
using Volo.Abp.Content;
namespace Volo.Abp.AspNetCore.Mvc.Content
{
public class RemoteStreamContentInputFormatter : InputFormatter
{
public RemoteStreamContentInputFormatter()
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("*/*"));
}
protected override bool CanReadType(Type type)
{
return typeof(IRemoteStreamContent) == type;
}
public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
{
var stream = new InternalRemoteStreamContent(context.HttpContext);
return InputFormatterResult.SuccessAsync(stream);
}
}
}

29
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Content/RemoteStreamContentOutputFormatter.cs

@ -0,0 +1,29 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Net.Http.Headers;
using Volo.Abp.Content;
namespace Volo.Abp.AspNetCore.Mvc.Content
{
public class RemoteStreamContentOutputFormatter : OutputFormatter
{
public RemoteStreamContentOutputFormatter()
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("*/*"));
}
protected override bool CanWriteType(Type type)
{
return typeof(IRemoteStreamContent).IsAssignableFrom(type);
}
public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context)
{
var httpContext = context.HttpContext;
var remoteStream = context.Object as IRemoteStreamContent;
using (var stream = remoteStream.GetStream())
await stream.CopyToAsync(httpContext.Response.Body);
}
}
}

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

@ -0,0 +1,12 @@
using System;
using System.IO;
namespace Volo.Abp.Content
{
public interface IRemoteStreamContent
{
string ContentType { get; }
long? ContentLength { get; }
Stream GetStream();
}
}

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

@ -0,0 +1,24 @@
using System;
using System.IO;
namespace Volo.Abp.Content
{
public class RemoteStreamContent : IRemoteStreamContent
{
private readonly Stream _stream;
public RemoteStreamContent(Stream stream)
{
_stream = stream;
}
public virtual string ContentType { get; set; }
public virtual long? ContentLength => _stream.Length;
public virtual Stream GetStream()
{
return _stream;
}
}
}

15
framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/Content/ReferencedRemoteStreamContent.cs

@ -0,0 +1,15 @@
using System.IO;
using Volo.Abp.Content;
namespace Volo.Abp.Http.Client.Content
{
internal class ReferencedRemoteStreamContent : RemoteStreamContent
{
private readonly object[] references;
public ReferencedRemoteStreamContent(Stream stream, params object[] references) : base(stream)
{
this.references = references;
}
}
}

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

@ -10,9 +10,11 @@ using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Volo.Abp.Content;
using Volo.Abp.DependencyInjection;
using Volo.Abp.DynamicProxy;
using Volo.Abp.Http.Client.Authentication;
using Volo.Abp.Http.Client.Content;
using Volo.Abp.Http.Modeling;
using Volo.Abp.Http.ProxyScripting.Generators;
using Volo.Abp.Json;
@ -103,17 +105,27 @@ namespace Volo.Abp.Http.Client.DynamicProxying
private async Task<T> MakeRequestAndGetResultAsync<T>(IAbpMethodInvocation invocation)
{
var responseAsString = await MakeRequestAsync(invocation);
var responseContent = await MakeRequestAsync(invocation);
if (typeof(T) == typeof(string))
if (typeof(T) == typeof(IRemoteStreamContent))
{
return (T)Convert.ChangeType(responseAsString, typeof(T));
/*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 ReferencedRemoteStreamContent(await responseContent.ReadAsStreamAsync(), responseContent));
}
else
{
var stringContent = await responseContent.ReadAsStringAsync();
if (typeof(T) == typeof(string))
return (T)((object)stringContent);
return JsonSerializer.Deserialize<T>(await responseContent.ReadAsStringAsync());
}
return JsonSerializer.Deserialize<T>(responseAsString);
}
private async Task<string> MakeRequestAsync(IAbpMethodInvocation invocation)
private async Task<HttpContent> MakeRequestAsync(IAbpMethodInvocation invocation)
{
var clientConfig = ClientOptions.HttpClientProxies.GetOrDefault(typeof(TService)) ?? throw new AbpException($"Could not get DynamicHttpClientProxyConfig for {typeof(TService).FullName}.");
var remoteServiceConfig = AbpRemoteServiceOptions.RemoteServices.GetConfigurationOrDefault(clientConfig.RemoteServiceName);
@ -140,14 +152,16 @@ namespace Volo.Abp.Http.Client.DynamicProxying
)
);
var response = await client.SendAsync(requestMessage, GetCancellationToken());
var response = await client.SendAsync(requestMessage,
HttpCompletionOption.ResponseHeadersRead /*this will buffer only the headers, the content will be used as a stream*/,
GetCancellationToken());
if (!response.IsSuccessStatusCode)
{
await ThrowExceptionForResponseAsync(response);
}
return await response.Content.ReadAsStringAsync();
return response.Content;
}
private ApiVersionInfo GetApiVersionInfo(ActionApiDescriptionModel action)

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

@ -3,33 +3,35 @@ using System.Linq;
using System.Net.Http;
using System.Text;
using JetBrains.Annotations;
using Volo.Abp.Content;
using Volo.Abp.Http.Modeling;
using Volo.Abp.Http.ProxyScripting.Generators;
using Volo.Abp.Json;
using Volo.Abp.Reflection;
namespace Volo.Abp.Http.Client.DynamicProxying
{
public static class RequestPayloadBuilder
{
[CanBeNull]
public static HttpContent BuildContent(ActionApiDescriptionModel action,IReadOnlyDictionary<string, object> methodArguments, IJsonSerializer jsonSerializer, ApiVersionInfo apiVersion)
public static HttpContent BuildContent(ActionApiDescriptionModel action, IReadOnlyDictionary<string, object> methodArguments, IJsonSerializer jsonSerializer, ApiVersionInfo apiVersion)
{
var body = GenerateBody(action, methodArguments, jsonSerializer);
if (body != null)
{
return new StringContent(body, Encoding.UTF8, MimeTypes.Application.Json);
return body;
}
body = GenerateFormPostData(action, methodArguments);
if (body != null)
{
return new StringContent(body, Encoding.UTF8, MimeTypes.Application.XWwwFormUrlencoded);
return body;
}
return null;
}
private static string GenerateBody(ActionApiDescriptionModel action, IReadOnlyDictionary<string, object> methodArguments, IJsonSerializer jsonSerializer)
private static HttpContent GenerateBody(ActionApiDescriptionModel action, IReadOnlyDictionary<string, object> methodArguments, IJsonSerializer jsonSerializer)
{
var parameters = action
.Parameters
@ -54,10 +56,20 @@ namespace Volo.Abp.Http.Client.DynamicProxying
return null;
}
return jsonSerializer.Serialize(value);
if (value is IRemoteStreamContent remoteStreamContent)
{
var content = new StreamContent(remoteStreamContent.GetStream());
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(remoteStreamContent.ContentType);
content.Headers.ContentLength = remoteStreamContent.ContentLength;
return content;
}
else
{
return new StringContent(jsonSerializer.Serialize(value), Encoding.UTF8, MimeTypes.Application.Json);
}
}
private static string GenerateFormPostData(ActionApiDescriptionModel action, IReadOnlyDictionary<string, object> methodArguments)
private static HttpContent GenerateFormPostData(ActionApiDescriptionModel action, IReadOnlyDictionary<string, object> methodArguments)
{
var parameters = action
.Parameters
@ -86,7 +98,7 @@ namespace Volo.Abp.Http.Client.DynamicProxying
isFirstParam = false;
}
return postDataBuilder.ToString();
return new StringContent(postDataBuilder.ToString(), Encoding.UTF8, MimeTypes.Application.XWwwFormUrlencoded);
}
}
}

Loading…
Cancel
Save