diff --git a/Volo.Abp.sln b/Volo.Abp.sln
index d6e55fe8f7..4c62a97e82 100644
--- a/Volo.Abp.sln
+++ b/Volo.Abp.sln
@@ -128,9 +128,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.MemoryDb.Tests", "
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.TestApp.Tests", "test\Volo.Abp.TestApp.Tests\Volo.Abp.TestApp.Tests.csproj", "{4C2F7B03-C598-4432-A43A-B065D9D0712F}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Http", "src\Volo.Abp.Http\Volo.Abp.Http.csproj", "{01A70034-D353-4BF9-821D-F2B6F7641532}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Http", "src\Volo.Abp.Http\Volo.Abp.Http.csproj", "{01A70034-D353-4BF9-821D-F2B6F7641532}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Http.Tests", "test\Volo.Abp.Http.Tests\Volo.Abp.Http.Tests.csproj", "{5B86E837-0C89-4058-A84B-0B51F2F573D4}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Http.Client", "src\Volo.Abp.Http.Client\Volo.Abp.Http.Client.csproj", "{D5E2FB37-0194-480A-B952-5FFECC1200EB}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Http.Client.Tests", "test\Volo.Abp.Http.Client.Tests\Volo.Abp.Http.Client.Tests.csproj", "{703BD43C-02B9-413F-854C-9CBA0C963196}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -326,10 +328,14 @@ Global
{01A70034-D353-4BF9-821D-F2B6F7641532}.Debug|Any CPU.Build.0 = Debug|Any CPU
{01A70034-D353-4BF9-821D-F2B6F7641532}.Release|Any CPU.ActiveCfg = Release|Any CPU
{01A70034-D353-4BF9-821D-F2B6F7641532}.Release|Any CPU.Build.0 = Release|Any CPU
- {5B86E837-0C89-4058-A84B-0B51F2F573D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {5B86E837-0C89-4058-A84B-0B51F2F573D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {5B86E837-0C89-4058-A84B-0B51F2F573D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {5B86E837-0C89-4058-A84B-0B51F2F573D4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D5E2FB37-0194-480A-B952-5FFECC1200EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D5E2FB37-0194-480A-B952-5FFECC1200EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D5E2FB37-0194-480A-B952-5FFECC1200EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D5E2FB37-0194-480A-B952-5FFECC1200EB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {703BD43C-02B9-413F-854C-9CBA0C963196}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {703BD43C-02B9-413F-854C-9CBA0C963196}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {703BD43C-02B9-413F-854C-9CBA0C963196}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {703BD43C-02B9-413F-854C-9CBA0C963196}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -391,7 +397,8 @@ Global
{D0279C94-E9A3-4A1B-968B-D3BBF3E06FD8} = {37087D1B-3693-4E96-983D-A69F210BDE53}
{4C2F7B03-C598-4432-A43A-B065D9D0712F} = {37087D1B-3693-4E96-983D-A69F210BDE53}
{01A70034-D353-4BF9-821D-F2B6F7641532} = {4C753F64-0C93-4D65-96C2-A40893AFC1E8}
- {5B86E837-0C89-4058-A84B-0B51F2F573D4} = {37087D1B-3693-4E96-983D-A69F210BDE53}
+ {D5E2FB37-0194-480A-B952-5FFECC1200EB} = {4C753F64-0C93-4D65-96C2-A40893AFC1E8}
+ {703BD43C-02B9-413F-854C-9CBA0C963196} = {37087D1B-3693-4E96-983D-A69F210BDE53}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5}
diff --git a/src/Volo.Abp.AspNetCore.Mvc/Properties/AssemblyInfo.cs b/src/Volo.Abp.AspNetCore.Mvc/Properties/AssemblyInfo.cs
index 214a7fbd4f..61be6f3536 100644
--- a/src/Volo.Abp.AspNetCore.Mvc/Properties/AssemblyInfo.cs
+++ b/src/Volo.Abp.AspNetCore.Mvc/Properties/AssemblyInfo.cs
@@ -1,4 +1,5 @@
using System.Reflection;
+using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
@@ -9,6 +10,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyProduct("Volo.Abp.AspNetCore.Mvc")]
[assembly: AssemblyTrademark("")]
+[assembly:InternalsVisibleTo("Volo.Abp.AspNetCore.Mvc.Tests")]
+
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj b/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj
index 828bdfbb27..da9a2c3a86 100644
--- a/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj
+++ b/src/Volo.Abp.AspNetCore.Mvc/Volo.Abp.AspNetCore.Mvc.csproj
@@ -13,6 +13,7 @@
+
diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAppServiceConvention.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAppServiceConvention.cs
index c04e563097..25e9265ab1 100644
--- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAppServiceConvention.cs
+++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAppServiceConvention.cs
@@ -11,6 +11,7 @@ using Microsoft.Extensions.Options;
using Volo.Abp.Application.Services;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Http;
+using Volo.Abp.Http.Modeling;
using Volo.Abp.Reflection;
namespace Volo.Abp.AspNetCore.Mvc
@@ -245,7 +246,7 @@ namespace Volo.Abp.AspNetCore.Mvc
protected virtual string GetModuleNameOrDefault(Type controllerType)
{
return GetControllerSettingOrNull(controllerType)?.ModuleName ??
- AbpControllerAssemblySetting.DefaultServiceModuleName;
+ ModuleApiDescriptionModel.DefaultServiceModuleName;
}
[CanBeNull]
diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpControllerAssemblySetting.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpControllerAssemblySetting.cs
index ed89e7ae9b..56f4fa0142 100644
--- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpControllerAssemblySetting.cs
+++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpControllerAssemblySetting.cs
@@ -6,11 +6,6 @@ namespace Volo.Abp.AspNetCore.Mvc
{
public class AbpControllerAssemblySetting
{
- ///
- /// "app".
- ///
- public const string DefaultServiceModuleName = "app";
-
public string ModuleName { get; }
public Assembly Assembly { get; }
diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ActionDescriptorExtensions.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ActionDescriptorExtensions.cs
new file mode 100644
index 0000000000..e12fae5152
--- /dev/null
+++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ActionDescriptorExtensions.cs
@@ -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;
+ }
+ }
+}
diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AppServiceControllerOptions.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AppServiceControllerOptions.cs
index dd1944b3f8..659b939e3c 100644
--- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AppServiceControllerOptions.cs
+++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AppServiceControllerOptions.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Reflection;
using Microsoft.AspNetCore.Http;
+using Volo.Abp.Http.Modeling;
namespace Volo.Abp.AspNetCore.Mvc
{
@@ -30,7 +31,7 @@ namespace Volo.Abp.AspNetCore.Mvc
public AbpControllerAssemblySettingBuilder CreateFor(
Assembly assembly,
- string moduleName = AbpControllerAssemblySetting.DefaultServiceModuleName,
+ string moduleName = ModuleApiDescriptionModel.DefaultServiceModuleName,
bool useConventionalHttpVerbs = true)
{
var setting = new AbpControllerAssemblySetting(moduleName, assembly, useConventionalHttpVerbs);
diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs
new file mode 100644
index 0000000000..799b65ac6e
--- /dev/null
+++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs
@@ -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 Logger { get; set; }
+
+ private readonly IApiDescriptionGroupCollectionProvider _descriptionProvider;
+ private readonly AbpAspNetCoreMvcOptions _options;
+
+ public AspNetCoreApiDescriptionModelProvider(
+ IApiDescriptionGroupCollectionProvider descriptionProvider,
+ IOptions options)
+ {
+ _descriptionProvider = descriptionProvider;
+ _options = options.Value;
+
+ Logger = NullLogger.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()
+ .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().FirstOrDefault();
+ if (areaAttr != null)
+ {
+ return areaAttr.RouteValue;
+ }
+
+ return ModuleApiDescriptionModel.DefaultServiceModuleName;
+ }
+ }
+}
diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ControllerAssemblySettingList.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ControllerAssemblySettingList.cs
index 36112a9ccc..c7701994bc 100644
--- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ControllerAssemblySettingList.cs
+++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ControllerAssemblySettingList.cs
@@ -10,7 +10,7 @@ namespace Volo.Abp.AspNetCore.Mvc
[CanBeNull]
public AbpControllerAssemblySetting GetSettingOrNull(Type controllerType)
{
- return this.FirstOrDefault(controllerSetting => controllerSetting.Assembly == controllerType.GetAssembly());
+ return this.FirstOrDefault(controllerSetting => controllerSetting.Assembly == controllerType.Assembly);
}
}
}
\ No newline at end of file
diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Controllers/AbpApiDefinitionController.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Controllers/AbpApiDefinitionController.cs
new file mode 100644
index 0000000000..0a8c6fb8ef
--- /dev/null
+++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Controllers/AbpApiDefinitionController.cs
@@ -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();
+ }
+ }
+}
diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Utils/ArrayMatcher.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Utils/ArrayMatcher.cs
new file mode 100644
index 0000000000..f1b5b2b8e6
--- /dev/null
+++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Utils/ArrayMatcher.cs
@@ -0,0 +1,42 @@
+using System.Collections.Generic;
+
+namespace Volo.Abp.AspNetCore.Mvc.Utils
+{
+ internal static class ArrayMatcher
+ {
+ public static T[] Match(T[] sourceArray, T[] destinationArray)
+ {
+ var result = new List();
+
+ var currentMethodParamIndex = 0;
+ var parentItem = default(T);
+
+ foreach (var sourceItem in sourceArray)
+ {
+ if (currentMethodParamIndex < destinationArray.Length)
+ {
+ var destinationItem = destinationArray[currentMethodParamIndex];
+
+ if (EqualityComparer.Default.Equals(sourceItem, destinationItem))
+ {
+ parentItem = default(T);
+ currentMethodParamIndex++;
+ }
+ else
+ {
+ if (parentItem == null)
+ {
+ parentItem = destinationItem;
+ currentMethodParamIndex++;
+ }
+ }
+ }
+
+ var resultItem = EqualityComparer.Default.Equals(parentItem, default(T)) ? sourceItem : parentItem;
+ result.Add(resultItem);
+ }
+
+ return result.ToArray();
+ }
+ }
+}
diff --git a/src/Volo.Abp.AspNetCore.TestBase/Volo.Abp.AspNetCore.TestBase.csproj b/src/Volo.Abp.AspNetCore.TestBase/Volo.Abp.AspNetCore.TestBase.csproj
index c6ff8966ba..7090e4bf39 100644
--- a/src/Volo.Abp.AspNetCore.TestBase/Volo.Abp.AspNetCore.TestBase.csproj
+++ b/src/Volo.Abp.AspNetCore.TestBase/Volo.Abp.AspNetCore.TestBase.csproj
@@ -13,6 +13,7 @@
+
diff --git a/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpAspNetCoreIntegratedTestBase.cs b/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpAspNetCoreIntegratedTestBase.cs
index 4f9da35c91..81c85197b1 100644
--- a/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpAspNetCoreIntegratedTestBase.cs
+++ b/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpAspNetCoreIntegratedTestBase.cs
@@ -5,6 +5,7 @@ using System.Net.Http;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.TestHost;
+using Microsoft.Extensions.DependencyInjection;
namespace Volo.Abp.AspNetCore.TestBase
{
@@ -22,8 +23,9 @@ namespace Volo.Abp.AspNetCore.TestBase
var builder = CreateWebHostBuilder();
Server = CreateTestServer(builder);
Client = Server.CreateClient();
-
ServiceProvider = Server.Host.Services;
+
+ ServiceProvider.GetRequiredService().Server = Server;
}
protected virtual IWebHostBuilder CreateWebHostBuilder()
diff --git a/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpAspNetCoreTestBaseModule.cs b/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpAspNetCoreTestBaseModule.cs
index 6f98ab1614..c6828feea5 100644
--- a/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpAspNetCoreTestBaseModule.cs
+++ b/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/AbpAspNetCoreTestBaseModule.cs
@@ -1,8 +1,10 @@
using Microsoft.Extensions.DependencyInjection;
+using Volo.Abp.Http.Client;
using Volo.Abp.Modularity;
namespace Volo.Abp.AspNetCore.TestBase
{
+ [DependsOn(typeof(AbpHttpClientModule))]
[DependsOn(typeof(AbpAspNetCoreModule))]
public class AbpAspNetCoreTestBaseModule : AbpModule
{
diff --git a/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/DynamicProxying/AspNetCoreTestDynamicProxyHttpClientFactory.cs b/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/DynamicProxying/AspNetCoreTestDynamicProxyHttpClientFactory.cs
new file mode 100644
index 0000000000..e0253f8f8b
--- /dev/null
+++ b/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/DynamicProxying/AspNetCoreTestDynamicProxyHttpClientFactory.cs
@@ -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();
+ }
+ }
+}
diff --git a/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/ITestServerAccessor.cs b/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/ITestServerAccessor.cs
new file mode 100644
index 0000000000..86247877cb
--- /dev/null
+++ b/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/ITestServerAccessor.cs
@@ -0,0 +1,9 @@
+using Microsoft.AspNetCore.TestHost;
+
+namespace Volo.Abp.AspNetCore.TestBase
+{
+ public interface ITestServerAccessor
+ {
+ TestServer Server { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/TestServerAccessor.cs b/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/TestServerAccessor.cs
new file mode 100644
index 0000000000..f848529536
--- /dev/null
+++ b/src/Volo.Abp.AspNetCore.TestBase/Volo/Abp/AspNetCore/TestBase/TestServerAccessor.cs
@@ -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; }
+ }
+}
\ No newline at end of file
diff --git a/src/Volo.Abp.Castle.Core/Volo/Abp/Castle/DynamicProxy/CastleAbpInterceptorAdapter.cs b/src/Volo.Abp.Castle.Core/Volo/Abp/Castle/DynamicProxy/CastleAbpInterceptorAdapter.cs
index 56675ec73f..1b1b958ff1 100644
--- a/src/Volo.Abp.Castle.Core/Volo/Abp/Castle/DynamicProxy/CastleAbpInterceptorAdapter.cs
+++ b/src/Volo.Abp.Castle.Core/Volo/Abp/Castle/DynamicProxy/CastleAbpInterceptorAdapter.cs
@@ -17,7 +17,9 @@ namespace Volo.Abp.Castle.DynamicProxy
public void Intercept(IInvocation invocation)
{
- if (invocation.MethodInvocationTarget.IsAsync())
+ var method = invocation.MethodInvocationTarget ?? invocation.Method;
+
+ if (method.IsAsync())
{
InterceptAsyncMethod(invocation);
}
diff --git a/src/Volo.Abp.Castle.Core/Volo/Abp/Castle/DynamicProxy/CastleAbpMethodInvocationAdapter.cs b/src/Volo.Abp.Castle.Core/Volo/Abp/Castle/DynamicProxy/CastleAbpMethodInvocationAdapter.cs
index 2ce330ec66..6758f138b0 100644
--- a/src/Volo.Abp.Castle.Core/Volo/Abp/Castle/DynamicProxy/CastleAbpMethodInvocationAdapter.cs
+++ b/src/Volo.Abp.Castle.Core/Volo/Abp/Castle/DynamicProxy/CastleAbpMethodInvocationAdapter.cs
@@ -15,7 +15,7 @@ namespace Volo.Abp.Castle.DynamicProxy
public object TargetObject => Invocation.InvocationTarget;
- public MethodInfo Method => Invocation.MethodInvocationTarget;
+ public MethodInfo Method => Invocation.MethodInvocationTarget ?? Invocation.Method;
public object ReturnValue
{
diff --git a/src/Volo.Abp.Http.Client/Microsoft/Extensions/DependencyInjection/ServiceCollectionDynamicHttpClientProxyExtensions.cs b/src/Volo.Abp.Http.Client/Microsoft/Extensions/DependencyInjection/ServiceCollectionDynamicHttpClientProxyExtensions.cs
new file mode 100644
index 0000000000..17f5d51f7a
--- /dev/null
+++ b/src/Volo.Abp.Http.Client/Microsoft/Extensions/DependencyInjection/ServiceCollectionDynamicHttpClientProxyExtensions.cs
@@ -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(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(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)
+ )
+ );
+ }
+ }
+}
diff --git a/src/Volo.Abp.Http.Client/Volo.Abp.Http.Client.csproj b/src/Volo.Abp.Http.Client/Volo.Abp.Http.Client.csproj
new file mode 100644
index 0000000000..ff4dd91623
--- /dev/null
+++ b/src/Volo.Abp.Http.Client/Volo.Abp.Http.Client.csproj
@@ -0,0 +1,19 @@
+
+
+
+ netstandard2.0
+ Volo.Abp.Http.Client
+ Volo.Abp.Http.Client
+ $(AssetTargetFallback);portable-net45+win8+wp8+wpa81;
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpClientOptions.cs b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpClientOptions.cs
new file mode 100644
index 0000000000..5adcd7525f
--- /dev/null
+++ b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpClientOptions.cs
@@ -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 HttpClientProxies { get; set; }
+
+ public AbpHttpClientOptions()
+ {
+ HttpClientProxies = new Dictionary();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpModule.cs b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpModule.cs
new file mode 100644
index 0000000000..37ddfe2755
--- /dev/null
+++ b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpModule.cs
@@ -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();
+ }
+ }
+}
diff --git a/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/ApplicationApiDescriptionModelManager.cs b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/ApplicationApiDescriptionModelManager.cs
new file mode 100644
index 0000000000..79cac746e5
--- /dev/null
+++ b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/ApplicationApiDescriptionModelManager.cs
@@ -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 _cache;
+ private readonly AsyncLock _asyncLock;
+
+ public ApplicationApiDescriptionModelManager(IDynamicProxyHttpClientFactory httpClientFactory)
+ {
+ _httpClientFactory = httpClientFactory;
+
+ _cache = new Dictionary();
+ _asyncLock = new AsyncLock();
+ }
+
+ public async Task 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 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;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DefaultDynamicProxyHttpClientFactory.cs b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DefaultDynamicProxyHttpClientFactory.cs
new file mode 100644
index 0000000000..6c827f5237
--- /dev/null
+++ b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DefaultDynamicProxyHttpClientFactory.cs
@@ -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();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpClientProxyConfig.cs b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpClientProxyConfig.cs
new file mode 100644
index 0000000000..20df2dfd7e
--- /dev/null
+++ b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpClientProxyConfig.cs
@@ -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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs
new file mode 100644
index 0000000000..b19d901bda
--- /dev/null
+++ b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs
@@ -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 : AbpInterceptor, ITransientDependency
+ {
+ private static readonly MethodInfo GenericInterceptAsyncMethod;
+
+ private readonly IDynamicProxyHttpClientFactory _httpClientFactory;
+ private readonly IApplicationApiDescriptionModelManager _discoverManager;
+ private readonly AbpHttpClientOptions _options;
+
+ static DynamicHttpProxyInterceptor()
+ {
+ GenericInterceptAsyncMethod = typeof(DynamicHttpProxyInterceptor)
+ .GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)
+ .First(m => m.Name == nameof(InterceptAsync) && m.IsGenericMethodDefinition);
+ }
+
+ public DynamicHttpProxyInterceptor(
+ IDynamicProxyHttpClientFactory httpClientFactory,
+ IOptions 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 InterceptAsync(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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/IApplicationApiDescriptionModelManager.cs b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/IApplicationApiDescriptionModelManager.cs
new file mode 100644
index 0000000000..bdc7f3d6af
--- /dev/null
+++ b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/IApplicationApiDescriptionModelManager.cs
@@ -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 GetAsync(string baseUrl, CancellationToken cancellationToken = default(CancellationToken));
+ }
+}
\ No newline at end of file
diff --git a/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/IDynamicProxyHttpClientFactory.cs b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/IDynamicProxyHttpClientFactory.cs
new file mode 100644
index 0000000000..aaf821d0f9
--- /dev/null
+++ b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/IDynamicProxyHttpClientFactory.cs
@@ -0,0 +1,9 @@
+using System.Net.Http;
+
+namespace Volo.Abp.Http.Client.DynamicProxying
+{
+ public interface IDynamicProxyHttpClientFactory
+ {
+ HttpClient Create();
+ }
+}
\ No newline at end of file
diff --git a/src/Volo.Abp.Http/Volo.Abp.Http.csproj b/src/Volo.Abp.Http/Volo.Abp.Http.csproj
index 475e1a8af2..d4031e3399 100644
--- a/src/Volo.Abp.Http/Volo.Abp.Http.csproj
+++ b/src/Volo.Abp.Http/Volo.Abp.Http.csproj
@@ -12,11 +12,8 @@
+
-
-
-
-
\ No newline at end of file
diff --git a/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ActionApiDescriptionModel.cs b/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ActionApiDescriptionModel.cs
new file mode 100644
index 0000000000..aaac0bfba9
--- /dev/null
+++ b/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ActionApiDescriptionModel.cs
@@ -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 ParametersOnMethod { get; set; }
+
+ public IList 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(),
+ ParametersOnMethod = method
+ .GetParameters()
+ .Select(MethodParameterApiDescriptionModel.Create)
+ .ToList()
+ };
+ }
+
+ public ParameterApiDescriptionModel AddParameter(ParameterApiDescriptionModel parameter)
+ {
+ Parameters.Add(parameter);
+ return parameter;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ApplicationApiDescriptionModel.cs b/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ApplicationApiDescriptionModel.cs
new file mode 100644
index 0000000000..63f9aef716
--- /dev/null
+++ b/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ApplicationApiDescriptionModel.cs
@@ -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 Modules { get; set; }
+
+ private ApplicationApiDescriptionModel()
+ {
+
+ }
+
+ public static ApplicationApiDescriptionModel Create()
+ {
+ return new ApplicationApiDescriptionModel
+ {
+ //TODO: Why ConcurrentDictionary?
+ Modules = new ConcurrentDictionary()
+ };
+ }
+
+ 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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerApiDescriptionModel.cs b/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerApiDescriptionModel.cs
new file mode 100644
index 0000000000..b4bd4df80e
--- /dev/null
+++ b/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerApiDescriptionModel.cs
@@ -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 Interfaces { get; set; }
+
+ public Dictionary Actions { get; set; }
+
+ private ControllerApiDescriptionModel()
+ {
+
+ }
+
+ public static ControllerApiDescriptionModel Create(string controllerName, Type type)
+ {
+ return new ControllerApiDescriptionModel
+ {
+ ControllerName = controllerName,
+ TypeAsString = type.FullName,
+ Actions = new Dictionary(),
+ 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()
+ };
+
+ foreach (var action in Actions.Values)
+ {
+ if (actions == null || actions.Contains(action.UniqueName))
+ {
+ subModel.AddAction(action);
+ }
+ }
+
+ return subModel;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerInterfaceApiDescriptionModel.cs b/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerInterfaceApiDescriptionModel.cs
new file mode 100644
index 0000000000..3868ec94f9
--- /dev/null
+++ b/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerInterfaceApiDescriptionModel.cs
@@ -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
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/IApiDescriptionModelProvider.cs b/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/IApiDescriptionModelProvider.cs
new file mode 100644
index 0000000000..35334fae33
--- /dev/null
+++ b/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/IApiDescriptionModelProvider.cs
@@ -0,0 +1,7 @@
+namespace Volo.Abp.Http.Modeling
+{
+ public interface IApiDescriptionModelProvider
+ {
+ ApplicationApiDescriptionModel CreateModel();
+ }
+}
\ No newline at end of file
diff --git a/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/MethodParameterApiDescriptionModel.cs b/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/MethodParameterApiDescriptionModel.cs
new file mode 100644
index 0000000000..fdc50090d2
--- /dev/null
+++ b/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/MethodParameterApiDescriptionModel.cs
@@ -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
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ModuleApiDescriptionModel.cs b/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ModuleApiDescriptionModel.cs
new file mode 100644
index 0000000000..20ce707dff
--- /dev/null
+++ b/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ModuleApiDescriptionModel.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Volo.Abp.Http.Modeling
+{
+ [Serializable]
+ public class ModuleApiDescriptionModel
+ {
+ ///
+ /// "app".
+ ///
+ public const string DefaultServiceModuleName = "app";
+
+ public string Name { get; set; }
+
+ public IDictionary Controllers { get; set; }
+
+ private ModuleApiDescriptionModel()
+ {
+
+ }
+
+ public static ModuleApiDescriptionModel Create(string name)
+ {
+ return new ModuleApiDescriptionModel
+ {
+ Name = name,
+ Controllers = new Dictionary()
+ };
+ }
+
+ 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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ParameterApiDescriptionModel.cs b/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ParameterApiDescriptionModel.cs
new file mode 100644
index 0000000000..d3e9a5fbab
--- /dev/null
+++ b/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ParameterApiDescriptionModel.cs
@@ -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
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ReturnValueApiDescriptionModel.cs b/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ReturnValueApiDescriptionModel.cs
new file mode 100644
index 0000000000..83b4cba346
--- /dev/null
+++ b/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ReturnValueApiDescriptionModel.cs
@@ -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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Volo.Abp/System/AbpTypeExtensions.cs b/src/Volo.Abp/System/AbpTypeExtensions.cs
deleted file mode 100644
index 2cbd8dea77..0000000000
--- a/src/Volo.Abp/System/AbpTypeExtensions.cs
+++ /dev/null
@@ -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;
- }
- }
-}
diff --git a/src/Volo.Abp/Volo.Abp.csproj b/src/Volo.Abp/Volo.Abp.csproj
index d02cb6b1b3..be558fc887 100644
--- a/src/Volo.Abp/Volo.Abp.csproj
+++ b/src/Volo.Abp/Volo.Abp.csproj
@@ -16,6 +16,7 @@
+
diff --git a/src/Volo.Abp/Volo/Abp/Modularity/AbpModuleDescriptor.cs b/src/Volo.Abp/Volo/Abp/Modularity/AbpModuleDescriptor.cs
index 69fada953c..7413e1154f 100644
--- a/src/Volo.Abp/Volo/Abp/Modularity/AbpModuleDescriptor.cs
+++ b/src/Volo.Abp/Volo/Abp/Modularity/AbpModuleDescriptor.cs
@@ -33,7 +33,7 @@ namespace Volo.Abp.Modularity
}
Type = type;
- Assembly = type.GetAssembly();
+ Assembly = type.Assembly;
Instance = instance;
IsLoadedAsPlugIn = isLoadedAsPlugIn;
diff --git a/src/Volo.Abp/Volo/Abp/Reflection/AssemblyFinder.cs b/src/Volo.Abp/Volo/Abp/Reflection/AssemblyFinder.cs
index 6d90a8fb5c..8178f1046b 100644
--- a/src/Volo.Abp/Volo/Abp/Reflection/AssemblyFinder.cs
+++ b/src/Volo.Abp/Volo/Abp/Reflection/AssemblyFinder.cs
@@ -30,7 +30,7 @@ namespace Volo.Abp.Reflection
foreach (var module in _moduleContainer.Modules)
{
- assemblies.Add(module.Type.GetAssembly());
+ assemblies.Add(module.Type.Assembly);
}
return assemblies.Distinct().ToImmutableList();
diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkInterceptor.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkInterceptor.cs
index 53b6efb2fa..1bf813905a 100644
--- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkInterceptor.cs
+++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkInterceptor.cs
@@ -32,7 +32,7 @@ namespace Volo.Abp.Uow
{
if (!UnitOfWorkHelper.IsUnitOfWorkMethod(invocation.Method))
{
- invocation.Proceed();
+ await invocation.ProceedAsync();
return;
}
diff --git a/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Utils/ArrayMacther_Tests.cs b/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Utils/ArrayMacther_Tests.cs
new file mode 100644
index 0000000000..a066622748
--- /dev/null
+++ b/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Utils/ArrayMacther_Tests.cs
@@ -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);
+ }
+ }
+}
diff --git a/test/Volo.Abp.AspNetCore.Tests/Volo/Abp/AspNetCore/AppTestBase.cs b/test/Volo.Abp.AspNetCore.Tests/Volo/Abp/AspNetCore/AppTestBase.cs
index d13cb810bf..6bb82c3656 100644
--- a/test/Volo.Abp.AspNetCore.Tests/Volo/Abp/AspNetCore/AppTestBase.cs
+++ b/test/Volo.Abp.AspNetCore.Tests/Volo/Abp/AspNetCore/AppTestBase.cs
@@ -6,7 +6,7 @@ using Newtonsoft.Json.Serialization;
using Shouldly;
using Volo.Abp.AspNetCore.TestBase;
-namespace Volo.Abp.AspNetCore.App
+namespace Volo.Abp.AspNetCore
{
public abstract class AbpAspNetCoreTestBase : AbpAspNetCoreIntegratedTestBase
where TStartup : class
diff --git a/test/Volo.Abp.Http.Tests/Volo.Abp.Http.Tests.csproj b/test/Volo.Abp.Http.Client.Tests/Volo.Abp.Http.Client.Tests.csproj
similarity index 84%
rename from test/Volo.Abp.Http.Tests/Volo.Abp.Http.Tests.csproj
rename to test/Volo.Abp.Http.Client.Tests/Volo.Abp.Http.Client.Tests.csproj
index fd4ae111e2..6593fa5d39 100644
--- a/test/Volo.Abp.Http.Tests/Volo.Abp.Http.Tests.csproj
+++ b/test/Volo.Abp.Http.Client.Tests/Volo.Abp.Http.Client.Tests.csproj
@@ -3,7 +3,7 @@
netcoreapp2.0
Volo.Abp.Http.Tests
- Volo.Abp.Http.Tests
+ Volo.Abp.Http.Client.Tests
true
false
false
@@ -12,7 +12,7 @@
-
+
diff --git a/test/Volo.Abp.Http.Tests/Volo/Abp/Http/AbpHttpTestBase.cs b/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/AbpHttpTestBase.cs
similarity index 83%
rename from test/Volo.Abp.Http.Tests/Volo/Abp/Http/AbpHttpTestBase.cs
rename to test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/AbpHttpTestBase.cs
index 35db320555..f950193109 100644
--- a/test/Volo.Abp.Http.Tests/Volo/Abp/Http/AbpHttpTestBase.cs
+++ b/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/AbpHttpTestBase.cs
@@ -1,3 +1,4 @@
+using Volo.Abp.AspNetCore;
using Volo.Abp.AspNetCore.App;
namespace Volo.Abp.Http
diff --git a/test/Volo.Abp.Http.Tests/Volo/Abp/Http/AbpHttpTestModule.cs b/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/AbpHttpTestModule.cs
similarity index 63%
rename from test/Volo.Abp.Http.Tests/Volo/Abp/Http/AbpHttpTestModule.cs
rename to test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/AbpHttpTestModule.cs
index ae2cb91658..4a150a2c19 100644
--- a/test/Volo.Abp.Http.Tests/Volo/Abp/Http/AbpHttpTestModule.cs
+++ b/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/AbpHttpTestModule.cs
@@ -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();
+ services.AddAssemblyOf();
+ services.AddHttpClientProxy("/");
}
}
}
diff --git a/test/Volo.Abp.Http.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs b/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs
similarity index 94%
rename from test/Volo.Abp.Http.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs
rename to test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs
index abd73531c2..4d6c18ceca 100644
--- a/test/Volo.Abp.Http.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs
+++ b/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/DynamicProxying/PersonAppServiceClientProxy_Tests.cs
@@ -13,7 +13,6 @@ namespace Volo.Abp.Http.DynamicProxying
public PersonAppServiceClientProxy_Tests()
{
- //TODO: Should actually test the proxy!
_peopleAppService = ServiceProvider.GetRequiredService();
}
diff --git a/test/Volo.Abp.Http.Tests/Volo/Abp/Http/Startup.cs b/test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/Startup.cs
similarity index 100%
rename from test/Volo.Abp.Http.Tests/Volo/Abp/Http/Startup.cs
rename to test/Volo.Abp.Http.Client.Tests/Volo/Abp/Http/Startup.cs
diff --git a/test/Volo.Abp.Tests/Volo/Abp/Reflection/AssemblyFinder_Tests.cs b/test/Volo.Abp.Tests/Volo/Abp/Reflection/AssemblyFinder_Tests.cs
index 92cd5aaf64..37c5b01518 100644
--- a/test/Volo.Abp.Tests/Volo/Abp/Reflection/AssemblyFinder_Tests.cs
+++ b/test/Volo.Abp.Tests/Volo/Abp/Reflection/AssemblyFinder_Tests.cs
@@ -30,7 +30,7 @@ namespace Volo.Abp.Reflection
foreach (var moduleType in moduleTypes)
{
- assemblyFinder.Assemblies.ShouldContain(moduleType.GetAssembly());
+ assemblyFinder.Assemblies.ShouldContain(moduleType.Assembly);
}
}
diff --git a/test/Volo.Abp.Tests/Volo/Abp/Reflection/TypeFinder_Tests.cs b/test/Volo.Abp.Tests/Volo/Abp/Reflection/TypeFinder_Tests.cs
index 4bbaf34400..c9876875ed 100644
--- a/test/Volo.Abp.Tests/Volo/Abp/Reflection/TypeFinder_Tests.cs
+++ b/test/Volo.Abp.Tests/Volo/Abp/Reflection/TypeFinder_Tests.cs
@@ -18,8 +18,8 @@ namespace Volo.Abp.Reflection
var fakeAssemblyFinder = Substitute.For();
fakeAssemblyFinder.Assemblies.Returns(new List
{
- typeof(AbpKernelModule).GetAssembly(),
- typeof(TypeFinder_Tests).GetAssembly()
+ typeof(AbpKernelModule).Assembly,
+ typeof(TypeFinder_Tests).Assembly
});
//Act