mirror of https://github.com/abpframework/abp.git
committed by
GitHub
53 changed files with 1094 additions and 47 deletions
@ -0,0 +1,29 @@ |
|||
using System.Reflection; |
|||
using Microsoft.AspNetCore.Mvc.Abstractions; |
|||
using Microsoft.AspNetCore.Mvc.Controllers; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc |
|||
{ |
|||
public static class ActionDescriptorExtensions |
|||
{ |
|||
public static ControllerActionDescriptor AsControllerActionDescriptor(this ActionDescriptor actionDescriptor) |
|||
{ |
|||
if (!actionDescriptor.IsControllerAction()) |
|||
{ |
|||
throw new AbpException($"{nameof(actionDescriptor)} should be type of {typeof(ControllerActionDescriptor).AssemblyQualifiedName}"); |
|||
} |
|||
|
|||
return actionDescriptor as ControllerActionDescriptor; |
|||
} |
|||
|
|||
public static MethodInfo GetMethodInfo(this ActionDescriptor actionDescriptor) |
|||
{ |
|||
return actionDescriptor.AsControllerActionDescriptor().MethodInfo; |
|||
} |
|||
|
|||
public static bool IsControllerAction(this ActionDescriptor actionDescriptor) |
|||
{ |
|||
return actionDescriptor is ControllerActionDescriptor; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,181 @@ |
|||
using System; |
|||
using System.Linq; |
|||
using System.Reflection; |
|||
using System.Text; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.AspNetCore.Mvc.ApiExplorer; |
|||
using Microsoft.AspNetCore.Mvc.ModelBinding; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Logging.Abstractions; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Abp.Application.Services; |
|||
using Volo.Abp.AspNetCore.Mvc.Utils; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Http.Modeling; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc |
|||
{ |
|||
public class AspNetCoreApiDescriptionModelProvider : IApiDescriptionModelProvider, ISingletonDependency |
|||
{ |
|||
public ILogger<AspNetCoreApiDescriptionModelProvider> Logger { get; set; } |
|||
|
|||
private readonly IApiDescriptionGroupCollectionProvider _descriptionProvider; |
|||
private readonly AbpAspNetCoreMvcOptions _options; |
|||
|
|||
public AspNetCoreApiDescriptionModelProvider( |
|||
IApiDescriptionGroupCollectionProvider descriptionProvider, |
|||
IOptions<AbpAspNetCoreMvcOptions> options) |
|||
{ |
|||
_descriptionProvider = descriptionProvider; |
|||
_options = options.Value; |
|||
|
|||
Logger = NullLogger<AspNetCoreApiDescriptionModelProvider>.Instance; |
|||
} |
|||
|
|||
public ApplicationApiDescriptionModel CreateModel() |
|||
{ |
|||
var model = ApplicationApiDescriptionModel.Create(); |
|||
|
|||
foreach (var descriptionGroupItem in _descriptionProvider.ApiDescriptionGroups.Items) |
|||
{ |
|||
foreach (var apiDescription in descriptionGroupItem.Items) |
|||
{ |
|||
if (!apiDescription.ActionDescriptor.IsControllerAction()) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
AddApiDescriptionToModel(apiDescription, model); |
|||
} |
|||
} |
|||
|
|||
return model; |
|||
} |
|||
|
|||
private void AddApiDescriptionToModel(ApiDescription apiDescription, ApplicationApiDescriptionModel model) |
|||
{ |
|||
var controllerType = apiDescription.ActionDescriptor.AsControllerActionDescriptor().ControllerTypeInfo.AsType(); |
|||
|
|||
var moduleModel = model.GetOrAddModule(GetModuleName(controllerType)); |
|||
|
|||
var controllerModel = moduleModel.GetOrAddController(GetControllerName(apiDescription), controllerType); |
|||
|
|||
var method = apiDescription.ActionDescriptor.GetMethodInfo(); |
|||
|
|||
var uniqueMethodName = GetUniqueActionName(method); |
|||
if (controllerModel.Actions.ContainsKey(uniqueMethodName)) |
|||
{ |
|||
Logger.LogWarning($"Controller '{controllerModel.ControllerName}' contains more than one action with name '{uniqueMethodName}' for module '{moduleModel.Name}'. Ignored: " + method); |
|||
return; |
|||
} |
|||
|
|||
var actionModel = controllerModel.AddAction(ActionApiDescriptionModel.Create( |
|||
method, |
|||
uniqueMethodName, |
|||
apiDescription.RelativePath, |
|||
apiDescription.HttpMethod |
|||
)); |
|||
|
|||
AddParameterDescriptionsToModel(actionModel, method, apiDescription); |
|||
} |
|||
|
|||
private static string GetControllerName(ApiDescription apiDescription) |
|||
{ |
|||
return apiDescription.GroupName?.RemovePostFix(ApplicationService.CommonPostfixes) |
|||
?? apiDescription.ActionDescriptor.AsControllerActionDescriptor().ControllerName; |
|||
} |
|||
|
|||
private static string GetUniqueActionName(MethodInfo method) |
|||
{ |
|||
var methodNameBuilder = new StringBuilder(method.Name); |
|||
|
|||
var parameters = method.GetParameters(); |
|||
if (parameters.Any()) |
|||
{ |
|||
methodNameBuilder.Append("By"); |
|||
|
|||
for (var i = 0; i < parameters.Length; i++) |
|||
{ |
|||
if (i > 0) |
|||
{ |
|||
methodNameBuilder.Append("And"); |
|||
} |
|||
|
|||
methodNameBuilder.Append(parameters[i].Name.ToPascalCase()); |
|||
} |
|||
} |
|||
|
|||
return methodNameBuilder.ToString(); |
|||
} |
|||
|
|||
private void AddParameterDescriptionsToModel(ActionApiDescriptionModel actionModel, MethodInfo method, ApiDescription apiDescription) |
|||
{ |
|||
if (!apiDescription.ParameterDescriptions.Any()) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var matchedMethodParamNames = ArrayMatcher.Match( |
|||
apiDescription.ParameterDescriptions.Select(p => p.Name).ToArray(), |
|||
method.GetParameters().Select(GetMethodParamName).ToArray() |
|||
); |
|||
|
|||
for (var i = 0; i < apiDescription.ParameterDescriptions.Count; i++) |
|||
{ |
|||
var parameterDescription = apiDescription.ParameterDescriptions[i]; |
|||
var matchedMethodParamName = matchedMethodParamNames.Length > i |
|||
? matchedMethodParamNames[i] |
|||
: parameterDescription.Name; |
|||
|
|||
actionModel.AddParameter(ParameterApiDescriptionModel.Create( |
|||
parameterDescription.Name, |
|||
matchedMethodParamName, |
|||
parameterDescription.Type, |
|||
parameterDescription.RouteInfo?.IsOptional ?? false, |
|||
parameterDescription.RouteInfo?.DefaultValue, |
|||
parameterDescription.RouteInfo?.Constraints?.Select(c => c.GetType().Name).ToArray(), |
|||
parameterDescription.Source.Id |
|||
) |
|||
); |
|||
} |
|||
} |
|||
|
|||
public string GetMethodParamName(ParameterInfo parameterInfo) |
|||
{ |
|||
var modelNameProvider = parameterInfo.GetCustomAttributes() |
|||
.OfType<IModelNameProvider>() |
|||
.FirstOrDefault(); |
|||
|
|||
if (modelNameProvider == null) |
|||
{ |
|||
return parameterInfo.Name; |
|||
} |
|||
|
|||
return modelNameProvider.Name; |
|||
} |
|||
|
|||
private string GetModuleName(Type controllerType) |
|||
{ |
|||
if (controllerType == null) |
|||
{ |
|||
return ModuleApiDescriptionModel.DefaultServiceModuleName; |
|||
} |
|||
|
|||
foreach (var controllerSetting in _options.AppServiceControllers.ControllerAssemblySettings) |
|||
{ |
|||
if (Equals(controllerType.Assembly, controllerSetting.Assembly)) |
|||
{ |
|||
return controllerSetting.ModuleName; |
|||
} |
|||
} |
|||
|
|||
var areaAttr = controllerType.GetCustomAttributes().OfType<AreaAttribute>().FirstOrDefault(); |
|||
if (areaAttr != null) |
|||
{ |
|||
return areaAttr.RouteValue; |
|||
} |
|||
|
|||
return ModuleApiDescriptionModel.DefaultServiceModuleName; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.Http.Modeling; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Controllers |
|||
{ |
|||
[Area("abp")] |
|||
public class AbpApiDefinitionController : AbpController |
|||
{ |
|||
private readonly IApiDescriptionModelProvider _modelProvider; |
|||
|
|||
public AbpApiDefinitionController(IApiDescriptionModelProvider modelProvider) |
|||
{ |
|||
_modelProvider = modelProvider; |
|||
} |
|||
|
|||
[HttpGet] |
|||
[Route("api/abp/api-description")] |
|||
public ApplicationApiDescriptionModel Get() |
|||
{ |
|||
return _modelProvider.CreateModel(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Utils |
|||
{ |
|||
internal static class ArrayMatcher |
|||
{ |
|||
public static T[] Match<T>(T[] sourceArray, T[] destinationArray) |
|||
{ |
|||
var result = new List<T>(); |
|||
|
|||
var currentMethodParamIndex = 0; |
|||
var parentItem = default(T); |
|||
|
|||
foreach (var sourceItem in sourceArray) |
|||
{ |
|||
if (currentMethodParamIndex < destinationArray.Length) |
|||
{ |
|||
var destinationItem = destinationArray[currentMethodParamIndex]; |
|||
|
|||
if (EqualityComparer<T>.Default.Equals(sourceItem, destinationItem)) |
|||
{ |
|||
parentItem = default(T); |
|||
currentMethodParamIndex++; |
|||
} |
|||
else |
|||
{ |
|||
if (parentItem == null) |
|||
{ |
|||
parentItem = destinationItem; |
|||
currentMethodParamIndex++; |
|||
} |
|||
} |
|||
} |
|||
|
|||
var resultItem = EqualityComparer<T>.Default.Equals(parentItem, default(T)) ? sourceItem : parentItem; |
|||
result.Add(resultItem); |
|||
} |
|||
|
|||
return result.ToArray(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
using System.Net.Http; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Http.Client.DynamicProxying; |
|||
|
|||
namespace Volo.Abp.AspNetCore.TestBase.DynamicProxying |
|||
{ |
|||
[Dependency(ReplaceServices = true)] |
|||
public class AspNetCoreTestDynamicProxyHttpClientFactory : IDynamicProxyHttpClientFactory, ITransientDependency |
|||
{ |
|||
private readonly ITestServerAccessor _testServerAccessor; |
|||
|
|||
public AspNetCoreTestDynamicProxyHttpClientFactory(ITestServerAccessor testServerAccessor) |
|||
{ |
|||
_testServerAccessor = testServerAccessor; |
|||
} |
|||
|
|||
public HttpClient Create() |
|||
{ |
|||
return _testServerAccessor.Server.CreateClient(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using Microsoft.AspNetCore.TestHost; |
|||
|
|||
namespace Volo.Abp.AspNetCore.TestBase |
|||
{ |
|||
public interface ITestServerAccessor |
|||
{ |
|||
TestServer Server { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
using Microsoft.AspNetCore.TestHost; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.AspNetCore.TestBase |
|||
{ |
|||
public class TestServerAccessor : ITestServerAccessor, ISingletonDependency |
|||
{ |
|||
public TestServer Server { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
using System; |
|||
using Castle.DynamicProxy; |
|||
using Volo.Abp.Castle.DynamicProxy; |
|||
using Volo.Abp.Http.Client; |
|||
using Volo.Abp.Http.Client.DynamicProxying; |
|||
using Volo.Abp.Http.Modeling; |
|||
|
|||
namespace Microsoft.Extensions.DependencyInjection |
|||
{ |
|||
public static class ServiceCollectionDynamicHttpClientProxyExtensions |
|||
{ |
|||
private static readonly ProxyGenerator ProxyGeneratorInstance = new ProxyGenerator(); |
|||
|
|||
//TODO: AddHttpClientProxies for adding all services from single assembly!
|
|||
|
|||
public static IServiceCollection AddHttpClientProxy<T>(this IServiceCollection services, string baseUrl, string moduleName = ModuleApiDescriptionModel.DefaultServiceModuleName) |
|||
{ |
|||
return services.AddHttpClientProxy(typeof(T), baseUrl, moduleName); |
|||
} |
|||
|
|||
public static IServiceCollection AddHttpClientProxy(this IServiceCollection services, Type type, string baseUrl, string moduleName = ModuleApiDescriptionModel.DefaultServiceModuleName) |
|||
{ |
|||
services.Configure<AbpHttpClientOptions>(options => |
|||
{ |
|||
options.HttpClientProxies[type] = new DynamicHttpClientProxyConfig(moduleName, baseUrl, type); |
|||
}); |
|||
|
|||
var interceptorType = typeof(DynamicHttpProxyInterceptor<>).MakeGenericType(type); |
|||
services.AddTransient(interceptorType); |
|||
|
|||
var interceptorAdapterType = typeof(CastleAbpInterceptorAdapter<>).MakeGenericType(interceptorType); |
|||
return services.AddTransient( |
|||
type, |
|||
serviceProvider => ProxyGeneratorInstance |
|||
.CreateInterfaceProxyWithoutTarget( |
|||
type, |
|||
(IInterceptor) serviceProvider.GetRequiredService(interceptorAdapterType) |
|||
) |
|||
); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<AssemblyName>Volo.Abp.Http.Client</AssemblyName> |
|||
<PackageId>Volo.Abp.Http.Client</PackageId> |
|||
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback> |
|||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
|||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
|||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\Volo.Abp.Castle.Core\Volo.Abp.Castle.Core.csproj" /> |
|||
<ProjectReference Include="..\Volo.Abp.Http\Volo.Abp.Http.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,16 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Volo.Abp.Http.Client.DynamicProxying; |
|||
|
|||
namespace Volo.Abp.Http.Client |
|||
{ |
|||
public class AbpHttpClientOptions |
|||
{ |
|||
public Dictionary<Type, DynamicHttpClientProxyConfig> HttpClientProxies { get; set; } |
|||
|
|||
public AbpHttpClientOptions() |
|||
{ |
|||
HttpClientProxies = new Dictionary<Type, DynamicHttpClientProxyConfig>(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace Volo.Abp.Http.Client |
|||
{ |
|||
[DependsOn(typeof(AbpHttpModule))] |
|||
public class AbpHttpClientModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(IServiceCollection services) |
|||
{ |
|||
services.AddAssemblyOf<AbpHttpClientModule>(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,65 @@ |
|||
using System.Collections.Generic; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Newtonsoft.Json; |
|||
using Newtonsoft.Json.Serialization; |
|||
using Nito.AsyncEx; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Http.Modeling; |
|||
|
|||
namespace Volo.Abp.Http.Client.DynamicProxying |
|||
{ |
|||
public class ApplicationApiDescriptionModelManager : IApplicationApiDescriptionModelManager, ISingletonDependency |
|||
{ |
|||
private readonly IDynamicProxyHttpClientFactory _httpClientFactory; |
|||
|
|||
private readonly Dictionary<string, ApplicationApiDescriptionModel> _cache; |
|||
private readonly AsyncLock _asyncLock; |
|||
|
|||
public ApplicationApiDescriptionModelManager(IDynamicProxyHttpClientFactory httpClientFactory) |
|||
{ |
|||
_httpClientFactory = httpClientFactory; |
|||
|
|||
_cache = new Dictionary<string, ApplicationApiDescriptionModel>(); |
|||
_asyncLock = new AsyncLock(); |
|||
} |
|||
|
|||
public async Task<ApplicationApiDescriptionModel> GetAsync(string baseUrl, CancellationToken cancellationToken = default(CancellationToken)) |
|||
{ |
|||
using (await _asyncLock.LockAsync(cancellationToken)) |
|||
{ |
|||
var model = _cache.GetOrDefault(baseUrl); |
|||
if (model == null) |
|||
{ |
|||
_cache[baseUrl] = model = await GetFromServerAsync(baseUrl); |
|||
} |
|||
|
|||
return model; |
|||
} |
|||
} |
|||
|
|||
private async Task<ApplicationApiDescriptionModel> GetFromServerAsync(string baseUrl) |
|||
{ |
|||
using (var client = _httpClientFactory.Create()) |
|||
{ |
|||
var response = await client.GetAsync(baseUrl + "api/abp/api-description"); |
|||
if (!response.IsSuccessStatusCode) |
|||
{ |
|||
throw new AbpException("Remote service returns error!"); |
|||
} |
|||
|
|||
var content = await response.Content.ReadAsStringAsync(); |
|||
|
|||
var result = JsonConvert.DeserializeObject( |
|||
content, |
|||
typeof(ApplicationApiDescriptionModel), |
|||
new JsonSerializerSettings |
|||
{ |
|||
ContractResolver = new CamelCasePropertyNamesContractResolver() |
|||
}); |
|||
|
|||
return (ApplicationApiDescriptionModel)result; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using System.Net.Http; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Volo.Abp.Http.Client.DynamicProxying |
|||
{ |
|||
public class DefaultDynamicProxyHttpClientFactory : IDynamicProxyHttpClientFactory, ITransientDependency |
|||
{ |
|||
public HttpClient Create() |
|||
{ |
|||
return new HttpClient(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
using System; |
|||
|
|||
namespace Volo.Abp.Http.Client.DynamicProxying |
|||
{ |
|||
public class DynamicHttpClientProxyConfig |
|||
{ |
|||
public string BaseUrl { get; } |
|||
|
|||
public string ModuleName { get; } |
|||
|
|||
public Type Type { get; } |
|||
|
|||
public DynamicHttpClientProxyConfig(string moduleName, string baseUrl, Type type) |
|||
{ |
|||
BaseUrl = baseUrl; |
|||
ModuleName = moduleName; |
|||
Type = type; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,133 @@ |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Reflection; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.Options; |
|||
using Newtonsoft.Json; |
|||
using Newtonsoft.Json.Serialization; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.DynamicProxy; |
|||
using Volo.Abp.Http.Modeling; |
|||
using Volo.Abp.Threading; |
|||
|
|||
namespace Volo.Abp.Http.Client.DynamicProxying |
|||
{ |
|||
public class DynamicHttpProxyInterceptor<TService> : AbpInterceptor, ITransientDependency |
|||
{ |
|||
private static readonly MethodInfo GenericInterceptAsyncMethod; |
|||
|
|||
private readonly IDynamicProxyHttpClientFactory _httpClientFactory; |
|||
private readonly IApplicationApiDescriptionModelManager _discoverManager; |
|||
private readonly AbpHttpClientOptions _options; |
|||
|
|||
static DynamicHttpProxyInterceptor() |
|||
{ |
|||
GenericInterceptAsyncMethod = typeof(DynamicHttpProxyInterceptor<TService>) |
|||
.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance) |
|||
.First(m => m.Name == nameof(InterceptAsync) && m.IsGenericMethodDefinition); |
|||
} |
|||
|
|||
public DynamicHttpProxyInterceptor( |
|||
IDynamicProxyHttpClientFactory httpClientFactory, |
|||
IOptions<AbpHttpClientOptions> options, |
|||
IApplicationApiDescriptionModelManager discoverManager) |
|||
{ |
|||
_httpClientFactory = httpClientFactory; |
|||
_discoverManager = discoverManager; |
|||
_options = options.Value; |
|||
} |
|||
|
|||
public override void Intercept(IAbpMethodInvocation invocation) |
|||
{ |
|||
AsyncHelper.RunSync(() => InterceptAsync(invocation)); |
|||
} |
|||
|
|||
public override Task InterceptAsync(IAbpMethodInvocation invocation) |
|||
{ |
|||
invocation.ReturnValue = GenericInterceptAsyncMethod |
|||
.MakeGenericMethod(invocation.Method.ReturnType.GenericTypeArguments[0]) |
|||
.Invoke(this, new object[] { invocation }); |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
|
|||
private async Task<T> InterceptAsync<T>(IAbpMethodInvocation invocation) |
|||
{ |
|||
var config = _options.HttpClientProxies.GetOrDefault(typeof(TService)); |
|||
if (config == null) |
|||
{ |
|||
throw new AbpException($"Could not get DynamicHttpClientProxyConfig for {typeof(T).FullName}."); |
|||
} |
|||
|
|||
var apiDescriptionModel = await _discoverManager.GetAsync(config.BaseUrl); |
|||
|
|||
var action = FindAction(apiDescriptionModel, invocation.Method, config); |
|||
|
|||
using (var client = _httpClientFactory.Create()) |
|||
{ |
|||
var response = await client.GetAsync(config.BaseUrl + action.Url); |
|||
if (!response.IsSuccessStatusCode) |
|||
{ |
|||
throw new AbpException("Remote service returns error!"); |
|||
} |
|||
|
|||
var content = await response.Content.ReadAsStringAsync(); |
|||
|
|||
var result = JsonConvert.DeserializeObject( |
|||
content, |
|||
typeof(T), |
|||
new JsonSerializerSettings |
|||
{ |
|||
ContractResolver = new CamelCasePropertyNamesContractResolver() |
|||
}); |
|||
|
|||
return (T)result; |
|||
} |
|||
} |
|||
|
|||
private ActionApiDescriptionModel FindAction(ApplicationApiDescriptionModel apiDescriptionModel, MethodInfo method, DynamicHttpClientProxyConfig config) |
|||
{ |
|||
var methodParameters = method.GetParameters().ToArray(); |
|||
|
|||
foreach (var module in apiDescriptionModel.Modules.Values) |
|||
{ |
|||
if (module.Name != config.ModuleName) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
foreach (var controller in module.Controllers.Values) |
|||
{ |
|||
if (controller.Interfaces.All(i => i.TypeAsString != typeof(TService).FullName)) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
foreach (var action in controller.Actions.Values) |
|||
{ |
|||
if (action.NameOnClass == method.Name && action.ParametersOnMethod.Count == methodParameters.Length) |
|||
{ |
|||
var found = true; |
|||
|
|||
for (int i = 0; i < methodParameters.Length; i++) |
|||
{ |
|||
if (action.ParametersOnMethod[i].TypeAsString != methodParameters[i].ParameterType.FullName) |
|||
{ |
|||
found = false; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (found) |
|||
{ |
|||
return action; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
throw new AbpException("Could not found remote action for method: " + method); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Http.Modeling; |
|||
|
|||
namespace Volo.Abp.Http.Client.DynamicProxying |
|||
{ |
|||
public interface IApplicationApiDescriptionModelManager |
|||
{ |
|||
Task<ApplicationApiDescriptionModel> GetAsync(string baseUrl, CancellationToken cancellationToken = default(CancellationToken)); |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using System.Net.Http; |
|||
|
|||
namespace Volo.Abp.Http.Client.DynamicProxying |
|||
{ |
|||
public interface IDynamicProxyHttpClientFactory |
|||
{ |
|||
HttpClient Create(); |
|||
} |
|||
} |
|||
@ -0,0 +1,53 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Reflection; |
|||
|
|||
namespace Volo.Abp.Http.Modeling |
|||
{ |
|||
[Serializable] |
|||
public class ActionApiDescriptionModel |
|||
{ |
|||
public string UniqueName { get; set; } |
|||
|
|||
public string NameOnClass { get; set; } |
|||
|
|||
public string HttpMethod { get; set; } |
|||
|
|||
public string Url { get; set; } |
|||
|
|||
public IList<MethodParameterApiDescriptionModel> ParametersOnMethod { get; set; } |
|||
|
|||
public IList<ParameterApiDescriptionModel> Parameters { get; set; } |
|||
|
|||
public ReturnValueApiDescriptionModel ReturnValue { get; set; } |
|||
|
|||
private ActionApiDescriptionModel() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public static ActionApiDescriptionModel Create(MethodInfo method, string uniqueName, string url, string httpMethod = null) |
|||
{ |
|||
return new ActionApiDescriptionModel |
|||
{ |
|||
UniqueName = uniqueName, |
|||
NameOnClass = method.Name, |
|||
Url = url, |
|||
HttpMethod = httpMethod, |
|||
ReturnValue = new ReturnValueApiDescriptionModel(method.ReturnType), |
|||
Parameters = new List<ParameterApiDescriptionModel>(), |
|||
ParametersOnMethod = method |
|||
.GetParameters() |
|||
.Select(MethodParameterApiDescriptionModel.Create) |
|||
.ToList() |
|||
}; |
|||
} |
|||
|
|||
public ParameterApiDescriptionModel AddParameter(ParameterApiDescriptionModel parameter) |
|||
{ |
|||
Parameters.Add(parameter); |
|||
return parameter; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,57 @@ |
|||
using System; |
|||
using System.Collections.Concurrent; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
|
|||
namespace Volo.Abp.Http.Modeling |
|||
{ |
|||
[Serializable] |
|||
public class ApplicationApiDescriptionModel |
|||
{ |
|||
public IDictionary<string, ModuleApiDescriptionModel> Modules { get; set; } |
|||
|
|||
private ApplicationApiDescriptionModel() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public static ApplicationApiDescriptionModel Create() |
|||
{ |
|||
return new ApplicationApiDescriptionModel |
|||
{ |
|||
//TODO: Why ConcurrentDictionary?
|
|||
Modules = new ConcurrentDictionary<string, ModuleApiDescriptionModel>() |
|||
}; |
|||
} |
|||
|
|||
public ModuleApiDescriptionModel AddModule(ModuleApiDescriptionModel module) |
|||
{ |
|||
if (Modules.ContainsKey(module.Name)) |
|||
{ |
|||
throw new AbpException("There is already a module with same name: " + module.Name); |
|||
} |
|||
|
|||
return Modules[module.Name] = module; |
|||
} |
|||
|
|||
public ModuleApiDescriptionModel GetOrAddModule(string name) |
|||
{ |
|||
return Modules.GetOrAdd(name, () => ModuleApiDescriptionModel.Create(name)); |
|||
} |
|||
|
|||
public ApplicationApiDescriptionModel CreateSubModel(string[] modules = null, string[] controllers = null, string[] actions = null) |
|||
{ |
|||
var subModel = new ApplicationApiDescriptionModel(); |
|||
|
|||
foreach (var module in Modules.Values) |
|||
{ |
|||
if (modules == null || modules.Contains(module.Name)) |
|||
{ |
|||
subModel.AddModule(module.CreateSubModel(controllers, actions)); |
|||
} |
|||
} |
|||
|
|||
return subModel; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,70 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
|
|||
namespace Volo.Abp.Http.Modeling |
|||
{ |
|||
[Serializable] |
|||
public class ControllerApiDescriptionModel |
|||
{ |
|||
public string ControllerName { get; set; } |
|||
|
|||
public string TypeAsString { get; set; } |
|||
|
|||
public List<ControllerInterfaceApiDescriptionModel> Interfaces { get; set; } |
|||
|
|||
public Dictionary<string, ActionApiDescriptionModel> Actions { get; set; } |
|||
|
|||
private ControllerApiDescriptionModel() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public static ControllerApiDescriptionModel Create(string controllerName, Type type) |
|||
{ |
|||
return new ControllerApiDescriptionModel |
|||
{ |
|||
ControllerName = controllerName, |
|||
TypeAsString = type.FullName, |
|||
Actions = new Dictionary<string, ActionApiDescriptionModel>(), |
|||
Interfaces = type |
|||
.GetInterfaces() |
|||
.Select(ControllerInterfaceApiDescriptionModel.Create) |
|||
.ToList() |
|||
}; |
|||
} |
|||
|
|||
public ActionApiDescriptionModel AddAction(ActionApiDescriptionModel action) |
|||
{ |
|||
if (Actions.ContainsKey(action.UniqueName)) |
|||
{ |
|||
throw new AbpException( |
|||
$"Can not add more than one action with same name to the same controller. Controller: {ControllerName}, Action: {action.UniqueName}." |
|||
); |
|||
} |
|||
|
|||
return Actions[action.UniqueName] = action; |
|||
} |
|||
|
|||
public ControllerApiDescriptionModel CreateSubModel(string[] actions) |
|||
{ |
|||
var subModel = new ControllerApiDescriptionModel |
|||
{ |
|||
TypeAsString = TypeAsString, |
|||
Interfaces = Interfaces, |
|||
ControllerName = ControllerName, |
|||
Actions = new Dictionary<string, ActionApiDescriptionModel>() |
|||
}; |
|||
|
|||
foreach (var action in Actions.Values) |
|||
{ |
|||
if (actions == null || actions.Contains(action.UniqueName)) |
|||
{ |
|||
subModel.AddAction(action); |
|||
} |
|||
} |
|||
|
|||
return subModel; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
using System; |
|||
|
|||
namespace Volo.Abp.Http.Modeling |
|||
{ |
|||
[Serializable] |
|||
public class ControllerInterfaceApiDescriptionModel |
|||
{ |
|||
public string TypeAsString { get; set; } |
|||
|
|||
private ControllerInterfaceApiDescriptionModel() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public static ControllerInterfaceApiDescriptionModel Create(Type type) |
|||
{ |
|||
return new ControllerInterfaceApiDescriptionModel |
|||
{ |
|||
TypeAsString = type.FullName |
|||
}; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
namespace Volo.Abp.Http.Modeling |
|||
{ |
|||
public interface IApiDescriptionModelProvider |
|||
{ |
|||
ApplicationApiDescriptionModel CreateModel(); |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
using System; |
|||
using System.Reflection; |
|||
|
|||
namespace Volo.Abp.Http.Modeling |
|||
{ |
|||
[Serializable] |
|||
public class MethodParameterApiDescriptionModel |
|||
{ |
|||
public string Name { get; set; } |
|||
|
|||
public string TypeAsString { get; set; } |
|||
|
|||
public bool IsOptional { get; set; } |
|||
|
|||
public object DefaultValue { get; set; } |
|||
|
|||
private MethodParameterApiDescriptionModel() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public static MethodParameterApiDescriptionModel Create(ParameterInfo parameterInfo) |
|||
{ |
|||
return new MethodParameterApiDescriptionModel |
|||
{ |
|||
Name = parameterInfo.Name, |
|||
TypeAsString = parameterInfo.ParameterType.FullName, |
|||
IsOptional = parameterInfo.IsOptional, |
|||
DefaultValue = parameterInfo.HasDefaultValue ? parameterInfo.DefaultValue : null |
|||
}; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,63 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
|
|||
namespace Volo.Abp.Http.Modeling |
|||
{ |
|||
[Serializable] |
|||
public class ModuleApiDescriptionModel |
|||
{ |
|||
/// <summary>
|
|||
/// "app".
|
|||
/// </summary>
|
|||
public const string DefaultServiceModuleName = "app"; |
|||
|
|||
public string Name { get; set; } |
|||
|
|||
public IDictionary<string, ControllerApiDescriptionModel> Controllers { get; set; } |
|||
|
|||
private ModuleApiDescriptionModel() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public static ModuleApiDescriptionModel Create(string name) |
|||
{ |
|||
return new ModuleApiDescriptionModel |
|||
{ |
|||
Name = name, |
|||
Controllers = new Dictionary<string, ControllerApiDescriptionModel>() |
|||
}; |
|||
} |
|||
|
|||
public ControllerApiDescriptionModel AddController(ControllerApiDescriptionModel controller) |
|||
{ |
|||
if (Controllers.ContainsKey(controller.ControllerName)) |
|||
{ |
|||
throw new AbpException($"There is already a controller with name: {controller.ControllerName} in module: {Name}"); |
|||
} |
|||
|
|||
return Controllers[controller.ControllerName] = controller; |
|||
} |
|||
|
|||
public ControllerApiDescriptionModel GetOrAddController(string name, Type type) |
|||
{ |
|||
return Controllers.GetOrAdd(name, () => ControllerApiDescriptionModel.Create(name, type)); |
|||
} |
|||
|
|||
public ModuleApiDescriptionModel CreateSubModel(string[] controllers, string[] actions) |
|||
{ |
|||
var subModel = ModuleApiDescriptionModel.Create(Name); |
|||
|
|||
foreach (var controller in Controllers.Values) |
|||
{ |
|||
if (controllers == null || controllers.Contains(controller.ControllerName)) |
|||
{ |
|||
subModel.AddController(controller.CreateSubModel(actions)); |
|||
} |
|||
} |
|||
|
|||
return subModel; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,44 @@ |
|||
using System; |
|||
|
|||
namespace Volo.Abp.Http.Modeling |
|||
{ |
|||
[Serializable] |
|||
public class ParameterApiDescriptionModel |
|||
{ |
|||
public string NameOnMethod { get; set; } |
|||
|
|||
public string Name { get; set; } |
|||
|
|||
public Type Type { get; set; } |
|||
|
|||
public string TypeAsString { get; set; } |
|||
|
|||
public bool IsOptional { get; set; } |
|||
|
|||
public object DefaultValue { get; set; } |
|||
|
|||
public string[] ConstraintTypes { get; set; } |
|||
|
|||
public string BindingSourceId { get; set; } |
|||
|
|||
private ParameterApiDescriptionModel() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public static ParameterApiDescriptionModel Create(string name, string nameOnMethod, Type type, bool isOptional = false, object defaultValue = null, string[] constraintTypes = null, string bindingSourceId = null) |
|||
{ |
|||
return new ParameterApiDescriptionModel |
|||
{ |
|||
Name = name, |
|||
NameOnMethod = nameOnMethod, |
|||
Type = type, |
|||
TypeAsString = type.FullName, |
|||
IsOptional = isOptional, |
|||
DefaultValue = defaultValue, |
|||
ConstraintTypes = constraintTypes, |
|||
BindingSourceId = bindingSourceId |
|||
}; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
using System; |
|||
|
|||
namespace Volo.Abp.Http.Modeling |
|||
{ |
|||
[Serializable] |
|||
public class ReturnValueApiDescriptionModel |
|||
{ |
|||
public Type Type { get; } |
|||
public string TypeAsString { get; } |
|||
|
|||
public ReturnValueApiDescriptionModel(Type type) |
|||
{ |
|||
Type = type; |
|||
TypeAsString = type.FullName; |
|||
} |
|||
} |
|||
} |
|||
@ -1,13 +0,0 @@ |
|||
using System.Reflection; |
|||
|
|||
namespace System |
|||
{ |
|||
public static class AbpTypeExtensions |
|||
{ |
|||
//TODO: This method can be removed because not needed anymore!
|
|||
public static Assembly GetAssembly(this Type type) |
|||
{ |
|||
return type.GetTypeInfo().Assembly; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.AspNetCore.Mvc.Utils |
|||
{ |
|||
public class ArrayMacther_Tests |
|||
{ |
|||
[Theory] |
|||
[InlineData( |
|||
new[] { "p1", "p2.a1", "p2.a2.b1", "p2.a2.b2", "p3", "p4", "p5.c1", "p5.c2" }, |
|||
new[] { "p1", "p2", "p3", "p4", "p5" }, |
|||
new[] { "p1", "p2", "p2", "p2", "p3", "p4", "p5", "p5" }) |
|||
] |
|||
public void Should_Find_Correct_Items(string[] sourceArray, string[] destinationArray, string[] expectedArray) |
|||
{ |
|||
var result = ArrayMatcher.Match(sourceArray, destinationArray); |
|||
Assert.Equal(expectedArray, result); |
|||
} |
|||
} |
|||
} |
|||
@ -1,3 +1,4 @@ |
|||
using Volo.Abp.AspNetCore; |
|||
using Volo.Abp.AspNetCore.App; |
|||
|
|||
namespace Volo.Abp.Http |
|||
@ -1,15 +1,18 @@ |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Volo.Abp.AspNetCore.App; |
|||
using Volo.Abp.Http.Client; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.TestApp.Application; |
|||
|
|||
namespace Volo.Abp.Http |
|||
{ |
|||
[DependsOn(typeof(AbpAspNetCoreMvcTestModule), typeof(AbpHttpModule))] |
|||
[DependsOn(typeof(AbpAspNetCoreMvcTestModule), typeof(AbpHttpClientModule))] |
|||
public class AbpHttpTestModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(IServiceCollection services) |
|||
{ |
|||
services.AddAssemblyOf<AbpAspNetCoreMvcTestModule>(); |
|||
services.AddAssemblyOf<AbpHttpTestModule>(); |
|||
services.AddHttpClientProxy<IPeopleAppService>("/"); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue