From f77a9d06c9b9f67a160d2853793af542a3c3ed15 Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 16 Sep 2021 10:25:39 +0800 Subject: [PATCH 01/13] Enhance ClientProxyBase. --- .../Abp/Http/Client/AbpHttpClientModule.cs | 3 + .../ApiVersionInfo.cs | 2 +- .../Client/ClientProxying/ClientProxyBase.cs | 262 ++++++++++++++++- .../ClientProxyRequestContext.cs} | 6 +- .../ClientProxyRequestPayloadBuilder.cs} | 13 +- .../ClientProxyUrlBuilder.cs} | 25 +- .../DynamicHttpProxyInterceptor.cs | 45 ++- .../DynamicHttpProxyInterceptorClientProxy.cs | 9 + .../Http/Client/Proxying/HttpProxyExecuter.cs | 267 ------------------ .../Client/Proxying/IHttpProxyExecuter.cs | 12 - 10 files changed, 311 insertions(+), 333 deletions(-) rename framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/{Proxying => ClientProxying}/ApiVersionInfo.cs (92%) rename framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/{Proxying/HttpProxyExecuterContext.cs => ClientProxying/ClientProxyRequestContext.cs} (85%) rename framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/{Proxying/RequestPayloadBuilder.cs => ClientProxying/ClientProxyRequestPayloadBuilder.cs} (88%) rename framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/{Proxying/UrlBuilder.cs => ClientProxying/ClientProxyUrlBuilder.cs} (80%) create mode 100644 framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptorClientProxy.cs delete mode 100644 framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/Proxying/HttpProxyExecuter.cs delete mode 100644 framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/Proxying/IHttpProxyExecuter.cs diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpClientModule.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpClientModule.cs index c4d0d0d181..c72b99c819 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpClientModule.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/AbpHttpClientModule.cs @@ -5,6 +5,7 @@ using Volo.Abp.MultiTenancy; using Volo.Abp.Threading; using Volo.Abp.Validation; using Volo.Abp.ExceptionHandling; +using Volo.Abp.Http.Client.DynamicProxying; namespace Volo.Abp.Http.Client { @@ -22,6 +23,8 @@ namespace Volo.Abp.Http.Client { var configuration = context.Services.GetConfiguration(); Configure(configuration); + + context.Services.AddTransient(typeof(DynamicHttpProxyInterceptorClientProxy<>)); } } } diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/Proxying/ApiVersionInfo.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ApiVersionInfo.cs similarity index 92% rename from framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/Proxying/ApiVersionInfo.cs rename to framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ApiVersionInfo.cs index d37de5d452..ecf2db7b9f 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/Proxying/ApiVersionInfo.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ApiVersionInfo.cs @@ -1,6 +1,6 @@ using System; -namespace Volo.Abp.Http.Client.Proxying +namespace Volo.Abp.Http.Client.ClientProxying { public class ApiVersionInfo //TODO: Rename to not conflict with api versioning apis { diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyBase.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyBase.cs index 33a1906114..703a1bd46e 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyBase.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyBase.cs @@ -1,9 +1,23 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Globalization; using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using Microsoft.Extensions.Primitives; +using Volo.Abp.Content; using Volo.Abp.DependencyInjection; +using Volo.Abp.Http.Client.Authentication; using Volo.Abp.Http.Client.Proxying; using Volo.Abp.Http.Modeling; +using Volo.Abp.Http.ProxyScripting.Generators; +using Volo.Abp.Json; +using Volo.Abp.MultiTenancy; +using Volo.Abp.Threading; +using Volo.Abp.Tracing; namespace Volo.Abp.Http.Client.ClientProxying { @@ -11,27 +25,37 @@ namespace Volo.Abp.Http.Client.ClientProxying { public IAbpLazyServiceProvider LazyServiceProvider { get; set; } - protected IHttpProxyExecuter HttpProxyExecuter => LazyServiceProvider.LazyGetRequiredService(); protected IClientProxyApiDescriptionFinder ClientProxyApiDescriptionFinder => LazyServiceProvider.LazyGetRequiredService(); + protected ICancellationTokenProvider CancellationTokenProvider => LazyServiceProvider.LazyGetRequiredService(); + protected ICorrelationIdProvider CorrelationIdProvider => LazyServiceProvider.LazyGetRequiredService(); + protected ICurrentTenant CurrentTenant => LazyServiceProvider.LazyGetRequiredService(); + protected IOptions AbpCorrelationIdOptions => LazyServiceProvider.LazyGetRequiredService>(); + protected IProxyHttpClientFactory HttpClientFactory => LazyServiceProvider.LazyGetRequiredService(); + protected IRemoteServiceConfigurationProvider RemoteServiceConfigurationProvider => LazyServiceProvider.LazyGetRequiredService(); + protected IOptions ClientOptions => LazyServiceProvider.LazyGetRequiredService>(); + protected IJsonSerializer JsonSerializer => LazyServiceProvider.LazyGetRequiredService(); + protected IRemoteServiceHttpClientAuthenticator ClientAuthenticator => LazyServiceProvider.LazyGetRequiredService(); + protected ClientProxyRequestPayloadBuilder ClientProxyRequestPayloadBuilder => LazyServiceProvider.LazyGetRequiredService(); + protected ClientProxyUrlBuilder ClientProxyUrlBuilder => LazyServiceProvider.LazyGetRequiredService(); protected virtual async Task RequestAsync(string methodName, params object[] arguments) { - await HttpProxyExecuter.MakeRequestAsync(BuildHttpProxyExecuterContext(methodName, arguments)); + await RequestAsync(BuildHttpProxyClientProxyContext(methodName, arguments)); } protected virtual async Task RequestAsync(string methodName, params object[] arguments) { - return await HttpProxyExecuter.MakeRequestAndGetResultAsync(BuildHttpProxyExecuterContext(methodName, arguments)); + return await RequestAsync(BuildHttpProxyClientProxyContext(methodName, arguments)); } - protected virtual HttpProxyExecuterContext BuildHttpProxyExecuterContext(string methodName, params object[] arguments) + protected virtual ClientProxyRequestContext BuildHttpProxyClientProxyContext(string methodName, params object[] arguments) { - var actionKey = GetActionKey(methodName, arguments); - var action = ClientProxyApiDescriptionFinder.FindAction(actionKey); - return new HttpProxyExecuterContext(action, BuildArguments(action, arguments), typeof(TService)); + var methodUniqueName = $"{typeof(TService).FullName}.{methodName}.{string.Join("-", arguments.Select(x => x.GetType().FullName))}"; + var action = ClientProxyApiDescriptionFinder.FindAction(methodUniqueName); + return new ClientProxyRequestContext(action, BuildActionArguments(action, arguments), typeof(TService)); } - protected virtual Dictionary BuildArguments(ActionApiDescriptionModel action, object[] arguments) + protected virtual Dictionary BuildActionArguments(ActionApiDescriptionModel action, object[] arguments) { var parameters = action.Parameters.GroupBy(x => x.NameOnMethod).Select(x => x.Key).ToList(); var dict = new Dictionary(); @@ -44,9 +68,225 @@ namespace Volo.Abp.Http.Client.ClientProxying return dict; } - private static string GetActionKey(string methodName, params object[] arguments) + public virtual async Task RequestAsync(ClientProxyRequestContext requestContext) { - return $"{typeof(TService).FullName}.{methodName}.{string.Join("-", arguments.Select(x => x.GetType().FullName))}"; + var responseContent = await RequestAsync(requestContext); + + if (typeof(T) == typeof(IRemoteStreamContent) || + typeof(T) == typeof(RemoteStreamContent)) + { + /* returning a class that holds a reference to response + * content just to be sure that GC does not dispose of + * it before we finish doing our work with the stream */ + return (T)(object)new RemoteStreamContent( + await responseContent.ReadAsStreamAsync(), + responseContent.Headers?.ContentDisposition?.FileNameStar ?? + RemoveQuotes(responseContent.Headers?.ContentDisposition?.FileName).ToString(), + responseContent.Headers?.ContentType?.ToString(), + responseContent.Headers?.ContentLength); + } + + var stringContent = await responseContent.ReadAsStringAsync(); + if (typeof(T) == typeof(string)) + { + return (T)(object)stringContent; + } + + if (stringContent.IsNullOrWhiteSpace()) + { + return default; + } + + return JsonSerializer.Deserialize(stringContent); + } + + public virtual async Task RequestAsync(ClientProxyRequestContext requestContext) + { + var clientConfig = ClientOptions.Value.HttpClientProxies.GetOrDefault(requestContext.ServiceType) ?? throw new AbpException($"Could not get HttpClientProxyConfig for {requestContext.ServiceType.FullName}."); + var remoteServiceConfig = await RemoteServiceConfigurationProvider.GetConfigurationOrDefaultAsync(clientConfig.RemoteServiceName); + + var client = HttpClientFactory.Create(clientConfig.RemoteServiceName); + + var apiVersion = await GetApiVersionInfoAsync(requestContext); + var url = remoteServiceConfig.BaseUrl.EnsureEndsWith('/') + await GetUrlWithParametersAsync(requestContext, apiVersion); + + var requestMessage = new HttpRequestMessage(requestContext.Action.GetHttpMethod(), url) + { + Content = ClientProxyRequestPayloadBuilder.BuildContent(requestContext.Action, requestContext.Arguments, JsonSerializer, apiVersion) + }; + + AddHeaders(requestContext.Arguments, requestContext.Action, requestMessage, apiVersion); + + if (requestContext.Action.AllowAnonymous != true) + { + await ClientAuthenticator.Authenticate( + new RemoteServiceHttpClientAuthenticateContext( + client, + requestMessage, + remoteServiceConfig, + clientConfig.RemoteServiceName + ) + ); + } + + var response = await client.SendAsync( + requestMessage, + HttpCompletionOption.ResponseHeadersRead /*this will buffer only the headers, the content will be used as a stream*/, + GetCancellationToken(requestContext.Arguments) + ); + + if (!response.IsSuccessStatusCode) + { + await ThrowExceptionForResponseAsync(response); + } + + return response.Content; + } + + protected virtual async Task GetApiVersionInfoAsync(ClientProxyRequestContext requestContext) + { + var apiVersion = await FindBestApiVersionAsync(requestContext); + + //TODO: Make names configurable? + var versionParam = requestContext.Action.Parameters.FirstOrDefault(p => p.Name == "apiVersion" && p.BindingSourceId == ParameterBindingSources.Path) ?? + requestContext.Action.Parameters.FirstOrDefault(p => p.Name == "api-version" && p.BindingSourceId == ParameterBindingSources.Query); + + return new ApiVersionInfo(versionParam?.BindingSourceId, apiVersion); + } + + protected virtual Task GetUrlWithParametersAsync(ClientProxyRequestContext requestContext, ApiVersionInfo apiVersion) + { + return Task.FromResult(ClientProxyUrlBuilder.GenerateUrlWithParameters(requestContext.Action, requestContext.Arguments, apiVersion)); + } + + protected virtual Task GetHttpContentAsync(ClientProxyRequestContext requestContext, ApiVersionInfo apiVersion) + { + return Task.FromResult(ClientProxyRequestPayloadBuilder.BuildContent(requestContext.Action, requestContext.Arguments, JsonSerializer, apiVersion)); + } + + protected virtual async Task FindBestApiVersionAsync(ClientProxyRequestContext requestContext) + { + var configuredVersion = await GetConfiguredApiVersionAsync(requestContext); + + if (requestContext.Action.SupportedVersions.IsNullOrEmpty()) + { + return configuredVersion ?? "1.0"; + } + + if (requestContext.Action.SupportedVersions.Contains(configuredVersion)) + { + return configuredVersion; + } + + return requestContext.Action.SupportedVersions.Last(); //TODO: Ensure to get the latest version! + } + + protected virtual async Task GetConfiguredApiVersionAsync(ClientProxyRequestContext requestContext) + { + var clientConfig = ClientOptions.Value.HttpClientProxies.GetOrDefault(requestContext.ServiceType) + ?? throw new AbpException($"Could not get DynamicHttpClientProxyConfig for {requestContext.ServiceType.FullName}."); + + return (await RemoteServiceConfigurationProvider + .GetConfigurationOrDefaultOrNullAsync(clientConfig.RemoteServiceName))?.Version; + } + + protected virtual async Task ThrowExceptionForResponseAsync(HttpResponseMessage response) + { + if (response.Headers.Contains(AbpHttpConsts.AbpErrorFormat)) + { + var errorResponse = JsonSerializer.Deserialize( + await response.Content.ReadAsStringAsync() + ); + + throw new AbpRemoteCallException(errorResponse.Error) + { + HttpStatusCode = (int) response.StatusCode + }; + } + + throw new AbpRemoteCallException( + new RemoteServiceErrorInfo + { + Message = response.ReasonPhrase, + Code = response.StatusCode.ToString() + } + ) + { + HttpStatusCode = (int) response.StatusCode + }; + } + + protected virtual void AddHeaders( + IReadOnlyDictionary argumentsDictionary, + ActionApiDescriptionModel action, + HttpRequestMessage requestMessage, + ApiVersionInfo apiVersion) + { + //API Version + if (!apiVersion.Version.IsNullOrEmpty()) + { + //TODO: What about other media types? + requestMessage.Headers.Add("accept", $"{MimeTypes.Text.Plain}; v={apiVersion.Version}"); + requestMessage.Headers.Add("accept", $"{MimeTypes.Application.Json}; v={apiVersion.Version}"); + requestMessage.Headers.Add("api-version", apiVersion.Version); + } + + //Header parameters + var headers = action.Parameters.Where(p => p.BindingSourceId == ParameterBindingSources.Header).ToArray(); + foreach (var headerParameter in headers) + { + var value = HttpActionParameterHelper.FindParameterValue(argumentsDictionary, headerParameter); + if (value != null) + { + requestMessage.Headers.Add(headerParameter.Name, value.ToString()); + } + } + + //CorrelationId + requestMessage.Headers.Add(AbpCorrelationIdOptions.Value.HttpHeaderName, CorrelationIdProvider.Get()); + + //TenantId + if (CurrentTenant.Id.HasValue) + { + //TODO: Use AbpAspNetCoreMultiTenancyOptions to get the key + requestMessage.Headers.Add(TenantResolverConsts.DefaultTenantKey, CurrentTenant.Id.Value.ToString()); + } + + //Culture + //TODO: Is that the way we want? Couldn't send the culture (not ui culture) + var currentCulture = CultureInfo.CurrentUICulture.Name ?? CultureInfo.CurrentCulture.Name; + if (!currentCulture.IsNullOrEmpty()) + { + requestMessage.Headers.AcceptLanguage.Add(new StringWithQualityHeaderValue(currentCulture)); + } + + //X-Requested-With + requestMessage.Headers.Add("X-Requested-With", "XMLHttpRequest"); + } + + protected virtual StringSegment RemoveQuotes(StringSegment input) + { + if (!StringSegment.IsNullOrEmpty(input) && input.Length >= 2 && input[0] == '"' && input[input.Length - 1] == '"') + { + input = input.Subsegment(1, input.Length - 2); + } + + return input; + } + + protected virtual CancellationToken GetCancellationToken(IReadOnlyDictionary arguments) + { + var cancellationTokenArg = arguments.LastOrDefault(); + + if (cancellationTokenArg.Value is CancellationToken cancellationToken) + { + if (cancellationToken != default) + { + return cancellationToken; + } + } + + return CancellationTokenProvider.Token; } } } diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/Proxying/HttpProxyExecuterContext.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyRequestContext.cs similarity index 85% rename from framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/Proxying/HttpProxyExecuterContext.cs rename to framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyRequestContext.cs index e61b2af93a..089a61c0d8 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/Proxying/HttpProxyExecuterContext.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyRequestContext.cs @@ -3,9 +3,9 @@ using System.Collections.Generic; using JetBrains.Annotations; using Volo.Abp.Http.Modeling; -namespace Volo.Abp.Http.Client.Proxying +namespace Volo.Abp.Http.Client.ClientProxying { - public class HttpProxyExecuterContext + public class ClientProxyRequestContext { [NotNull] public ActionApiDescriptionModel Action { get; } @@ -16,7 +16,7 @@ namespace Volo.Abp.Http.Client.Proxying [NotNull] public Type ServiceType { get; } - public HttpProxyExecuterContext( + public ClientProxyRequestContext( [NotNull] ActionApiDescriptionModel action, [NotNull] IReadOnlyDictionary arguments, [NotNull] Type serviceType) diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/Proxying/RequestPayloadBuilder.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyRequestPayloadBuilder.cs similarity index 88% rename from framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/Proxying/RequestPayloadBuilder.cs rename to framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyRequestPayloadBuilder.cs index 583798f43f..3ba7026c8e 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/Proxying/RequestPayloadBuilder.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyRequestPayloadBuilder.cs @@ -6,17 +6,18 @@ using System.Net.Http.Headers; using System.Text; using JetBrains.Annotations; using Volo.Abp.Content; -using Volo.Abp.Http.Client.DynamicProxying; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Http.Client.Proxying; using Volo.Abp.Http.Modeling; using Volo.Abp.Http.ProxyScripting.Generators; using Volo.Abp.Json; -namespace Volo.Abp.Http.Client.Proxying +namespace Volo.Abp.Http.Client.ClientProxying { - public static class RequestPayloadBuilder + public class ClientProxyRequestPayloadBuilder : ITransientDependency { [CanBeNull] - public static HttpContent BuildContent(ActionApiDescriptionModel action, IReadOnlyDictionary methodArguments, IJsonSerializer jsonSerializer, ApiVersionInfo apiVersion) + public virtual HttpContent BuildContent(ActionApiDescriptionModel action, IReadOnlyDictionary methodArguments, IJsonSerializer jsonSerializer, ApiVersionInfo apiVersion) { var body = GenerateBody(action, methodArguments, jsonSerializer); if (body != null) @@ -29,7 +30,7 @@ namespace Volo.Abp.Http.Client.Proxying return body; } - private static HttpContent GenerateBody(ActionApiDescriptionModel action, IReadOnlyDictionary methodArguments, IJsonSerializer jsonSerializer) + protected virtual HttpContent GenerateBody(ActionApiDescriptionModel action, IReadOnlyDictionary methodArguments, IJsonSerializer jsonSerializer) { var parameters = action .Parameters @@ -57,7 +58,7 @@ namespace Volo.Abp.Http.Client.Proxying return new StringContent(jsonSerializer.Serialize(value), Encoding.UTF8, MimeTypes.Application.Json); } - private static HttpContent GenerateFormPostData(ActionApiDescriptionModel action, IReadOnlyDictionary methodArguments) + protected virtual HttpContent GenerateFormPostData(ActionApiDescriptionModel action, IReadOnlyDictionary methodArguments) { var parameters = action .Parameters diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/Proxying/UrlBuilder.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyUrlBuilder.cs similarity index 80% rename from framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/Proxying/UrlBuilder.cs rename to framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyUrlBuilder.cs index 5f916fdd88..464ca3f26d 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/Proxying/UrlBuilder.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyUrlBuilder.cs @@ -5,16 +5,25 @@ using System.Globalization; using System.Linq; using System.Text; using JetBrains.Annotations; -using Volo.Abp.Http.Client.DynamicProxying; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Http.Client.Proxying; using Volo.Abp.Http.Modeling; using Volo.Abp.Http.ProxyScripting.Generators; using Volo.Abp.Localization; -namespace Volo.Abp.Http.Client.Proxying +namespace Volo.Abp.Http.Client.ClientProxying { - internal static class UrlBuilder + public class ClientProxyUrlBuilder : ITransientDependency { - public static string GenerateUrlWithParameters(ActionApiDescriptionModel action, IReadOnlyDictionary methodArguments, ApiVersionInfo apiVersion) + protected IServiceScopeFactory ServiceProviderFactory { get; } + + public ClientProxyUrlBuilder(IServiceScopeFactory serviceProviderFactory) + { + ServiceProviderFactory = serviceProviderFactory; + } + + public string GenerateUrlWithParameters(ActionApiDescriptionModel action, IReadOnlyDictionary methodArguments, ApiVersionInfo apiVersion) { // The ASP.NET Core route value provider and query string value provider: // Treat values as invariant culture. @@ -30,7 +39,7 @@ namespace Volo.Abp.Http.Client.Proxying } } - private static void ReplacePathVariables(StringBuilder urlBuilder, IList actionParameters, IReadOnlyDictionary methodArguments, ApiVersionInfo apiVersion) + protected virtual void ReplacePathVariables(StringBuilder urlBuilder, IList actionParameters, IReadOnlyDictionary methodArguments, ApiVersionInfo apiVersion) { var pathParameters = actionParameters .Where(p => p.BindingSourceId == ParameterBindingSources.Path) @@ -72,7 +81,7 @@ namespace Volo.Abp.Http.Client.Proxying } } - private static void AddQueryStringParameters(StringBuilder urlBuilder, IList actionParameters, IReadOnlyDictionary methodArguments, ApiVersionInfo apiVersion) + protected virtual void AddQueryStringParameters(StringBuilder urlBuilder, IList actionParameters, IReadOnlyDictionary methodArguments, ApiVersionInfo apiVersion) { var queryStringParameters = actionParameters .Where(p => p.BindingSourceId.IsIn(ParameterBindingSources.ModelBinding, ParameterBindingSources.Query)) @@ -100,7 +109,7 @@ namespace Volo.Abp.Http.Client.Proxying } } - private static bool AddQueryStringParameter( + protected virtual bool AddQueryStringParameter( StringBuilder urlBuilder, bool isFirstParam, string name, @@ -133,7 +142,7 @@ namespace Volo.Abp.Http.Client.Proxying return true; } - private static string ConvertValueToString([CanBeNull] object value) + protected virtual string ConvertValueToString([CanBeNull] object value) { if (value is DateTime dateTimeValue) { diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs index 68eb146201..73354773da 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; using Volo.Abp.DynamicProxy; +using Volo.Abp.Http.Client.ClientProxying; using Volo.Abp.Http.Client.Proxying; using Volo.Abp.Http.Modeling; @@ -15,32 +16,21 @@ namespace Volo.Abp.Http.Client.DynamicProxying { public class DynamicHttpProxyInterceptor : AbpInterceptor, ITransientDependency { - // ReSharper disable once StaticMemberInGenericType - protected static MethodInfo MakeRequestAndGetResultAsyncMethod { get; } - + public ILogger> Logger { get; set; } + protected DynamicHttpProxyInterceptorClientProxy InterceptorClientProxy { get; } protected AbpHttpClientOptions ClientOptions { get; } - protected IHttpProxyExecuter HttpProxyExecuter { get; } protected IProxyHttpClientFactory HttpClientFactory { get; } protected IRemoteServiceConfigurationProvider RemoteServiceConfigurationProvider { get; } protected IApiDescriptionFinder ApiDescriptionFinder { get; } - public ILogger> Logger { get; set; } - - static DynamicHttpProxyInterceptor() - { - MakeRequestAndGetResultAsyncMethod = typeof(HttpProxyExecuter) - .GetMethods(BindingFlags.Public | BindingFlags.Instance) - .First(m => m.Name == nameof(IHttpProxyExecuter.MakeRequestAndGetResultAsync) && m.IsGenericMethodDefinition); - } - public DynamicHttpProxyInterceptor( - IHttpProxyExecuter httpProxyExecuter, + DynamicHttpProxyInterceptorClientProxy interceptorClientProxy, IOptions clientOptions, IProxyHttpClientFactory httpClientFactory, IRemoteServiceConfigurationProvider remoteServiceConfigurationProvider, IApiDescriptionFinder apiDescriptionFinder) { - HttpProxyExecuter = httpProxyExecuter; + InterceptorClientProxy = interceptorClientProxy; HttpClientFactory = httpClientFactory; RemoteServiceConfigurationProvider = remoteServiceConfigurationProvider; ApiDescriptionFinder = apiDescriptionFinder; @@ -49,23 +39,27 @@ namespace Volo.Abp.Http.Client.DynamicProxying Logger = NullLogger>.Instance; } - public override async Task InterceptAsync(IAbpMethodInvocation invocation) { - var context = new HttpProxyExecuterContext( + var context = new ClientProxyRequestContext( await GetActionApiDescriptionModel(invocation), invocation.ArgumentsDictionary, typeof(TService)); if (invocation.Method.ReturnType.GenericTypeArguments.IsNullOrEmpty()) { - await HttpProxyExecuter.MakeRequestAsync(context); + await InterceptorClientProxy.RequestAsync(context); } else { - var result = (Task)MakeRequestAndGetResultAsyncMethod + var result = (Task)InterceptorClientProxy + .GetType() + .GetMethods() + .Single(m => m.Name == nameof(InterceptorClientProxy.RequestAsync) && + m.IsGenericMethod && + m.GetParameters().Any(x => x.ParameterType == typeof(ClientProxyRequestContext))) .MakeGenericMethod(invocation.Method.ReturnType.GenericTypeArguments[0]) - .Invoke(HttpProxyExecuter, new object[] { context }); + .Invoke(InterceptorClientProxy, new object[] { context }); invocation.ReturnValue = await GetResultAsync( result, @@ -74,7 +68,7 @@ namespace Volo.Abp.Http.Client.DynamicProxying } } - private async Task GetActionApiDescriptionModel(IAbpMethodInvocation invocation) + protected virtual async Task GetActionApiDescriptionModel(IAbpMethodInvocation invocation) { var clientConfig = ClientOptions.HttpClientProxies.GetOrDefault(typeof(TService)) ?? throw new AbpException($"Could not get DynamicHttpClientProxyConfig for {typeof(TService).FullName}."); var remoteServiceConfig = await RemoteServiceConfigurationProvider.GetConfigurationOrDefaultAsync(clientConfig.RemoteServiceName); @@ -88,13 +82,14 @@ namespace Volo.Abp.Http.Client.DynamicProxying ); } - private async Task GetResultAsync(Task task, Type resultType) + protected virtual async Task GetResultAsync(Task task, Type resultType) { await task; - return typeof(Task<>) + var resultProperty = typeof(Task<>) .MakeGenericType(resultType) - .GetProperty(nameof(Task.Result), BindingFlags.Instance | BindingFlags.Public) - .GetValue(task); + .GetProperty(nameof(Task.Result), BindingFlags.Instance | BindingFlags.Public); + Check.NotNull(resultProperty, nameof(resultProperty)); + return resultProperty.GetValue(task); } } } diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptorClientProxy.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptorClientProxy.cs new file mode 100644 index 0000000000..d2a9bb971e --- /dev/null +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptorClientProxy.cs @@ -0,0 +1,9 @@ +using Volo.Abp.Http.Client.ClientProxying; + +namespace Volo.Abp.Http.Client.DynamicProxying +{ + public class DynamicHttpProxyInterceptorClientProxy : ClientProxyBase + { + + } +} diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/Proxying/HttpProxyExecuter.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/Proxying/HttpProxyExecuter.cs deleted file mode 100644 index 6fe5376f2d..0000000000 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/Proxying/HttpProxyExecuter.cs +++ /dev/null @@ -1,267 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Options; -using Microsoft.Extensions.Primitives; -using Volo.Abp.Content; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Http.Client.Authentication; -using Volo.Abp.Http.Modeling; -using Volo.Abp.Http.ProxyScripting.Generators; -using Volo.Abp.Json; -using Volo.Abp.MultiTenancy; -using Volo.Abp.Threading; -using Volo.Abp.Tracing; - -namespace Volo.Abp.Http.Client.Proxying -{ - public class HttpProxyExecuter : IHttpProxyExecuter, ITransientDependency - { - protected ICancellationTokenProvider CancellationTokenProvider { get; } - protected ICorrelationIdProvider CorrelationIdProvider { get; } - protected ICurrentTenant CurrentTenant { get; } - protected AbpCorrelationIdOptions AbpCorrelationIdOptions { get; } - protected IProxyHttpClientFactory HttpClientFactory { get; } - protected IRemoteServiceConfigurationProvider RemoteServiceConfigurationProvider { get; } - protected AbpHttpClientOptions ClientOptions { get; } - protected IJsonSerializer JsonSerializer { get; } - protected IRemoteServiceHttpClientAuthenticator ClientAuthenticator { get; } - - public HttpProxyExecuter( - ICancellationTokenProvider cancellationTokenProvider, - ICorrelationIdProvider correlationIdProvider, - ICurrentTenant currentTenant, - IOptions abpCorrelationIdOptions, - IProxyHttpClientFactory httpClientFactory, - IRemoteServiceConfigurationProvider remoteServiceConfigurationProvider, - IOptions clientOptions, - IRemoteServiceHttpClientAuthenticator clientAuthenticator, - IJsonSerializer jsonSerializer) - { - CancellationTokenProvider = cancellationTokenProvider; - CorrelationIdProvider = correlationIdProvider; - CurrentTenant = currentTenant; - AbpCorrelationIdOptions = abpCorrelationIdOptions.Value; - HttpClientFactory = httpClientFactory; - RemoteServiceConfigurationProvider = remoteServiceConfigurationProvider; - ClientOptions = clientOptions.Value; - ClientAuthenticator = clientAuthenticator; - JsonSerializer = jsonSerializer; - } - - public virtual async Task MakeRequestAndGetResultAsync(HttpProxyExecuterContext context) - { - var responseContent = await MakeRequestAsync(context); - - if (typeof(T) == typeof(IRemoteStreamContent) || - typeof(T) == typeof(RemoteStreamContent)) - { - /* returning a class that holds a reference to response - * content just to be sure that GC does not dispose of - * it before we finish doing our work with the stream */ - return (T) (object) new RemoteStreamContent( - await responseContent.ReadAsStreamAsync(), - responseContent.Headers?.ContentDisposition?.FileNameStar ?? RemoveQuotes(responseContent.Headers?.ContentDisposition?.FileName).ToString(), - responseContent.Headers?.ContentType?.ToString(), - responseContent.Headers?.ContentLength); - } - - var stringContent = await responseContent.ReadAsStringAsync(); - if (typeof(T) == typeof(string)) - { - return (T)(object)stringContent; - } - - if (stringContent.IsNullOrWhiteSpace()) - { - return default; - } - - return JsonSerializer.Deserialize(stringContent); - } - - public virtual async Task MakeRequestAsync(HttpProxyExecuterContext context) - { - var clientConfig = ClientOptions.HttpClientProxies.GetOrDefault(context.ServiceType) ?? throw new AbpException($"Could not get HttpClientProxyConfig for {context.ServiceType.FullName}."); - var remoteServiceConfig = await RemoteServiceConfigurationProvider.GetConfigurationOrDefaultAsync(clientConfig.RemoteServiceName); - - var client = HttpClientFactory.Create(clientConfig.RemoteServiceName); - - var apiVersion = await GetApiVersionInfoAsync(context); - var url = remoteServiceConfig.BaseUrl.EnsureEndsWith('/') + UrlBuilder.GenerateUrlWithParameters(context.Action, context.Arguments, apiVersion); - - var requestMessage = new HttpRequestMessage(context.Action.GetHttpMethod(), url) - { - Content = RequestPayloadBuilder.BuildContent(context.Action, context.Arguments, JsonSerializer, apiVersion) - }; - - AddHeaders(context.Arguments, context.Action, requestMessage, apiVersion); - - if (context.Action.AllowAnonymous != true) - { - await ClientAuthenticator.Authenticate( - new RemoteServiceHttpClientAuthenticateContext( - client, - requestMessage, - remoteServiceConfig, - clientConfig.RemoteServiceName - ) - ); - } - - var response = await client.SendAsync( - requestMessage, - HttpCompletionOption.ResponseHeadersRead /*this will buffer only the headers, the content will be used as a stream*/, - GetCancellationToken(context.Arguments) - ); - - if (!response.IsSuccessStatusCode) - { - await ThrowExceptionForResponseAsync(response); - } - - return response.Content; - } - - private async Task GetApiVersionInfoAsync(HttpProxyExecuterContext context) - { - var apiVersion = await FindBestApiVersionAsync(context); - - //TODO: Make names configurable? - var versionParam = context.Action.Parameters.FirstOrDefault(p => p.Name == "apiVersion" && p.BindingSourceId == ParameterBindingSources.Path) ?? - context.Action.Parameters.FirstOrDefault(p => p.Name == "api-version" && p.BindingSourceId == ParameterBindingSources.Query); - - return new ApiVersionInfo(versionParam?.BindingSourceId, apiVersion); - } - - private async Task FindBestApiVersionAsync(HttpProxyExecuterContext context) - { - var configuredVersion = await GetConfiguredApiVersionAsync(context); - - if (context.Action.SupportedVersions.IsNullOrEmpty()) - { - return configuredVersion ?? "1.0"; - } - - if (context.Action.SupportedVersions.Contains(configuredVersion)) - { - return configuredVersion; - } - - return context.Action.SupportedVersions.Last(); //TODO: Ensure to get the latest version! - } - - private async Task GetConfiguredApiVersionAsync(HttpProxyExecuterContext context) - { - var clientConfig = ClientOptions.HttpClientProxies.GetOrDefault(context.ServiceType) - ?? throw new AbpException($"Could not get DynamicHttpClientProxyConfig for {context.ServiceType.FullName}."); - - return (await RemoteServiceConfigurationProvider - .GetConfigurationOrDefaultOrNullAsync(clientConfig.RemoteServiceName))?.Version; - } - - private async Task ThrowExceptionForResponseAsync(HttpResponseMessage response) - { - if (response.Headers.Contains(AbpHttpConsts.AbpErrorFormat)) - { - var errorResponse = JsonSerializer.Deserialize( - await response.Content.ReadAsStringAsync() - ); - - throw new AbpRemoteCallException(errorResponse.Error) - { - HttpStatusCode = (int) response.StatusCode - }; - } - - throw new AbpRemoteCallException( - new RemoteServiceErrorInfo - { - Message = response.ReasonPhrase, - Code = response.StatusCode.ToString() - } - ) - { - HttpStatusCode = (int) response.StatusCode - }; - } - - protected virtual void AddHeaders( - IReadOnlyDictionary argumentsDictionary, - ActionApiDescriptionModel action, - HttpRequestMessage requestMessage, - ApiVersionInfo apiVersion) - { - //API Version - if (!apiVersion.Version.IsNullOrEmpty()) - { - //TODO: What about other media types? - requestMessage.Headers.Add("accept", $"{MimeTypes.Text.Plain}; v={apiVersion.Version}"); - requestMessage.Headers.Add("accept", $"{MimeTypes.Application.Json}; v={apiVersion.Version}"); - requestMessage.Headers.Add("api-version", apiVersion.Version); - } - - //Header parameters - var headers = action.Parameters.Where(p => p.BindingSourceId == ParameterBindingSources.Header).ToArray(); - foreach (var headerParameter in headers) - { - var value = HttpActionParameterHelper.FindParameterValue(argumentsDictionary, headerParameter); - if (value != null) - { - requestMessage.Headers.Add(headerParameter.Name, value.ToString()); - } - } - - //CorrelationId - requestMessage.Headers.Add(AbpCorrelationIdOptions.HttpHeaderName, CorrelationIdProvider.Get()); - - //TenantId - if (CurrentTenant.Id.HasValue) - { - //TODO: Use AbpAspNetCoreMultiTenancyOptions to get the key - requestMessage.Headers.Add(TenantResolverConsts.DefaultTenantKey, CurrentTenant.Id.Value.ToString()); - } - - //Culture - //TODO: Is that the way we want? Couldn't send the culture (not ui culture) - var currentCulture = CultureInfo.CurrentUICulture.Name ?? CultureInfo.CurrentCulture.Name; - if (!currentCulture.IsNullOrEmpty()) - { - requestMessage.Headers.AcceptLanguage.Add(new StringWithQualityHeaderValue(currentCulture)); - } - - //X-Requested-With - requestMessage.Headers.Add("X-Requested-With", "XMLHttpRequest"); - } - - protected virtual StringSegment RemoveQuotes(StringSegment input) - { - if (!StringSegment.IsNullOrEmpty(input) && input.Length >= 2 && input[0] == '"' && input[input.Length - 1] == '"') - { - input = input.Subsegment(1, input.Length - 2); - } - - return input; - } - - protected virtual CancellationToken GetCancellationToken(IReadOnlyDictionary arguments) - { - var cancellationTokenArg = arguments.LastOrDefault(); - - if (cancellationTokenArg.Value is CancellationToken cancellationToken) - { - if (cancellationToken != default) - { - return cancellationToken; - } - } - - return CancellationTokenProvider.Token; - } - } -} diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/Proxying/IHttpProxyExecuter.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/Proxying/IHttpProxyExecuter.cs deleted file mode 100644 index a7f48f14fe..0000000000 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/Proxying/IHttpProxyExecuter.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Net.Http; -using System.Threading.Tasks; - -namespace Volo.Abp.Http.Client.Proxying -{ - public interface IHttpProxyExecuter - { - Task MakeRequestAsync(HttpProxyExecuterContext context); - - Task MakeRequestAndGetResultAsync(HttpProxyExecuterContext context); - } -} From f48cafb4ccdccd0b8630a4cb749a3d056b7312e5 Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 16 Sep 2021 14:52:20 +0800 Subject: [PATCH 02/13] Change public to protected in ClientProxyBase. --- .../Http/Client/ClientProxying/ClientProxyBase.cs | 4 ++-- .../DynamicProxying/DynamicHttpProxyInterceptor.cs | 4 ++-- .../DynamicHttpProxyInterceptorClientProxy.cs | 12 +++++++++++- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyBase.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyBase.cs index 703a1bd46e..b39fc35ee8 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyBase.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyBase.cs @@ -68,7 +68,7 @@ namespace Volo.Abp.Http.Client.ClientProxying return dict; } - public virtual async Task RequestAsync(ClientProxyRequestContext requestContext) + protected virtual async Task RequestAsync(ClientProxyRequestContext requestContext) { var responseContent = await RequestAsync(requestContext); @@ -100,7 +100,7 @@ namespace Volo.Abp.Http.Client.ClientProxying return JsonSerializer.Deserialize(stringContent); } - public virtual async Task RequestAsync(ClientProxyRequestContext requestContext) + protected virtual async Task RequestAsync(ClientProxyRequestContext requestContext) { var clientConfig = ClientOptions.Value.HttpClientProxies.GetOrDefault(requestContext.ServiceType) ?? throw new AbpException($"Could not get HttpClientProxyConfig for {requestContext.ServiceType.FullName}."); var remoteServiceConfig = await RemoteServiceConfigurationProvider.GetConfigurationOrDefaultAsync(clientConfig.RemoteServiceName); diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs index 73354773da..2a385b241e 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs @@ -48,14 +48,14 @@ namespace Volo.Abp.Http.Client.DynamicProxying if (invocation.Method.ReturnType.GenericTypeArguments.IsNullOrEmpty()) { - await InterceptorClientProxy.RequestAsync(context); + await InterceptorClientProxy.CallRequestAsync(context); } else { var result = (Task)InterceptorClientProxy .GetType() .GetMethods() - .Single(m => m.Name == nameof(InterceptorClientProxy.RequestAsync) && + .Single(m => m.Name == nameof(InterceptorClientProxy.CallRequestAsync) && m.IsGenericMethod && m.GetParameters().Any(x => x.ParameterType == typeof(ClientProxyRequestContext))) .MakeGenericMethod(invocation.Method.ReturnType.GenericTypeArguments[0]) diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptorClientProxy.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptorClientProxy.cs index d2a9bb971e..0d8cd3f9df 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptorClientProxy.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptorClientProxy.cs @@ -1,9 +1,19 @@ -using Volo.Abp.Http.Client.ClientProxying; +using System.Net.Http; +using System.Threading.Tasks; +using Volo.Abp.Http.Client.ClientProxying; namespace Volo.Abp.Http.Client.DynamicProxying { public class DynamicHttpProxyInterceptorClientProxy : ClientProxyBase { + public virtual async Task CallRequestAsync(ClientProxyRequestContext requestContext) + { + return await base.RequestAsync(requestContext); + } + public virtual async Task CallRequestAsync(ClientProxyRequestContext requestContext) + { + return await base.RequestAsync(requestContext); + } } } From 96aae3a1ae57b3bb33a18289f967777fcb4acec0 Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 16 Sep 2021 15:27:42 +0800 Subject: [PATCH 03/13] Refactor BuildActionArguments method. --- .../Http/Client/ClientProxying/ClientProxyBase.cs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyBase.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyBase.cs index b39fc35ee8..82d31c2067 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyBase.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyBase.cs @@ -57,15 +57,10 @@ namespace Volo.Abp.Http.Client.ClientProxying protected virtual Dictionary BuildActionArguments(ActionApiDescriptionModel action, object[] arguments) { - var parameters = action.Parameters.GroupBy(x => x.NameOnMethod).Select(x => x.Key).ToList(); - var dict = new Dictionary(); - - for (var i = 0; i < parameters.Count; i++) - { - dict[parameters[i]] = arguments[i]; - } - - return dict; + return action.Parameters + .GroupBy(x => x.NameOnMethod) + .Select((x, i) => new KeyValuePair(x.Key, arguments[i])) + .ToDictionary(x => x.Key, x => x.Value); } protected virtual async Task RequestAsync(ClientProxyRequestContext requestContext) From e5c4254df51b187d53f6a84146526331bcb6fadf Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 16 Sep 2021 15:37:50 +0800 Subject: [PATCH 04/13] Remove BuildActionArguments. --- .../Client/ClientProxying/ClientProxyBase.cs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyBase.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyBase.cs index 82d31c2067..b547ca256c 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyBase.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyBase.cs @@ -52,15 +52,13 @@ namespace Volo.Abp.Http.Client.ClientProxying { var methodUniqueName = $"{typeof(TService).FullName}.{methodName}.{string.Join("-", arguments.Select(x => x.GetType().FullName))}"; var action = ClientProxyApiDescriptionFinder.FindAction(methodUniqueName); - return new ClientProxyRequestContext(action, BuildActionArguments(action, arguments), typeof(TService)); - } - - protected virtual Dictionary BuildActionArguments(ActionApiDescriptionModel action, object[] arguments) - { - return action.Parameters - .GroupBy(x => x.NameOnMethod) - .Select((x, i) => new KeyValuePair(x.Key, arguments[i])) - .ToDictionary(x => x.Key, x => x.Value); + return new ClientProxyRequestContext( + action, + action.Parameters + .GroupBy(x => x.NameOnMethod) + .Select((x, i) => new KeyValuePair(x.Key, arguments[i])) + .ToDictionary(x => x.Key, x => x.Value), + typeof(TService)); } protected virtual async Task RequestAsync(ClientProxyRequestContext requestContext) From b65a8d7dc571e3a0eb5bc59d8cbd88f62df6a44a Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 16 Sep 2021 16:59:31 +0800 Subject: [PATCH 05/13] Cache CallRequestAsyncMethod. --- .../DynamicHttpProxyInterceptor.cs | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs index 2a385b241e..c35995447a 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs @@ -16,6 +16,17 @@ namespace Volo.Abp.Http.Client.DynamicProxying { public class DynamicHttpProxyInterceptor : AbpInterceptor, ITransientDependency { + + // ReSharper disable once StaticMemberInGenericType + protected static MethodInfo CallRequestAsyncMethod { get; } + + static DynamicHttpProxyInterceptor() + { + CallRequestAsyncMethod = typeof(DynamicHttpProxyInterceptor) + .GetMethods(BindingFlags.NonPublic | BindingFlags.Instance) + .First(m => m.Name == nameof(CallRequestAsync) && m.IsGenericMethodDefinition); + } + public ILogger> Logger { get; set; } protected DynamicHttpProxyInterceptorClientProxy InterceptorClientProxy { get; } protected AbpHttpClientOptions ClientOptions { get; } @@ -52,25 +63,19 @@ namespace Volo.Abp.Http.Client.DynamicProxying } else { - var result = (Task)InterceptorClientProxy - .GetType() - .GetMethods() - .Single(m => m.Name == nameof(InterceptorClientProxy.CallRequestAsync) && - m.IsGenericMethod && - m.GetParameters().Any(x => x.ParameterType == typeof(ClientProxyRequestContext))) - .MakeGenericMethod(invocation.Method.ReturnType.GenericTypeArguments[0]) - .Invoke(InterceptorClientProxy, new object[] { context }); + var returnType = invocation.Method.ReturnType.GenericTypeArguments[0]; + var result = (Task)CallRequestAsyncMethod + .MakeGenericMethod(returnType) + .Invoke(this, new object[] { context }); - invocation.ReturnValue = await GetResultAsync( - result, - invocation.Method.ReturnType.GetGenericArguments()[0] - ); + invocation.ReturnValue = await GetResultAsync(result, returnType); } } protected virtual async Task GetActionApiDescriptionModel(IAbpMethodInvocation invocation) { - var clientConfig = ClientOptions.HttpClientProxies.GetOrDefault(typeof(TService)) ?? throw new AbpException($"Could not get DynamicHttpClientProxyConfig for {typeof(TService).FullName}."); + var clientConfig = ClientOptions.HttpClientProxies.GetOrDefault(typeof(TService)) ?? + throw new AbpException($"Could not get DynamicHttpClientProxyConfig for {typeof(TService).FullName}."); var remoteServiceConfig = await RemoteServiceConfigurationProvider.GetConfigurationOrDefaultAsync(clientConfig.RemoteServiceName); var client = HttpClientFactory.Create(clientConfig.RemoteServiceName); @@ -82,6 +87,11 @@ namespace Volo.Abp.Http.Client.DynamicProxying ); } + protected virtual async Task CallRequestAsync(ClientProxyRequestContext context) + { + return await InterceptorClientProxy.CallRequestAsync(context); + } + protected virtual async Task GetResultAsync(Task task, Type resultType) { await task; From 6e87e155ded0396ac3f5a444b04c932a9a545fd9 Mon Sep 17 00:00:00 2001 From: Mehmet Erim Date: Wed, 22 Sep 2021 17:25:32 +0300 Subject: [PATCH 06/13] fix testing errors --- .../application-configuration.service.spec.ts | 27 ---- .../lib/tests/config-state.service.spec.ts | 12 +- .../lib/tests/localization.service.spec.ts | 29 ++-- .../lib/tests/permission.directive.spec.ts | 2 +- .../lib/tests/visibility.directive.spec.ts | 126 ------------------ .../suppress-unsaved-changes-warning.token.ts | 1 - npm/ng-packs/scripts/prod-build.ts | 9 -- 7 files changed, 29 insertions(+), 177 deletions(-) delete mode 100644 npm/ng-packs/packages/core/src/lib/tests/application-configuration.service.spec.ts delete mode 100644 npm/ng-packs/packages/core/src/lib/tests/visibility.directive.spec.ts diff --git a/npm/ng-packs/packages/core/src/lib/tests/application-configuration.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/application-configuration.service.spec.ts deleted file mode 100644 index 43b6218697..0000000000 --- a/npm/ng-packs/packages/core/src/lib/tests/application-configuration.service.spec.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; -import { of } from 'rxjs'; -import { ApplicationConfigurationService, RestService } from '../services'; - -describe('ApplicationConfigurationService', () => { - let spectator: SpectatorService; - const createService = createServiceFactory({ - service: ApplicationConfigurationService, - mocks: [RestService], - }); - - beforeEach(() => (spectator = createService())); - - it('should send a GET to application-configuration API', () => { - const rest = spectator.inject(RestService); - - const requestSpy = jest.spyOn(rest, 'request'); - requestSpy.mockReturnValue(of(null)); - - spectator.service.getConfiguration().subscribe(); - - expect(requestSpy).toHaveBeenCalledWith( - { method: 'GET', url: '/api/abp/application-configuration' }, - {}, - ); - }); -}); diff --git a/npm/ng-packs/packages/core/src/lib/tests/config-state.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/config-state.service.spec.ts index a123f65ae3..3dc1b45732 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/config-state.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/config-state.service.spec.ts @@ -1,5 +1,7 @@ import { HttpClientTestingModule } from '@angular/common/http/testing'; import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest'; +import { of } from 'rxjs'; +import { AbpApplicationConfigurationService } from '../proxy/volo/abp/asp-net-core/mvc/application-configurations/abp-application-configuration.service'; import { ApplicationConfigurationDto, CurrentUserDto, @@ -107,14 +109,20 @@ describe('ConfigStateService', () => { const createService = createServiceFactory({ service: ConfigStateService, imports: [HttpClientTestingModule], - providers: [{ provide: CORE_OPTIONS, useValue: { skipGetAppConfiguration: true } }], + providers: [ + { provide: CORE_OPTIONS, useValue: { skipGetAppConfiguration: true } }, + { + provide: AbpApplicationConfigurationService, + useValue: { get: () => of(CONFIG_STATE_DATA) }, + }, + ], }); beforeEach(() => { spectator = createService(); configState = spectator.service; - configState.setState(CONFIG_STATE_DATA); + configState.refreshAppState(); }); describe('#getAll', () => { diff --git a/npm/ng-packs/packages/core/src/lib/tests/localization.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/localization.service.spec.ts index c8d89f54e4..b531b1bf23 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/localization.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/localization.service.spec.ts @@ -1,13 +1,15 @@ import { Injector } from '@angular/core'; import { Router } from '@angular/router'; import { createServiceFactory, SpectatorService, SpyObject } from '@ngneat/spectator/jest'; -import { of } from 'rxjs'; +import { BehaviorSubject } from 'rxjs'; import { AbpApplicationConfigurationService } from '../proxy/volo/abp/asp-net-core/mvc/application-configurations/abp-application-configuration.service'; import { ConfigStateService, SessionStateService } from '../services'; import { LocalizationService } from '../services/localization.service'; import { CORE_OPTIONS } from '../tokens/options.token'; import { CONFIG_STATE_DATA } from './config-state.service.spec'; +const appConfigData$ = new BehaviorSubject(CONFIG_STATE_DATA); + describe('LocalizationService', () => { let spectator: SpectatorService; let sessionState: SpyObject; @@ -25,7 +27,7 @@ describe('LocalizationService', () => { }, { provide: AbpApplicationConfigurationService, - useValue: { get: () => of(CONFIG_STATE_DATA) }, + useValue: { get: () => appConfigData$ }, }, ], }); @@ -36,8 +38,9 @@ describe('LocalizationService', () => { configState = spectator.inject(ConfigStateService); service = spectator.service; - configState.setState(CONFIG_STATE_DATA); + configState.refreshAppState(); sessionState.setLanguage('tr'); + appConfigData$.next(CONFIG_STATE_DATA); }); describe('#currentLang', () => { @@ -108,12 +111,13 @@ describe('LocalizationService', () => { `( 'should return observable $expected when resource name is $resource and key is $key', async ({ resource, key, defaultValue, expected }) => { - configState.setState({ + appConfigData$.next({ localization: { values: { foo: { bar: 'baz' }, x: { y: 'z' } }, defaultResourceName: 'x', }, - }); + } as any); + configState.refreshAppState(); service.localize(resource, key, defaultValue).subscribe(result => { expect(result).toBe(expected); @@ -149,12 +153,13 @@ describe('LocalizationService', () => { `( 'should return $expected when resource name is $resource and key is $key', ({ resource, key, defaultValue, expected }) => { - configState.setState({ + appConfigData$.next({ localization: { values: { foo: { bar: 'baz' }, x: { y: 'z' } }, defaultResourceName: 'x', }, - }); + } as any); + configState.refreshAppState(); const result = service.localizeSync(resource, key, defaultValue); @@ -195,12 +200,13 @@ describe('LocalizationService', () => { `( 'should return observable $expected when resource names are $resources and keys are $keys', async ({ resources, keys, defaultValue, expected }) => { - configState.setState({ + appConfigData$.next({ localization: { values: { foo: { bar: 'baz' }, x: { y: 'z' } }, defaultResourceName: 'x', }, - }); + } as any); + configState.refreshAppState(); service.localizeWithFallback(resources, keys, defaultValue).subscribe(result => { expect(result).toBe(expected); @@ -241,12 +247,13 @@ describe('LocalizationService', () => { `( 'should return $expected when resource names are $resources and keys are $keys', ({ resources, keys, defaultValue, expected }) => { - configState.setState({ + appConfigData$.next({ localization: { values: { foo: { bar: 'baz' }, x: { y: 'z' } }, defaultResourceName: 'x', }, - }); + } as any); + configState.refreshAppState(); const result = service.localizeWithFallbackSync(resources, keys, defaultValue); diff --git a/npm/ng-packs/packages/core/src/lib/tests/permission.directive.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/permission.directive.spec.ts index 7b65a4a9db..d465a60fa3 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/permission.directive.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/permission.directive.spec.ts @@ -17,7 +17,7 @@ describe('PermissionDirective', () => { describe('with condition', () => { beforeEach(() => { spectator = createDirective( - `
Testing Permission Directive
`, + `
Testing Permission Directive
`, ); directive = spectator.directive; }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/visibility.directive.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/visibility.directive.spec.ts deleted file mode 100644 index 04c217eed5..0000000000 --- a/npm/ng-packs/packages/core/src/lib/tests/visibility.directive.spec.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { SpectatorDirective, createDirectiveFactory } from '@ngneat/spectator/jest'; -import { VisibilityDirective } from '../directives/visibility.directive'; - -describe('VisibilityDirective', () => { - let spectator: SpectatorDirective; - let directive: VisibilityDirective; - const createDirective = createDirectiveFactory({ - directive: VisibilityDirective, - }); - - describe('without content', () => { - beforeEach(() => { - spectator = createDirective('
'); - directive = spectator.directive; - }); - - it('should be created', () => { - expect(directive).toBeTruthy(); - }); - - xit('should be removed', done => { - setTimeout(() => { - expect(spectator.query('div')).toBeFalsy(); - done(); - }, 0); - }); - }); - - describe('without mutation observer and with content', () => { - beforeEach(() => { - spectator = createDirective('

Content

'); - directive = spectator.directive; - }); - - it('should not removed', done => { - setTimeout(() => { - expect(spectator.query('div')).toBeTruthy(); - done(); - }, 0); - }); - }); - - describe('without mutation observer and with focused element', () => { - beforeEach(() => { - spectator = createDirective( - '

Content

', - ); - directive = spectator.directive; - }); - - it('should not removed', done => { - setTimeout(() => { - expect(spectator.query('#main')).toBeTruthy(); - done(); - }, 0); - }); - }); - - describe('without content and with focused element', () => { - beforeEach(() => { - spectator = createDirective( - '
', - ); - directive = spectator.directive; - }); - - xit('should be removed', done => { - setTimeout(() => { - expect(spectator.query('#main')).toBeFalsy(); - done(); - }, 0); - }); - }); - - describe('with mutation observer and with content', () => { - beforeEach(() => { - spectator = createDirective('
Content
'); - directive = spectator.directive; - }); - - xit('should remove the main div element when content removed', done => { - spectator.query('#content').remove(); - - setTimeout(() => { - expect(spectator.query('div')).toBeFalsy(); - done(); - }, 0); - }); - - it('should not remove the main div element', done => { - spectator.query('div').appendChild(document.createElement('div')); - - setTimeout(() => { - expect(spectator.query('div')).toBeTruthy(); - done(); - }, 100); - }); - }); - - describe('with mutation observer and with focused element', () => { - beforeEach(() => { - spectator = createDirective( - '

Content

', - ); - directive = spectator.directive; - }); - - xit('should remove the main div element when content removed', done => { - spectator.query('#content').remove(); - - setTimeout(() => { - expect(spectator.query('#main')).toBeFalsy(); - done(); - }, 0); - }); - - it('should not remove the main div element', done => { - spectator.query('#content').appendChild(document.createElement('div')); - - setTimeout(() => { - expect(spectator.query('#main')).toBeTruthy(); - done(); - }, 100); - }); - }); -}); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tokens/suppress-unsaved-changes-warning.token.ts b/npm/ng-packs/packages/theme-shared/src/lib/tokens/suppress-unsaved-changes-warning.token.ts index af68c8130c..b76a18706c 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tokens/suppress-unsaved-changes-warning.token.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tokens/suppress-unsaved-changes-warning.token.ts @@ -1,6 +1,5 @@ import { InjectionToken } from '@angular/core'; -// TODO: Should be documented export const SUPPRESS_UNSAVED_CHANGES_WARNING = new InjectionToken( 'SUPPRESS_UNSAVED_CHANGES_WARNING', ); diff --git a/npm/ng-packs/scripts/prod-build.ts b/npm/ng-packs/scripts/prod-build.ts index 33f2aa2c44..2f189020f4 100644 --- a/npm/ng-packs/scripts/prod-build.ts +++ b/npm/ng-packs/scripts/prod-build.ts @@ -17,15 +17,6 @@ import fse from 'fs-extra'; overwrite: true, }); - // TODO: Will be removed in v3.1, it is added to fix the prod build error - await fse.copy( - '../node_modules/@swimlane', - '../../../templates/app/angular/node_modules/@swimlane', - { - overwrite: true, - }, - ); - await execa('yarn', ['ng', 'build', '--prod'], { stdout: 'inherit', cwd: '../../../templates/app/angular', From b163ab9e7612eefd456ad8530e06fc3a6d8b4826 Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 23 Sep 2021 14:22:48 +0800 Subject: [PATCH 07/13] Check API description. --- .../Abp/Http/Client/ClientProxying/ClientProxyBase.cs | 4 ++++ .../Http/Client/ClientProxying/ClientProxyUrlBuilder.cs | 8 -------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyBase.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyBase.cs index b547ca256c..e45176393b 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyBase.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyBase.cs @@ -52,6 +52,10 @@ namespace Volo.Abp.Http.Client.ClientProxying { var methodUniqueName = $"{typeof(TService).FullName}.{methodName}.{string.Join("-", arguments.Select(x => x.GetType().FullName))}"; var action = ClientProxyApiDescriptionFinder.FindAction(methodUniqueName); + if (action == null) + { + throw new AbpException($"The API description of the {typeof(TService).FullName}.{methodName} method was not found!"); + } return new ClientProxyRequestContext( action, action.Parameters diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyUrlBuilder.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyUrlBuilder.cs index 464ca3f26d..e6a9b8ff71 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyUrlBuilder.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/ClientProxying/ClientProxyUrlBuilder.cs @@ -5,7 +5,6 @@ using System.Globalization; using System.Linq; using System.Text; using JetBrains.Annotations; -using Microsoft.Extensions.DependencyInjection; using Volo.Abp.DependencyInjection; using Volo.Abp.Http.Client.Proxying; using Volo.Abp.Http.Modeling; @@ -16,13 +15,6 @@ namespace Volo.Abp.Http.Client.ClientProxying { public class ClientProxyUrlBuilder : ITransientDependency { - protected IServiceScopeFactory ServiceProviderFactory { get; } - - public ClientProxyUrlBuilder(IServiceScopeFactory serviceProviderFactory) - { - ServiceProviderFactory = serviceProviderFactory; - } - public string GenerateUrlWithParameters(ActionApiDescriptionModel action, IReadOnlyDictionary methodArguments, ApiVersionInfo apiVersion) { // The ASP.NET Core route value provider and query string value provider: From d673f1b396f7787bbf958c3b903be20a3835d9ac Mon Sep 17 00:00:00 2001 From: Mehmet Erim Date: Thu, 23 Sep 2021 09:28:34 +0300 Subject: [PATCH 08/13] fix theme-shared testing errors --- .../extensions/src/tests/enum.util.spec.ts | 7 +- .../extensions/src/tests/state.util.spec.ts | 5 +- .../tests/modal-container.component.spec.ts | 34 -------- .../src/lib/tests/modal.component.spec.ts | 2 +- .../src/lib/tests/modal.service.spec.ts | 87 ------------------- .../tests/sort-order-icon.component.spec.ts | 46 ---------- .../src/lib/tests/validation-utils.spec.ts | 36 +++++--- 7 files changed, 31 insertions(+), 186 deletions(-) delete mode 100644 npm/ng-packs/packages/theme-shared/src/lib/tests/modal-container.component.spec.ts delete mode 100644 npm/ng-packs/packages/theme-shared/src/lib/tests/modal.service.spec.ts delete mode 100644 npm/ng-packs/packages/theme-shared/src/lib/tests/sort-order-icon.component.spec.ts diff --git a/npm/ng-packs/packages/theme-shared/extensions/src/tests/enum.util.spec.ts b/npm/ng-packs/packages/theme-shared/extensions/src/tests/enum.util.spec.ts index ffdc6224a5..9a292541f1 100644 --- a/npm/ng-packs/packages/theme-shared/extensions/src/tests/enum.util.spec.ts +++ b/npm/ng-packs/packages/theme-shared/extensions/src/tests/enum.util.spec.ts @@ -1,5 +1,5 @@ import { ConfigStateService, LocalizationService } from '@abp/ng.core'; -import { BehaviorSubject } from 'rxjs'; +import { BehaviorSubject, of } from 'rxjs'; import { take } from 'rxjs/operators'; import { PropData } from '../lib/models/props'; import { createEnum, createEnumOptions, createEnumValueResolver } from '../lib/utils/enum.util'; @@ -109,8 +109,9 @@ describe('Enum Utils', () => { }); function createMockLocalizationService() { - const configState = new ConfigStateService(null); - configState.setState({ localization: mockL10n } as any); + const fakeAppConfigService = { get: () => of({ localization: mockL10n }) } as any; + const configState = new ConfigStateService(fakeAppConfigService); + configState.refreshAppState(); return new LocalizationService(mockSessionState, null, null, configState); } diff --git a/npm/ng-packs/packages/theme-shared/extensions/src/tests/state.util.spec.ts b/npm/ng-packs/packages/theme-shared/extensions/src/tests/state.util.spec.ts index 000b4b4255..cedd632165 100644 --- a/npm/ng-packs/packages/theme-shared/extensions/src/tests/state.util.spec.ts +++ b/npm/ng-packs/packages/theme-shared/extensions/src/tests/state.util.spec.ts @@ -10,8 +10,9 @@ import { mapEntitiesToContributors, } from '../lib/utils/state.util'; -const configState = new ConfigStateService(null); -configState.setState(createMockState() as any); +const fakeAppConfigService = { get: () => of(createMockState()) } as any; +const configState = new ConfigStateService(fakeAppConfigService); +configState.refreshAppState(); describe('State Utils', () => { describe('#getObjectExtensionEntitiesFromStore', () => { diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/modal-container.component.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/modal-container.component.spec.ts deleted file mode 100644 index 9cb45127e5..0000000000 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/modal-container.component.spec.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Component, ComponentFactoryResolver, ComponentRef } from '@angular/core'; -import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; -import { ModalContainerComponent } from '../components/modal/modal-container.component'; - -describe('ModalContainerComponent', () => { - @Component({ template: '
bar
' }) - class TestComponent {} - - let componentRef: ComponentRef; - let spectator: Spectator; - - const createComponent = createComponentFactory({ - component: ModalContainerComponent, - entryComponents: [TestComponent], - }); - - beforeEach(() => (spectator = createComponent())); - - afterEach(() => componentRef.destroy()); - - describe('#container', () => { - it('should be a ViewContainerRef', () => { - let foo = document.querySelector('div.foo'); - expect(foo).toBeNull(); - - const cfResolver = spectator.inject(ComponentFactoryResolver); - const factory = cfResolver.resolveComponentFactory(TestComponent); - componentRef = spectator.component.container.createComponent(factory); - - foo = document.querySelector('div.foo'); - expect(foo.textContent).toBe('bar'); - }); - }); -}); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/modal.component.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/modal.component.spec.ts index 88ec95c700..e1f15db581 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/modal.component.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/modal.component.spec.ts @@ -103,7 +103,7 @@ describe('ModalComponent', () => { xit('should close with the abpClose', async () => { await wait0ms(); - spectator.dispatchMouseEvent(spectator.component.abpClose, 'click'); + spectator.dispatchMouseEvent(spectator.query('[abpClose]'), 'click'); await wait0ms(); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/modal.service.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/modal.service.spec.ts deleted file mode 100644 index 0c383bc7cf..0000000000 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/modal.service.spec.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { Component, TemplateRef, ViewChild } from '@angular/core'; -import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; -import { ModalContainerComponent } from '../components/modal/modal-container.component'; -import { ModalService } from '../services'; - -describe('ModalContainerComponent', () => { - @Component({ - template: ` - -
bar
-
- `, - }) - class TestComponent { - @ViewChild('ref', { static: true }) - template: TemplateRef; - - constructor(public modalService: ModalService) {} - } - - let spectator: Spectator; - let service: ModalService; - - const createComponent = createComponentFactory({ - component: TestComponent, - entryComponents: [ModalContainerComponent], - }); - - beforeEach(() => { - spectator = createComponent(); - service = spectator.component.modalService; - }); - - afterEach(() => { - service.getContainer().clear(); - service['containerComponentRef'].changeDetectorRef.detectChanges(); - service['containerComponentRef'].destroy(); - }); - - describe('#getContainer', () => { - it('should return the ViewContainerRef of ModalContainerComponent', () => { - let foo = document.querySelector('div.foo'); - expect(foo).toBeNull(); - - const containerRef = service.getContainer(); - const embeddedViewRef = containerRef.createEmbeddedView(spectator.component.template); - - foo = document.querySelector('div.foo'); - expect(foo).toBe(embeddedViewRef.rootNodes[0]); - expect(foo.textContent).toBe('bar'); - }); - }); - - describe('#renderTemplate', () => { - it('should render given template using the ViewContainerRef of ModalContainerComponent', () => { - let foo = document.querySelector('div.foo'); - expect(foo).toBeNull(); - - service.renderTemplate(spectator.component.template); - - foo = document.querySelector('div.foo'); - expect(foo.textContent).toBe('bar'); - }); - }); - - describe('#detectChanges', () => { - it('should call detectChanges on the containerComponentRef', () => { - const spy = jest.spyOn(service['containerComponentRef'].changeDetectorRef, 'detectChanges'); - - service.detectChanges(); - - expect(spy).toHaveBeenCalledTimes(1); - }); - }); - - describe('#clearModal', () => { - it('should call clear on the ViewContainerRef and detectChanges', () => { - const clear = jest.spyOn(service.getContainer(), 'clear'); - const detectChanges = jest.spyOn(service, 'detectChanges'); - - service.clearModal(); - - expect(clear).toHaveBeenCalledTimes(1); - expect(detectChanges).toHaveBeenCalledTimes(1); - }); - }); -}); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/sort-order-icon.component.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/sort-order-icon.component.spec.ts deleted file mode 100644 index a91f5b76c1..0000000000 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/sort-order-icon.component.spec.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest'; -import { SortOrderIconComponent } from '../components/sort-order-icon/sort-order-icon.component'; - -describe('SortOrderIconComponent', () => { - let spectator: SpectatorHost; - let component: SortOrderIconComponent; - const createHost = createHostFactory(SortOrderIconComponent); - - beforeEach(() => { - spectator = createHost( - '', - { - hostProps: { - selectedSortKey: '', - order: '', - }, - }, - ); - component = spectator.component; - }); - - test('should have correct icon class when selectedSortKey and sortKey are the same', () => { - const newKey = 'testKey'; - component.sort(newKey); - expect(component.selectedSortKey).toBe(newKey); - expect(component.order).toBe('asc'); - expect(component.icon).toBe('sorting_asc'); - }); - - test("shouldn't have any icon class when sortKey and selectedSortKey are different", () => { - const newKey = 'otherKey'; - component.sort(newKey); - expect(component.selectedSortKey).toBe(newKey); - expect(component.order).toBe('asc'); - expect(component.icon).toBe('sorting'); - }); - - test('should change order correctly when sort function called', () => { - component.sort('testKey'); - expect(component.order).toBe('asc'); - component.sort('testKey'); - expect(component.order).toBe('desc'); - component.sort('testKey'); - expect(component.order).toBe(''); - }); -}); diff --git a/npm/ng-packs/packages/theme-shared/src/lib/tests/validation-utils.spec.ts b/npm/ng-packs/packages/theme-shared/src/lib/tests/validation-utils.spec.ts index be212ac8dc..54e828036c 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/tests/validation-utils.spec.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/tests/validation-utils.spec.ts @@ -1,4 +1,4 @@ -import { ConfigStateService } from '@abp/ng.core'; +import { AbpApplicationConfigurationService, ConfigStateService } from '@abp/ng.core'; import { CoreTestingModule } from '@abp/ng.core/testing'; import { HttpClient } from '@angular/common/http'; import { Component, Injector } from '@angular/core'; @@ -6,6 +6,7 @@ import { Validators } from '@angular/forms'; import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; import { NgxValidateCoreModule, validatePassword } from '@ngx-validate/core'; import { OAuthService } from 'angular-oauth2-oidc'; +import { of } from 'rxjs'; import { getPasswordValidators } from '../utils'; @Component({ template: '', selector: 'abp-dummy' }) class DummyComponent {} @@ -16,6 +17,26 @@ describe('ValidationUtils', () => { component: DummyComponent, imports: [CoreTestingModule.withConfig(), NgxValidateCoreModule.forRoot()], mocks: [HttpClient, OAuthService], + providers: [ + { + provide: AbpApplicationConfigurationService, + useValue: { + get: () => + of({ + setting: { + values: { + 'Abp.Identity.Password.RequiredLength': '6', + 'Abp.Identity.Password.RequiredUniqueChars': '1', + 'Abp.Identity.Password.RequireNonAlphanumeric': 'True', + 'Abp.Identity.Password.RequireLowercase': 'True', + 'Abp.Identity.Password.RequireUppercase': 'True', + 'Abp.Identity.Password.RequireDigit': 'True', + }, + }, + }), + }, + }, + ], }); beforeEach(() => (spectator = createComponent())); @@ -23,18 +44,7 @@ describe('ValidationUtils', () => { describe('#getPasswordValidators', () => { it('should return password valdiators', () => { const configState = spectator.inject(ConfigStateService); - configState.setState({ - setting: { - values: { - 'Abp.Identity.Password.RequiredLength': '6', - 'Abp.Identity.Password.RequiredUniqueChars': '1', - 'Abp.Identity.Password.RequireNonAlphanumeric': 'True', - 'Abp.Identity.Password.RequireLowercase': 'True', - 'Abp.Identity.Password.RequireUppercase': 'True', - 'Abp.Identity.Password.RequireDigit': 'True', - }, - }, - }); + configState.refreshAppState(); const validators = getPasswordValidators(spectator.inject(Injector)); const expectedValidators = [ From ae96ff6088ef4ec7fb975b5c0c931bdb251bee3b Mon Sep 17 00:00:00 2001 From: enisn Date: Thu, 23 Sep 2021 10:34:37 +0300 Subject: [PATCH 09/13] CmsKit - Implement MultiTenancy --- .../CmsKit/Admin/Menus/MenuItemAdminAppService.cs | 3 ++- .../Volo.CmsKit.Domain/Volo/CmsKit/Menus/MenuItem.cs | 12 +++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Menus/MenuItemAdminAppService.cs b/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Menus/MenuItemAdminAppService.cs index 32458ecb90..6697a89532 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Menus/MenuItemAdminAppService.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Admin.Application/Volo/CmsKit/Admin/Menus/MenuItemAdminAppService.cs @@ -59,7 +59,8 @@ namespace Volo.CmsKit.Admin.Menus input.Order, input.Target, input.ElementId, - input.CssClass + input.CssClass, + CurrentTenant.Id ); await MenuItemRepository.InsertAsync(menuItem); diff --git a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Menus/MenuItem.cs b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Menus/MenuItem.cs index 65d6b0e1ca..34b986b4d1 100644 --- a/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Menus/MenuItem.cs +++ b/modules/cms-kit/src/Volo.CmsKit.Domain/Volo/CmsKit/Menus/MenuItem.cs @@ -23,7 +23,7 @@ namespace Volo.CmsKit.Menus public string DisplayName { get; protected set; } public bool IsActive { get; set; } - + [NotNull] public string Url { get; protected set; } @@ -39,7 +39,7 @@ namespace Volo.CmsKit.Menus public Guid? PageId { get; protected set; } - public Guid? TenantId { get; protected set; } + public Guid? TenantId { get; protected set; } public MenuItem(Guid id, [NotNull] string displayName, @@ -50,8 +50,9 @@ namespace Volo.CmsKit.Menus int order = 0, [CanBeNull] string target = null, [CanBeNull] string elementId = null, - [CanBeNull] string cssClass = null) - :base(id) + [CanBeNull] string cssClass = null, + [CanBeNull] Guid? tenantId = null) + : base(id) { SetDisplayName(displayName); IsActive = isActive; @@ -62,6 +63,7 @@ namespace Volo.CmsKit.Menus Target = target; ElementId = elementId; CssClass = cssClass; + TenantId = tenantId; } public void SetDisplayName([NotNull] string displayName) @@ -69,7 +71,7 @@ namespace Volo.CmsKit.Menus DisplayName = Check.NotNullOrEmpty(displayName, nameof(displayName), MenuItemConsts.MaxDisplayNameLength); } - public void SetUrl([NotNull]string url) + public void SetUrl([NotNull] string url) { Url = Check.NotNullOrEmpty(url, nameof(url), MenuItemConsts.MaxUrlLength); } From ffffa6723d7aaa0b8689665cdc3f658a2176ece5 Mon Sep 17 00:00:00 2001 From: Mehmet Erim Date: Thu, 23 Sep 2021 11:55:51 +0300 Subject: [PATCH 10/13] change default modal size --- .../feature-management/feature-management.component.html | 2 +- .../identity/src/lib/components/roles/roles.component.html | 2 +- .../identity/src/lib/components/users/users.component.html | 2 +- .../src/lib/components/tenants/tenants.component.html | 4 ++-- .../account-layout/tenant-box/tenant-box.component.html | 2 +- .../theme-shared/src/lib/components/modal/modal.component.ts | 4 ++-- npm/ng-packs/tsconfig.json | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.html b/npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.html index 126fb9638c..a9c8032e4d 100644 --- a/npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.html +++ b/npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.html @@ -1,4 +1,4 @@ - +

{{ 'AbpFeatureManagement::Features' | abpLocalization }}

diff --git a/npm/ng-packs/packages/identity/src/lib/components/roles/roles.component.html b/npm/ng-packs/packages/identity/src/lib/components/roles/roles.component.html index d010fe1983..79c516d8b7 100644 --- a/npm/ng-packs/packages/identity/src/lib/components/roles/roles.component.html +++ b/npm/ng-packs/packages/identity/src/lib/components/roles/roles.component.html @@ -19,7 +19,7 @@ - +

{{ (selected?.id ? 'AbpIdentity::Edit' : 'AbpIdentity::NewRole') | abpLocalization }}

diff --git a/npm/ng-packs/packages/identity/src/lib/components/users/users.component.html b/npm/ng-packs/packages/identity/src/lib/components/users/users.component.html index 881726d490..64ad0dcd06 100644 --- a/npm/ng-packs/packages/identity/src/lib/components/users/users.component.html +++ b/npm/ng-packs/packages/identity/src/lib/components/users/users.component.html @@ -29,7 +29,7 @@ - +

{{ (selected?.id ? 'AbpIdentity::Edit' : 'AbpIdentity::NewUser') | abpLocalization }}

diff --git a/npm/ng-packs/packages/tenant-management/src/lib/components/tenants/tenants.component.html b/npm/ng-packs/packages/tenant-management/src/lib/components/tenants/tenants.component.html index 28742e3a46..1fe64b0f91 100644 --- a/npm/ng-packs/packages/tenant-management/src/lib/components/tenants/tenants.component.html +++ b/npm/ng-packs/packages/tenant-management/src/lib/components/tenants/tenants.component.html @@ -30,11 +30,11 @@ - +

{{ - (selected?.id ? 'AbpTenantManagement::Edit' : 'AbpTenantManagement::NewTenant') + (selected?.id ? 'AbpTenantManagement::Edit' : 'AbpTenantManagement::NewTenant') | abpLocalization }}

diff --git a/npm/ng-packs/packages/theme-basic/src/lib/components/account-layout/tenant-box/tenant-box.component.html b/npm/ng-packs/packages/theme-basic/src/lib/components/account-layout/tenant-box/tenant-box.component.html index e4381b7b05..ef74c61bc1 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/components/account-layout/tenant-box/tenant-box.component.html +++ b/npm/ng-packs/packages/theme-basic/src/lib/components/account-layout/tenant-box/tenant-box.component.html @@ -24,7 +24,7 @@ - +
Switch Tenant
diff --git a/npm/ng-packs/packages/theme-shared/src/lib/components/modal/modal.component.ts b/npm/ng-packs/packages/theme-shared/src/lib/components/modal/modal.component.ts index 008a2c3e9b..2cbf09c7f3 100644 --- a/npm/ng-packs/packages/theme-shared/src/lib/components/modal/modal.component.ts +++ b/npm/ng-packs/packages/theme-shared/src/lib/components/modal/modal.component.ts @@ -143,8 +143,8 @@ export class ModalComponent implements OnInit, OnDestroy, DismissableModal { setTimeout(() => this.listen(), 0); this.modalRef = this.modal.open(this.modalContent, { - size: 'lg', - centered: true, + size: 'md', + centered: false, keyboard: false, scrollable: true, beforeDismiss: () => { diff --git a/npm/ng-packs/tsconfig.json b/npm/ng-packs/tsconfig.json index 674468c130..dcca4d0af8 100644 --- a/npm/ng-packs/tsconfig.json +++ b/npm/ng-packs/tsconfig.json @@ -24,7 +24,7 @@ "@abp/ng.core/locale": ["packages/core/locale/src/public-api.ts"], "@abp/ng.core/testing": ["packages/core/testing/src/public-api.ts"], "@abp/ng.feature-management": ["packages/feature-management/src/public-api.ts"], - "@abp/ng.identity": ["packages/identity//src/public-api.ts"], + "@abp/ng.identity": ["packages/identity/src/public-api.ts"], "@abp/ng.identity/config": ["packages/identity/config/src/public-api.ts"], "@abp/ng.permission-management": ["packages/permission-management/src/public-api.ts"], "@abp/ng.setting-management": ["packages/setting-management/src/public-api.ts"], From 4a6cbacaeb863616b853885d2399c5907bcdc381 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Thu, 23 Sep 2021 17:13:45 +0800 Subject: [PATCH 11/13] Remove ProfileManagementScriptBundleContributor --- ...ntProfilePasswordManagementGroupViewComponent.cs | 3 --- ...ofilePersonalInfoManagementGroupViewComponent.cs | 3 --- .../ProfileManagementScriptBundleContributor.cs | 13 ------------- 3 files changed, 19 deletions(-) delete mode 100644 modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/ProfileManagementScriptBundleContributor.cs diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/Password/AccountProfilePasswordManagementGroupViewComponent.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/Password/AccountProfilePasswordManagementGroupViewComponent.cs index 1c4a0c9936..8bacadfb4a 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/Password/AccountProfilePasswordManagementGroupViewComponent.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/Password/AccountProfilePasswordManagementGroupViewComponent.cs @@ -9,9 +9,6 @@ using Volo.Abp.Validation; namespace Volo.Abp.Account.Web.Pages.Account.Components.ProfileManagementGroup.Password { - [Widget( - ScriptTypes = new[] { typeof(ProfileManagementScriptBundleContributor) } - )] public class AccountProfilePasswordManagementGroupViewComponent : AbpViewComponent { private readonly IProfileAppService _profileAppService; diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/AccountProfilePersonalInfoManagementGroupViewComponent.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/AccountProfilePersonalInfoManagementGroupViewComponent.cs index d199f32625..c9fb2b660e 100644 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/AccountProfilePersonalInfoManagementGroupViewComponent.cs +++ b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/PersonalInfo/AccountProfilePersonalInfoManagementGroupViewComponent.cs @@ -9,9 +9,6 @@ using Volo.Abp.Validation; namespace Volo.Abp.Account.Web.Pages.Account.Components.ProfileManagementGroup.PersonalInfo { - [Widget( - ScriptTypes = new[] { typeof(ProfileManagementScriptBundleContributor) } - )] public class AccountProfilePersonalInfoManagementGroupViewComponent : AbpViewComponent { private readonly IProfileAppService _profileAppService; diff --git a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/ProfileManagementScriptBundleContributor.cs b/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/ProfileManagementScriptBundleContributor.cs deleted file mode 100644 index 54a47c84be..0000000000 --- a/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Components/ProfileManagementGroup/ProfileManagementScriptBundleContributor.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Collections.Generic; -using Volo.Abp.AspNetCore.Mvc.UI.Bundling; - -namespace Volo.Abp.Account.Web.Pages.Account.Components.ProfileManagementGroup -{ - public class ProfileManagementScriptBundleContributor: BundleContributor - { - public override void ConfigureBundle(BundleConfigurationContext context) - { - context.Files.AddIfNotContains("/client-proxies/identity-proxy.js"); - } - } -} From de5e8b40fd8679862d0ed35a955e6c2635ee53de Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 23 Sep 2021 19:28:09 +0800 Subject: [PATCH 12/13] Update Text-Templating.md --- docs/zh-Hans/Text-Templating.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/zh-Hans/Text-Templating.md b/docs/zh-Hans/Text-Templating.md index be9cbb3a79..d61c85bc9b 100644 --- a/docs/zh-Hans/Text-Templating.md +++ b/docs/zh-Hans/Text-Templating.md @@ -187,9 +187,9 @@ var result = await _templateRenderer.RenderAsync( 示例中我们并没有创建模型类,但是创建了一个匿名对象模型. -### 大驼峰 与 小驼峰 +### PascalCase 与 snake_case -PascalCase 属性名(如 `UserName`) 在模板中用做小驼峰(如 `userName`). +PascalCase 属性名(如 `UserName`) 在模板中使用蛇形命名(如 `user_name`). ## 本地化 @@ -454,4 +454,4 @@ public class MyTemplateContentProvider * 本文开发和引用的[应用程序示例源码](https://github.com/abpframework/abp-samples/tree/master/TextTemplateDemo). * [本地化系统](Localization.md). -* [虚拟文件系统](Virtual-File-System.md). \ No newline at end of file +* [虚拟文件系统](Virtual-File-System.md). From 17af378d8a1ea5e31daaa8aa56b0992e295add24 Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 23 Sep 2021 20:40:23 +0800 Subject: [PATCH 13/13] Check SettingManagementFeatures. --- .../SettingManagement/EmailSettingsAppService.cs | 3 ++- .../Settings/EmailingPageContributor.cs | 15 ++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Application/Volo/Abp/SettingManagement/EmailSettingsAppService.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.Application/Volo/Abp/SettingManagement/EmailSettingsAppService.cs index ca3297c87b..35266c4306 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Application/Volo/Abp/SettingManagement/EmailSettingsAppService.cs +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Application/Volo/Abp/SettingManagement/EmailSettingsAppService.cs @@ -59,8 +59,9 @@ namespace Volo.Abp.SettingManagement await SettingManager.SetForTenantOrGlobalAsync(CurrentTenant.Id, EmailSettingNames.DefaultFromDisplayName, input.DefaultFromDisplayName); } - private async Task CheckFeatureAsync() + protected virtual async Task CheckFeatureAsync() { + await FeatureChecker.CheckEnabledAsync(SettingManagementFeatures.Enable); if (CurrentTenant.IsAvailable) { await FeatureChecker.CheckEnabledAsync(SettingManagementFeatures.AllowTenantsToChangeEmailSettings); diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Settings/EmailingPageContributor.cs b/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Settings/EmailingPageContributor.cs index 1cbb75428c..dd596cab16 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Settings/EmailingPageContributor.cs +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Web/Settings/EmailingPageContributor.cs @@ -48,17 +48,18 @@ namespace Volo.Abp.SettingManagement.Web.Settings private async Task CheckFeatureAsync(SettingPageCreationContext context) { - var currentTenant = context.ServiceProvider.GetRequiredService(); - - if (!currentTenant.IsAvailable) + var featureCheck = context.ServiceProvider.GetRequiredService(); + if (!await featureCheck.IsEnabledAsync(SettingManagementFeatures.Enable)) { - return true; + return false; } - var featureCheck = context.ServiceProvider.GetRequiredService(); - - return await featureCheck.IsEnabledAsync(SettingManagementFeatures.AllowTenantsToChangeEmailSettings); + if (context.ServiceProvider.GetRequiredService().IsAvailable) + { + return await featureCheck.IsEnabledAsync(SettingManagementFeatures.AllowTenantsToChangeEmailSettings); + } + return true; } } }