mirror of https://github.com/abpframework/abp.git
20 changed files with 673 additions and 12 deletions
@ -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()); |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Volo.Abp.Http.ProxyScripting.Configuration |
|||
{ |
|||
public class AbpApiProxyScriptingOptions |
|||
{ |
|||
public IDictionary<string, Type> Generators { get; } |
|||
|
|||
public AbpApiProxyScriptingOptions() |
|||
{ |
|||
Generators = new Dictionary<string, Type>(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
using Volo.Abp.Http.Modeling; |
|||
|
|||
namespace Volo.Abp.Http.ProxyScripting.Generators |
|||
{ |
|||
public interface IProxyScriptGenerator |
|||
{ |
|||
string CreateScript(ApplicationApiDescriptionModel model); |
|||
} |
|||
} |
|||
@ -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 |
|||
{ |
|||
/// <summary>
|
|||
/// "jquery".
|
|||
/// </summary>
|
|||
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(); |
|||
} |
|||
} |
|||
} |
|||
@ -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"; |
|||
} |
|||
} |
|||
@ -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<ParameterApiDescriptionModel> 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<ParameterApiDescriptionModel> 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; |
|||
} |
|||
} |
|||
} |
|||
@ -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<string> ReservedWords = new HashSet<string> { |
|||
"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(", "); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
namespace Volo.Abp.Http.ProxyScripting |
|||
{ |
|||
public interface IProxyScriptManager |
|||
{ |
|||
string GetScript(ProxyScriptingModel scriptingModel); |
|||
} |
|||
} |
|||
@ -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<string, string> _cache; |
|||
|
|||
public ProxyScriptManager( |
|||
IApiDescriptionModelProvider modelProvider, |
|||
IOptions<AbpApiProxyScriptingOptions> options, |
|||
IServiceProvider serviceProvider, |
|||
IJsonSerializer jsonSerializer) |
|||
{ |
|||
_modelProvider = modelProvider; |
|||
_options = options.Value; |
|||
_serviceProvider = serviceProvider; |
|||
_jsonSerializer = jsonSerializer; |
|||
|
|||
_cache = new ConcurrentDictionary<string, string>(); |
|||
} |
|||
|
|||
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<IProxyScriptGenerator>().CreateScript(apiModel); |
|||
} |
|||
} |
|||
|
|||
private string CreateCacheKey(ProxyScriptingModel model) |
|||
{ |
|||
return _jsonSerializer.Serialize(model).ToMd5(); |
|||
} |
|||
} |
|||
} |
|||
@ -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<string, string> Properties { get; set; } |
|||
|
|||
public ProxyScriptingModel(string generatorType, bool useCache = true) |
|||
{ |
|||
GeneratorType = generatorType; |
|||
UseCache = useCache; |
|||
|
|||
Properties = new Dictionary<string, string>(); |
|||
} |
|||
|
|||
public bool IsPartialRequest() |
|||
{ |
|||
return !(Modules.IsNullOrEmpty() && Controllers.IsNullOrEmpty() && Actions.IsNullOrEmpty()); |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue