diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs index ac7804ceb3..c562244d68 100644 --- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs @@ -15,12 +15,12 @@ using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.ViewComponents; using Microsoft.Extensions.DependencyInjection.Extensions; +using Volo.Abp.Http; namespace Volo.Abp.AspNetCore.Mvc { - [DependsOn( - typeof(AbpAspNetCoreModule) - )] + [DependsOn(typeof(AbpHttpModule))] + [DependsOn(typeof(AbpAspNetCoreModule))] public class AbpAspNetCoreMvcModule : AbpModule { public override void ConfigureServices(IServiceCollection services) 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/ApiExploring/AbpApiDefinitionController.cs similarity index 83% rename from src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Controllers/AbpApiDefinitionController.cs rename to src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpApiDefinitionController.cs index 0a8c6fb8ef..869f1ba136 100644 --- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Controllers/AbpApiDefinitionController.cs +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpApiDefinitionController.cs @@ -1,7 +1,7 @@ using Microsoft.AspNetCore.Mvc; using Volo.Abp.Http.Modeling; -namespace Volo.Abp.AspNetCore.Mvc.Controllers +namespace Volo.Abp.AspNetCore.Mvc.ApiExploring { [Area("abp")] public class AbpApiDefinitionController : AbpController @@ -17,7 +17,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Controllers [Route("api/abp/api-description")] public ApplicationApiDescriptionModel Get() { - return _modelProvider.CreateModel(); + return _modelProvider.CreateApiModel(); } } } 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 index 799b65ac6e..4647fb1a1f 100644 --- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs @@ -32,7 +32,7 @@ namespace Volo.Abp.AspNetCore.Mvc Logger = NullLogger.Instance; } - public ApplicationApiDescriptionModel CreateModel() + public ApplicationApiDescriptionModel CreateApiModel() { var model = ApplicationApiDescriptionModel.Create(); diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ProxyScripting/AbpServiceProxiesController.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ProxyScripting/AbpServiceProxiesController.cs new file mode 100644 index 0000000000..9be8853a48 --- /dev/null +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ProxyScripting/AbpServiceProxiesController.cs @@ -0,0 +1,24 @@ +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.Http.ProxyScripting; + +namespace Volo.Abp.AspNetCore.Mvc.ProxyScripting +{ + //TODO: [DontWrapResult] + //TODO: [DisableAuditing] + public class AbpServiceProxiesController : AbpController + { + private readonly IProxyScriptManager _proxyScriptManager; + + public AbpServiceProxiesController(IProxyScriptManager proxyScriptManager) + { + _proxyScriptManager = proxyScriptManager; + } + + [Produces("text/javascript", "text/plain")] + public string GetAll(ServiceProxyGenerationModel model) + { + model.Normalize(); + return _proxyScriptManager.GetScript(model.CreateOptions()); + } + } +} diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ProxyScripting/ServiceProxyGenerationModel.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ProxyScripting/ServiceProxyGenerationModel.cs new file mode 100644 index 0000000000..5cc9a0a3c5 --- /dev/null +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ProxyScripting/ServiceProxyGenerationModel.cs @@ -0,0 +1,55 @@ +using System; +using System.Linq; +using Volo.Abp.Http.ProxyScripting; +using Volo.Abp.Http.ProxyScripting.Generators.JQuery; + +namespace Volo.Abp.AspNetCore.Mvc.ProxyScripting +{ + public class ServiceProxyGenerationModel //: TODO: IShouldNormalize + { + public string Type { get; set; } + + public bool UseCache { get; set; } + + public string Modules { get; set; } + + public string Controllers { get; set; } + + public string Actions { get; set; } + + public ServiceProxyGenerationModel() + { + UseCache = true; + } + + public void Normalize() + { + if (Type.IsNullOrEmpty()) + { + Type = JQueryProxyScriptGenerator.Name; + } + } + + public ProxyScriptingModel CreateOptions() + { + var options = new ProxyScriptingModel(Type, UseCache); + + if (!Modules.IsNullOrEmpty()) + { + options.Modules = Modules.Split('|').Select(m => m.Trim()).ToArray(); + } + + if (!Controllers.IsNullOrEmpty()) + { + options.Controllers = Controllers.Split('|').Select(m => m.Trim()).ToArray(); + } + + if (!Actions.IsNullOrEmpty()) + { + options.Actions = Actions.Split('|').Select(m => m.Trim()).ToArray(); + } + + return options; + } + } +} \ 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 index 09dadfe8ac..681a9f9c1d 100644 --- 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 @@ -7,6 +7,7 @@ using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; using Volo.Abp.DynamicProxy; using Volo.Abp.Http.Modeling; +using Volo.Abp.Http.ProxyScripting.Generators; using Volo.Abp.Json; using Volo.Abp.Threading; @@ -104,7 +105,7 @@ namespace Volo.Abp.Http.Client.DynamicProxying private static void AddHeaders(IAbpMethodInvocation invocation, ActionApiDescriptionModel action, HttpRequestMessage requestMessage) { - foreach (var headerParameter in action.Parameters.Where(p => p.BindingSourceId == "Header")) + foreach (var headerParameter in action.Parameters.Where(p => p.BindingSourceId == ParameterBindingSources.Header)) { var value = HttpActionParameterHelper.FindParameterValue(invocation.ArgumentsDictionary, headerParameter); if (value != null) diff --git a/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/RequestPayloadBuilder.cs b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/RequestPayloadBuilder.cs index 7719e031b0..f58bda5b9c 100644 --- a/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/RequestPayloadBuilder.cs +++ b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/RequestPayloadBuilder.cs @@ -4,6 +4,7 @@ using System.Net.Http; using System.Text; using JetBrains.Annotations; using Volo.Abp.Http.Modeling; +using Volo.Abp.Http.ProxyScripting.Generators; using Volo.Abp.Json; namespace Volo.Abp.Http.Client.DynamicProxying @@ -33,7 +34,7 @@ namespace Volo.Abp.Http.Client.DynamicProxying { var parameters = action .Parameters - .Where(p => p.BindingSourceId == "Body") + .Where(p => p.BindingSourceId == ParameterBindingSources.Body) .ToArray(); if (parameters.Length <= 0) @@ -61,7 +62,7 @@ namespace Volo.Abp.Http.Client.DynamicProxying { var parameters = action .Parameters - .Where(p => p.BindingSourceId == "Form") + .Where(p => p.BindingSourceId == ParameterBindingSources.Form) .ToArray(); if (!parameters.Any()) diff --git a/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/UrlBuilder.cs b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/UrlBuilder.cs index 1210e9a813..f829c9a7a7 100644 --- a/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/UrlBuilder.cs +++ b/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/UrlBuilder.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using Volo.Abp.Http.Modeling; +using Volo.Abp.Http.ProxyScripting.Generators; namespace Volo.Abp.Http.Client.DynamicProxying { @@ -21,7 +22,7 @@ namespace Volo.Abp.Http.Client.DynamicProxying private static void ReplacePathVariables(StringBuilder urlBuilder, IList actionParameters, IReadOnlyDictionary methodArguments) { var pathParameters = actionParameters - .Where(p => p.BindingSourceId == "Path") + .Where(p => p.BindingSourceId == ParameterBindingSources.Path) .ToArray(); if (!pathParameters.Any()) @@ -57,7 +58,7 @@ namespace Volo.Abp.Http.Client.DynamicProxying private static void AddQueryStringParameters(StringBuilder urlBuilder, IList actionParameters, IReadOnlyDictionary methodArguments) { var queryStringParameters = actionParameters - .Where(p => p.BindingSourceId.IsIn("ModelBinding", "Query")) + .Where(p => p.BindingSourceId.IsIn(ParameterBindingSources.ModelBinding, ParameterBindingSources.Query)) .ToArray(); if (!queryStringParameters.Any()) diff --git a/src/Volo.Abp.Http/Volo/Abp/Http/AbpHttpModule.cs b/src/Volo.Abp.Http/Volo/Abp/Http/AbpHttpModule.cs index 2e3d11e01b..84a438bb48 100644 --- a/src/Volo.Abp.Http/Volo/Abp/Http/AbpHttpModule.cs +++ b/src/Volo.Abp.Http/Volo/Abp/Http/AbpHttpModule.cs @@ -1,4 +1,6 @@ using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Http.ProxyScripting.Configuration; +using Volo.Abp.Http.ProxyScripting.Generators.JQuery; using Volo.Abp.Modularity; namespace Volo.Abp.Http @@ -8,6 +10,11 @@ namespace Volo.Abp.Http public override void ConfigureServices(IServiceCollection services) { services.AddAssemblyOf(); + + services.Configure(options => + { + options.Generators[JQueryProxyScriptGenerator.Name] = typeof(JQueryProxyScriptGenerator); + }); } } } diff --git a/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/IApiDescriptionModelProvider.cs b/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/IApiDescriptionModelProvider.cs index 35334fae33..800dc504db 100644 --- a/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/IApiDescriptionModelProvider.cs +++ b/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/IApiDescriptionModelProvider.cs @@ -2,6 +2,6 @@ namespace Volo.Abp.Http.Modeling { public interface IApiDescriptionModelProvider { - ApplicationApiDescriptionModel CreateModel(); + ApplicationApiDescriptionModel CreateApiModel(); } } \ No newline at end of file diff --git a/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Configuration/AbpApiProxyScriptingOptions.cs b/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Configuration/AbpApiProxyScriptingOptions.cs new file mode 100644 index 0000000000..e698d00416 --- /dev/null +++ b/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Configuration/AbpApiProxyScriptingOptions.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; + +namespace Volo.Abp.Http.ProxyScripting.Configuration +{ + public class AbpApiProxyScriptingOptions + { + public IDictionary Generators { get; } + + public AbpApiProxyScriptingOptions() + { + Generators = new Dictionary(); + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/IProxyScriptGenerator.cs b/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/IProxyScriptGenerator.cs new file mode 100644 index 0000000000..fba0dc7d62 --- /dev/null +++ b/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/IProxyScriptGenerator.cs @@ -0,0 +1,9 @@ +using Volo.Abp.Http.Modeling; + +namespace Volo.Abp.Http.ProxyScripting.Generators +{ + public interface IProxyScriptGenerator + { + string CreateScript(ApplicationApiDescriptionModel model); + } +} \ No newline at end of file diff --git a/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/JQuery/JQueryProxyScriptGenerator.cs b/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/JQuery/JQueryProxyScriptGenerator.cs new file mode 100644 index 0000000000..7e0e2431d3 --- /dev/null +++ b/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/JQuery/JQueryProxyScriptGenerator.cs @@ -0,0 +1,121 @@ +using System; +using System.Text; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Http.Modeling; + +namespace Volo.Abp.Http.ProxyScripting.Generators.JQuery +{ + public class JQueryProxyScriptGenerator : IProxyScriptGenerator, ITransientDependency + { + /// + /// "jquery". + /// + public const string Name = "jquery"; + + public string CreateScript(ApplicationApiDescriptionModel model) + { + var script = new StringBuilder(); + + script.AppendLine("/* This file is automatically generated by ABP framework to use MVC Controllers from javascript. */"); + script.AppendLine(); + script.AppendLine("var abp = abp || {};"); + script.AppendLine("abp.services = abp.services || {};"); + + foreach (var module in model.Modules.Values) + { + script.AppendLine(); + AddModuleScript(script, module); + } + + return script.ToString(); + } + + private static void AddModuleScript(StringBuilder script, ModuleApiDescriptionModel module) + { + script.AppendLine($"// module '{module.Name.ToCamelCase()}'"); + script.AppendLine("(function(){"); + script.AppendLine(); + script.AppendLine($" abp.services.{module.Name.ToCamelCase()} = abp.services.{module.Name.ToCamelCase()} || {{}};"); + + foreach (var controller in module.Controllers.Values) + { + script.AppendLine(); + AddControllerScript(script, module, controller); + } + + script.AppendLine(); + script.AppendLine("})();"); + } + + private static void AddControllerScript(StringBuilder script, ModuleApiDescriptionModel module, ControllerApiDescriptionModel controller) + { + script.AppendLine($" // controller '{controller.ControllerName.ToCamelCase()}'"); + script.AppendLine(" (function(){"); + script.AppendLine(); + + script.AppendLine($" abp.services.{module.Name.ToCamelCase()}.{controller.ControllerName.ToCamelCase()} = abp.services.{module.Name.ToCamelCase()}.{controller.ControllerName.ToCamelCase()} || {{}};"); + + foreach (var action in controller.Actions.Values) + { + script.AppendLine(); + AddActionScript(script, module, controller, action); + } + + script.AppendLine(); + script.AppendLine(" })();"); + } + + private static void AddActionScript(StringBuilder script, ModuleApiDescriptionModel module, ControllerApiDescriptionModel controller, ActionApiDescriptionModel action) + { + var parameterList = ProxyScriptingJsFuncHelper.GenerateJsFuncParameterList(action, "ajaxParams"); + + script.AppendLine($" // action '{action.NameOnClass.ToCamelCase()}'"); + script.AppendLine($" abp.services.{module.Name.ToCamelCase()}.{controller.ControllerName.ToCamelCase()}{ProxyScriptingJsFuncHelper.WrapWithBracketsOrWithDotPrefix(action.NameOnClass.ToCamelCase())} = function({parameterList}) {{"); + script.AppendLine(" return abp.ajax($.extend(true, {"); + + AddAjaxCallParameters(script, controller, action); + + script.AppendLine(" }, ajaxParams));;"); + script.AppendLine(" };"); + } + + private static void AddAjaxCallParameters(StringBuilder script, ControllerApiDescriptionModel controller, ActionApiDescriptionModel action) + { + var httpMethod = action.HttpMethod?.ToUpperInvariant() ?? "POST"; + + script.AppendLine(" url: abp.appPath + '" + ProxyScriptingHelper.GenerateUrlWithParameters(action) + "',"); + script.Append(" type: '" + httpMethod + "'"); + + if (action.ReturnValue.Type == typeof(void)) + { + script.AppendLine(","); + script.Append(" dataType: null"); + } + + var headers = ProxyScriptingHelper.GenerateHeaders(action, 8); + if (headers != null) + { + script.AppendLine(","); + script.Append(" headers: " + headers); + } + + var body = ProxyScriptingHelper.GenerateBody(action); + if (!body.IsNullOrEmpty()) + { + script.AppendLine(","); + script.Append(" data: JSON.stringify(" + body + ")"); + } + else + { + var formData = ProxyScriptingHelper.GenerateFormPostData(action, 8); + if (!formData.IsNullOrEmpty()) + { + script.AppendLine(","); + script.Append(" data: " + formData); + } + } + + script.AppendLine(); + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/ParameterBindingSources.cs b/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/ParameterBindingSources.cs new file mode 100644 index 0000000000..ee710ebb08 --- /dev/null +++ b/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/ParameterBindingSources.cs @@ -0,0 +1,14 @@ +namespace Volo.Abp.Http.ProxyScripting.Generators +{ + public static class ParameterBindingSources + { + public const string ModelBinding = "ModelBinding"; + public const string Query = "Query"; + public const string Body = "Body"; + public const string Path = "Path"; + public const string Form = "Form"; + public const string Header = "Header"; + public const string Custom = "Custom"; + public const string Services = "Services"; + } +} \ No newline at end of file diff --git a/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/ProxyScriptingHelper.cs b/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/ProxyScriptingHelper.cs new file mode 100644 index 0000000000..c962263bd8 --- /dev/null +++ b/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/ProxyScriptingHelper.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Volo.Abp.Http.Modeling; + +namespace Volo.Abp.Http.ProxyScripting.Generators +{ + internal static class ProxyScriptingHelper + { + public const string DefaultHttpVerb = "POST"; + + public static string GenerateUrlWithParameters(ActionApiDescriptionModel action) + { + //TODO: Can be optimized using StringBuilder? + var url = ReplacePathVariables(action.Url, action.Parameters); + url = AddQueryStringParameters(url, action.Parameters); + return url; + } + + public static string GenerateHeaders(ActionApiDescriptionModel action, int indent = 0) + { + var parameters = action + .Parameters + .Where(p => p.BindingSourceId == ParameterBindingSources.Header) + .ToArray(); + + if (!parameters.Any()) + { + return null; + } + + return ProxyScriptingJsFuncHelper.CreateJsObjectLiteral(parameters, indent); + } + + public static string GenerateBody(ActionApiDescriptionModel action) + { + var parameters = action + .Parameters + .Where(p => p.BindingSourceId == ParameterBindingSources.Body) + .ToArray(); + + if (parameters.Length <= 0) + { + return null; + } + + if (parameters.Length > 1) + { + throw new AbpException( + $"Only one complex type allowed as argument to a controller action that's binding source is 'Body'. But {action.UniqueName} ({action.Url}) contains more than one!" + ); + } + + return ProxyScriptingJsFuncHelper.GetParamNameInJsFunc(parameters[0]); + } + + public static string GenerateFormPostData(ActionApiDescriptionModel action, int indent = 0) + { + var parameters = action + .Parameters + .Where(p => p.BindingSourceId == ParameterBindingSources.Form) + .ToArray(); + + if (!parameters.Any()) + { + return null; + } + + return ProxyScriptingJsFuncHelper.CreateJsObjectLiteral(parameters, indent); + } + + private static string ReplacePathVariables(string url, IList actionParameters) + { + var pathParameters = actionParameters + .Where(p => p.BindingSourceId == ParameterBindingSources.Path) + .ToArray(); + + if (!pathParameters.Any()) + { + return url; + } + + foreach (var pathParameter in pathParameters) + { + url = url.Replace($"{{{pathParameter.Name}}}", $"' + {ProxyScriptingJsFuncHelper.GetParamNameInJsFunc(pathParameter)} + '"); + } + + return url; + } + + private static string AddQueryStringParameters(string url, IList actionParameters) + { + var queryStringParameters = actionParameters + .Where(p => p.BindingSourceId.IsIn(ParameterBindingSources.ModelBinding, ParameterBindingSources.Query)) + .ToArray(); + + if (!queryStringParameters.Any()) + { + return url; + } + + var qsBuilderParams = queryStringParameters + .Select(p => $"{{ name: '{p.Name.ToCamelCase()}', value: {ProxyScriptingJsFuncHelper.GetParamNameInJsFunc(p)} }}") + .JoinAsString(", "); + + return url + $"' + abp.utils.buildQueryString([{qsBuilderParams}]) + '"; + } + + public static string GetConventionalVerbForMethodName(string methodName) + { + if (methodName.StartsWith("Get", StringComparison.OrdinalIgnoreCase)) + { + return "GET"; + } + + if (methodName.StartsWith("Put", StringComparison.OrdinalIgnoreCase) || + methodName.StartsWith("Update", StringComparison.OrdinalIgnoreCase)) + { + return "PUT"; + } + + if (methodName.StartsWith("Delete", StringComparison.OrdinalIgnoreCase) || + methodName.StartsWith("Remove", StringComparison.OrdinalIgnoreCase)) + { + return "DELETE"; + } + + if (methodName.StartsWith("Patch", StringComparison.OrdinalIgnoreCase)) + { + return "PATCH"; + } + + if (methodName.StartsWith("Post", StringComparison.OrdinalIgnoreCase) || + methodName.StartsWith("Create", StringComparison.OrdinalIgnoreCase) || + methodName.StartsWith("Insert", StringComparison.OrdinalIgnoreCase)) + { + return "POST"; + } + + //Default + return DefaultHttpVerb; + } + } +} diff --git a/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/ProxyScriptingJsFuncHelper.cs b/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/ProxyScriptingJsFuncHelper.cs new file mode 100644 index 0000000000..a92bbe1710 --- /dev/null +++ b/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/ProxyScriptingJsFuncHelper.cs @@ -0,0 +1,141 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Volo.Abp.Http.Modeling; + +namespace Volo.Abp.Http.ProxyScripting.Generators +{ + internal static class ProxyScriptingJsFuncHelper + { + private const string ValidJsVariableNameChars = "abcdefghijklmnopqrstuxwvyzABCDEFGHIJKLMNOPQRSTUXWVYZ0123456789_"; + + private static readonly HashSet ReservedWords = new HashSet { + "abstract", + "else", + "instanceof", + "super", + "boolean", + "enum", + "int", + "switch", + "break", + "export", + "interface", + "synchronized", + "byte", + "extends", + "let", + "this", + "case", + "false", + "long", + "throw", + "catch", + "final", + "native", + "throws", + "char", + "finally", + "new", + "transient", + "class", + "float", + "null", + "true", + "const", + "for", + "package", + "try", + "continue", + "function", + "private", + "typeof", + "debugger", + "goto", + "protected", + "var", + "default", + "if", + "public", + "void", + "delete", + "implements", + "return", + "volatile", + "do", + "import", + "short", + "while", + "double", + "in", + "static", + "with" + }; + + public static string NormalizeJsVariableName(string name, string additionalChars = "") + { + var validChars = ValidJsVariableNameChars + additionalChars; + + var sb = new StringBuilder(name); + + sb.Replace('-', '_'); + + //Delete invalid chars + foreach (var c in name) + { + if (!validChars.Contains(c)) + { + sb.Replace(c.ToString(), ""); + } + } + + if (sb.Length == 0) + { + return "_" + Guid.NewGuid().ToString("N").Left(8); + } + + return sb.ToString(); + } + + public static string WrapWithBracketsOrWithDotPrefix(string name) + { + if (!ReservedWords.Contains(name)) + { + return "." + name; + } + + return "['" + name + "']"; + } + + public static string GetParamNameInJsFunc(ParameterApiDescriptionModel parameterInfo) + { + return parameterInfo.Name == parameterInfo.NameOnMethod + ? NormalizeJsVariableName(parameterInfo.Name.ToCamelCase(), ".") + : NormalizeJsVariableName(parameterInfo.NameOnMethod.ToCamelCase()) + "." + NormalizeJsVariableName(parameterInfo.Name.ToCamelCase(), "."); + } + + public static string CreateJsObjectLiteral(ParameterApiDescriptionModel[] parameters, int indent = 0) + { + var sb = new StringBuilder(); + + sb.AppendLine("{"); + + foreach (var prm in parameters) + { + sb.AppendLine($"{new string(' ', indent)} '{prm.Name}': {GetParamNameInJsFunc(prm)}"); + } + + sb.Append(new string(' ', indent) + "}"); + + return sb.ToString(); + } + + public static string GenerateJsFuncParameterList(ActionApiDescriptionModel action, string ajaxParametersName) + { + var methodParamNames = action.Parameters.Select(p => p.NameOnMethod).Distinct().ToList(); + methodParamNames.Add(ajaxParametersName); + return methodParamNames.Select(prmName => NormalizeJsVariableName(prmName.ToCamelCase())).JoinAsString(", "); + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/IProxyScriptManager.cs b/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/IProxyScriptManager.cs new file mode 100644 index 0000000000..764fe5aaf0 --- /dev/null +++ b/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/IProxyScriptManager.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.Http.ProxyScripting +{ + public interface IProxyScriptManager + { + string GetScript(ProxyScriptingModel scriptingModel); + } +} \ No newline at end of file diff --git a/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/ProxyScriptManager.cs b/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/ProxyScriptManager.cs new file mode 100644 index 0000000000..d87d20df9e --- /dev/null +++ b/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/ProxyScriptManager.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Http.Modeling; +using Volo.Abp.Http.ProxyScripting.Configuration; +using Volo.Abp.Http.ProxyScripting.Generators; +using Volo.Abp.Json; + +namespace Volo.Abp.Http.ProxyScripting +{ + public class ProxyScriptManager : IProxyScriptManager, ISingletonDependency + { + private readonly IApiDescriptionModelProvider _modelProvider; + private readonly AbpApiProxyScriptingOptions _options; + private readonly IServiceProvider _serviceProvider; + private readonly IJsonSerializer _jsonSerializer; + + private readonly ConcurrentDictionary _cache; + + public ProxyScriptManager( + IApiDescriptionModelProvider modelProvider, + IOptions options, + IServiceProvider serviceProvider, + IJsonSerializer jsonSerializer) + { + _modelProvider = modelProvider; + _options = options.Value; + _serviceProvider = serviceProvider; + _jsonSerializer = jsonSerializer; + + _cache = new ConcurrentDictionary(); + } + + public string GetScript(ProxyScriptingModel scriptingModel) + { + if (scriptingModel.UseCache) + { + return _cache.GetOrAdd(CreateCacheKey(scriptingModel), (key) => CreateScript(scriptingModel)); + } + + return _cache[CreateCacheKey(scriptingModel)] = CreateScript(scriptingModel); + } + + private string CreateScript(ProxyScriptingModel scriptingModel) + { + var apiModel = _modelProvider.CreateApiModel(); + + if (scriptingModel.IsPartialRequest()) + { + apiModel = apiModel.CreateSubModel(scriptingModel.Modules, scriptingModel.Controllers, scriptingModel.Actions); + } + + var generatorType = _options.Generators.GetOrDefault(scriptingModel.GeneratorType); + if (generatorType == null) + { + throw new AbpException($"Could not find a proxy script generator with given name: {scriptingModel.GeneratorType}"); + } + + using (var scope = _serviceProvider.CreateScope()) + { + return scope.ServiceProvider.GetRequiredService(generatorType).As().CreateScript(apiModel); + } + } + + private string CreateCacheKey(ProxyScriptingModel model) + { + return _jsonSerializer.Serialize(model).ToMd5(); + } + } +} diff --git a/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/ProxyScriptingModel.cs b/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/ProxyScriptingModel.cs new file mode 100644 index 0000000000..5ab85a781b --- /dev/null +++ b/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/ProxyScriptingModel.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; + +namespace Volo.Abp.Http.ProxyScripting +{ + public class ProxyScriptingModel + { + public string GeneratorType { get; set; } + + public bool UseCache { get; set; } + + public string[] Modules { get; set; } + + public string[] Controllers { get; set; } + + public string[] Actions { get; set; } + + public IDictionary Properties { get; set; } + + public ProxyScriptingModel(string generatorType, bool useCache = true) + { + GeneratorType = generatorType; + UseCache = useCache; + + Properties = new Dictionary(); + } + + public bool IsPartialRequest() + { + return !(Modules.IsNullOrEmpty() && Controllers.IsNullOrEmpty() && Actions.IsNullOrEmpty()); + } + } +} \ No newline at end of file diff --git a/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ProxyScripting/AbpServiceProxiesController_Tests.cs b/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ProxyScripting/AbpServiceProxiesController_Tests.cs new file mode 100644 index 0000000000..19227045e5 --- /dev/null +++ b/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ProxyScripting/AbpServiceProxiesController_Tests.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; +using Shouldly; +using Xunit; + +namespace Volo.Abp.AspNetCore.Mvc.ProxyScripting +{ + public class AbpServiceProxiesController_Tests : AspNetCoreMvcTestBase + { + [Fact] + public async Task GetAll() + { + var script = await GetResponseAsStringAsync("/AbpServiceProxies/GetAll"); + script.Length.ShouldBeGreaterThan(0); + } + } +}