diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpApiDefinitionController.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpApiDefinitionController.cs index ae72975486..e1c2d1f859 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpApiDefinitionController.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApiExploring/AbpApiDefinitionController.cs @@ -14,9 +14,9 @@ namespace Volo.Abp.AspNetCore.Mvc.ApiExploring } [HttpGet] - public ApplicationApiDescriptionModel Get() + public ApplicationApiDescriptionModel Get(ApplicationApiDescriptionModelRequestDto model) { - return _modelProvider.CreateApiModel(); + return _modelProvider.CreateApiModel(model); } } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs index 3069d72e9d..aefad0f02e 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AspNetCoreApiDescriptionModelProvider.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; +using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; @@ -12,10 +13,13 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Volo.Abp.Application.Services; +using Volo.Abp.AspNetCore.Mvc.ApiExploring; using Volo.Abp.AspNetCore.Mvc.Conventions; using Volo.Abp.AspNetCore.Mvc.Utils; using Volo.Abp.DependencyInjection; using Volo.Abp.Http.Modeling; +using Volo.Abp.Reflection; +using Volo.Abp.Threading; namespace Volo.Abp.AspNetCore.Mvc { @@ -39,7 +43,7 @@ namespace Volo.Abp.AspNetCore.Mvc Logger = NullLogger.Instance; } - public ApplicationApiDescriptionModel CreateApiModel() + public ApplicationApiDescriptionModel CreateApiModel(ApplicationApiDescriptionModelRequestDto input) { //TODO: Can cache the model? @@ -54,19 +58,22 @@ namespace Volo.Abp.AspNetCore.Mvc continue; } - AddApiDescriptionToModel(apiDescription, model); + AddApiDescriptionToModel(apiDescription, model, input); } } return model; } - private void AddApiDescriptionToModel(ApiDescription apiDescription, ApplicationApiDescriptionModel model) + private void AddApiDescriptionToModel( + ApiDescription apiDescription, + ApplicationApiDescriptionModel applicationModel, + ApplicationApiDescriptionModelRequestDto input) { var controllerType = apiDescription.ActionDescriptor.AsControllerActionDescriptor().ControllerTypeInfo.AsType(); var setting = FindSetting(controllerType); - var moduleModel = model.GetOrAddModule(GetRootPath(controllerType, setting)); + var moduleModel = applicationModel.GetOrAddModule(GetRootPath(controllerType, setting)); var controllerModel = moduleModel.GetOrAddController(controllerType.FullName, CalculateControllerName(controllerType, setting), controllerType, _modelOptions.IgnoredInterfaces); @@ -80,6 +87,7 @@ namespace Volo.Abp.AspNetCore.Mvc } Logger.LogDebug($"ActionApiDescriptionModel.Create: {controllerModel.ControllerName}.{uniqueMethodName}"); + var actionModel = controllerModel.AddAction(uniqueMethodName, ActionApiDescriptionModel.Create( uniqueMethodName, method, @@ -88,6 +96,11 @@ namespace Volo.Abp.AspNetCore.Mvc GetSupportedVersions(controllerType, method, setting) )); + if (input.IncludeTypes) + { + AddCustomTypesToModel(applicationModel, method); + } + AddParameterDescriptionsToModel(actionModel, method, apiDescription); } @@ -149,6 +162,68 @@ namespace Volo.Abp.AspNetCore.Mvc return supportedVersions.Select(v => v.ToString()).Distinct().ToList(); } + private void AddCustomTypesToModel(ApplicationApiDescriptionModel applicationModel, MethodInfo method) + { + foreach (var parameterInfo in method.GetParameters()) + { + AddCustomTypesToModel(applicationModel, parameterInfo.ParameterType); + } + + AddCustomTypesToModel(applicationModel, method.ReturnType); + } + + private static void AddCustomTypesToModel(ApplicationApiDescriptionModel applicationModel, [CanBeNull] Type type) + { + if (type == null) + { + return; + } + + type = AsyncHelper.UnwrapTask(type); + + if (type == typeof(object) || + type == typeof(void) || + type == typeof(Enum) || + type == typeof(ValueType) || + TypeHelper.IsPrimitiveExtended(type)) + { + return; + } + + if (TypeHelper.IsEnumerable(type, out var itemType)) + { + AddCustomTypesToModel(applicationModel, itemType); + return; + } + + if (TypeHelper.IsDictionary(type, out var keyType, out var valueType)) + { + AddCustomTypesToModel(applicationModel, keyType); + AddCustomTypesToModel(applicationModel, valueType); + return; + } + + /* TODO: Add interfaces + */ + + var typeName = ModelingTypeHelper.GetFullNameHandlingNullableAndGenerics(type); + + if (applicationModel.Types.ContainsKey(typeName)) + { + return; + } + + var typeModel = TypeApiDescriptionModel.Create(type); + applicationModel.Types[typeName] = typeModel; + + AddCustomTypesToModel(applicationModel, type.BaseType); + + foreach (var propertyInfo in type.GetProperties()) + { + AddCustomTypesToModel(applicationModel, propertyInfo.PropertyType); + } + } + private void AddParameterDescriptionsToModel(ActionApiDescriptionModel actionModel, MethodInfo method, ApiDescription apiDescription) { if (!apiDescription.ParameterDescriptions.Any()) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs index 63fb4f8eee..f5a5a44980 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/AbpCliCoreModule.cs @@ -28,10 +28,11 @@ namespace Volo.Abp.Cli options.Commands["add-package"] = typeof(AddPackageCommand); options.Commands["add-module"] = typeof(AddModuleCommand); options.Commands["login"] = typeof(LoginCommand); - options.Commands["logout"] = typeof(LogoutCommand); + options.Commands["logout"] = typeof(LogoutCommand); + options.Commands["generate-proxy"] = typeof(GenerateProxyCommand); options.Commands["suite"] = typeof(SuiteCommand); options.Commands["switch-to-preview"] = typeof(SwitchNightlyPreviewCommand); - options.Commands["switch-to-stable"] = typeof(SwitchStableCommand); + options.Commands["switch-to-stable"] = typeof(SwitchStableCommand); }); } } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GenerateProxyCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GenerateProxyCommand.cs new file mode 100644 index 0000000000..17fe2d3781 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GenerateProxyCommand.cs @@ -0,0 +1,731 @@ +using System; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Newtonsoft.Json.Linq; +using Volo.Abp.Cli.Args; +using Volo.Abp.Cli.ProjectBuilding; +using Volo.Abp.Cli.ProjectBuilding.Building; +using Volo.Abp.DependencyInjection; +using System.Collections.Generic; +using System.Linq; +using System.Net; + +namespace Volo.Abp.Cli.Commands +{ + public class GenerateProxyCommand : IConsoleCommand, ITransientDependency + { + public static Dictionary> propertyList = new Dictionary>(); + public ILogger Logger { get; set; } + + protected TemplateProjectBuilder TemplateProjectBuilder { get; } + + public GenerateProxyCommand(TemplateProjectBuilder templateProjectBuilder) + { + TemplateProjectBuilder = templateProjectBuilder; + + Logger = NullLogger.Instance; + } + + public async Task ExecuteAsync(CommandLineArgs commandLineArgs) + { + var angularPath = $"angular.json"; + if (!File.Exists(angularPath)) + { + throw new CliUsageException( + "angular.json file not found. You must run this command in angular folder." + + Environment.NewLine + Environment.NewLine + + GetUsageInfo() + ); + } + + var module = commandLineArgs.Options.GetOrNull(Options.Module.Short, Options.Module.Long); + module = module == null ? "app" : module.ToLower(); + + var apiUrl = commandLineArgs.Options.GetOrNull(Options.ApiUrl.Short, Options.ApiUrl.Long); + if (string.IsNullOrWhiteSpace(apiUrl)) + { + var environmentJson = File.ReadAllText("src/environments/environment.ts").Split("export const environment = ")[1].Replace(";", " "); + var environment = JObject.Parse(environmentJson); + apiUrl = environment["apis"]["default"]["url"].ToString(); + } + apiUrl += "/api/abp/api-definition?IncludeTypes=true"; + + var uiFramework = GetUiFramework(commandLineArgs); + + WebClient client = new WebClient(); + string json = client.DownloadString(apiUrl); + //var sr = File.OpenText("api-definition.json"); + //var json = sr.ReadToEnd(); + + Logger.LogInformation("Downloading api definition..."); + Logger.LogInformation("Api Url: " + apiUrl); + + var data = JObject.Parse(json); + + Logger.LogInformation("Modules are combining"); + var moduleList = GetCombinedModules(data, module); + + if (moduleList.Count < 1) + { + throw new CliUsageException( + "Module can not find!" + + Environment.NewLine + Environment.NewLine + + GetUsageInfo() + ); + } + + Logger.LogInformation("Modules and types are creating"); + + foreach (var moduleItem in moduleList) + { + var moduleValue = JObject.Parse(moduleItem.Value); + + var rootPath = moduleItem.Key; + + Logger.LogInformation($"{rootPath} directory is creating"); + + Directory.CreateDirectory($"src/app/{rootPath}/shared/models"); + Directory.CreateDirectory($"src/app/{rootPath}/shared/services"); + + var serviceIndexList = new List(); + var modelIndexList = new List(); + + foreach (var controller in moduleValue.Root.ToList().Select(item => item.First)) + { + var serviceFileText = new StringBuilder(); + + serviceFileText.AppendLine("[firstTypeList]"); + serviceFileText.AppendLine("import { Injectable } from '@angular/core';"); + serviceFileText.AppendLine("import { Observable } from 'rxjs';"); + serviceFileText.AppendLine("[secondTypeList]"); + serviceFileText.AppendLine(""); + serviceFileText.AppendLine("@Injectable({providedIn: 'root'})"); + serviceFileText.AppendLine("export class [controllerName]Service {"); + serviceFileText.AppendLine(" constructor(private restService: RestService) {}"); + serviceFileText.AppendLine(""); + + var firstTypeList = new List(); + var secondTypeList = new List(); + + var controllerName = (string)controller["controllerName"]; + var controllerServiceName = controllerName.PascalToKebabCase() + ".service.ts"; + + foreach (var actionItem in controller["actions"]) + { + var action = actionItem.First; + var actionName = (string)action["uniqueName"]; + + actionName = (char.ToLower(actionName[0]) + actionName.Substring(1)).Replace("Async", "").Replace("Controller", ""); + + var returnValueType = (string)action["returnValue"]["type"]; + + var parameters = action["parameters"]; + var parametersText = new StringBuilder(); + var parametersIndex = 0; + var bodyExtra = ""; + var modelBindingExtra = ""; + var modelBindingExtraList = new List(); + var parameterModel = new List(); + + foreach (var parameter in parameters.OrderBy(p => p["bindingSourceId"])) + { + var bindingSourceId = (string)parameter["bindingSourceId"]; + bindingSourceId = char.ToLower(bindingSourceId[0]) + bindingSourceId.Substring(1); + + var name = (string)parameter["name"]; + var typeSimple = (string)parameter["typeSimple"]; + var typeArray = ((string)parameter["type"]).Split("."); + var type = (typeArray[typeArray.Length - 1]).TrimEnd('>'); + var isOptional = (bool)parameter["isOptional"]; + var defaultValue = (string)parameter["defaultValue"]; + + var modelIndex = CreateType(data, (string)parameter["type"], rootPath, modelIndexList); + + if (!string.IsNullOrWhiteSpace(modelIndex)) + { + modelIndexList.Add(modelIndex); + } + + if (bindingSourceId == "body") + { + bodyExtra = ", body"; + parameterModel = AddParameter(bindingSourceId, type, isOptional, defaultValue, bindingSourceId, parameterModel); + } + else if (bindingSourceId == "path") + { + parameterModel = AddParameter(name, typeSimple, isOptional, defaultValue, bindingSourceId, parameterModel); + } + else if (bindingSourceId == "modelBinding") + { + var parameterNameOnMethod = (string)parameter["nameOnMethod"]; + + var parametersOnMethod = action["parametersOnMethod"]; + foreach (var parameterOnMethod in parametersOnMethod) + { + var parametersOnMethodName = (string)parameterOnMethod["name"]; + if (parametersOnMethodName == parameterNameOnMethod) + { + typeSimple = (string)parameterOnMethod["typeSimple"]; + typeArray = ((string)parameterOnMethod["type"]).Split("."); + type = typeArray[typeArray.Length - 1]; + isOptional = (bool)parameterOnMethod["isOptional"]; + defaultValue = (string)parameterOnMethod["defaultValue"]; + + if (typeSimple == "string" || typeSimple == "boolean" || typeSimple == "number") + { + parameterModel = AddParameter(name, typeSimple, isOptional, defaultValue, bindingSourceId, parameterModel); + } + + modelIndex = CreateType(data, (string)parameterOnMethod["type"], rootPath, modelIndexList); + + if (!string.IsNullOrWhiteSpace(modelIndex)) + { + modelIndexList.Add(modelIndex); + } + } + } + + if (typeSimple != "string" && typeSimple != "boolean" && typeSimple != "number") + { + parametersText.Append($"params = {{}} as {type}"); + modelBindingExtra = ", params"; + if (!string.IsNullOrWhiteSpace(modelIndex)) + { + secondTypeList.Add(type); + } + else { + firstTypeList.Add(type); + } + break; + } + } + } + + if (parameterModel != null && parameterModel.Count > 0) + { + foreach (var parameterItem in parameterModel.OrderBy(p => p.DisplayOrder)) + { + var parameterItemModelName = parameterItem.Type.PascalToKebabCase() + ".ts"; + var parameterItemModelPath = $"src/app/{rootPath}/shared/models/{parameterItemModelName}"; + if (parameterItem.BindingSourceId == "body" && !File.Exists(parameterItemModelPath)) + { + parameterItem.Type = "any"; + } + + parametersIndex++; + + if (parametersIndex > 1) + { + parametersText.Append(", "); + } + + parametersText.Append(parameterItem.Name + (parameterItem.IsOptional ? "?" : "") + ": " + parameterItem.Type + (parameterItem.Value != null ? (" = " + (string.IsNullOrWhiteSpace(parameterItem.Value) ? "''" : parameterItem.Value)) : "")); + + if (parameterItem.BindingSourceId == "modelBinding") + { + modelBindingExtraList.Add(parameterItem.Name); + } + else if (parameterItem.BindingSourceId == "body" && File.Exists(parameterItemModelPath)) + { + secondTypeList.Add(parameterItem.Type); + } + } + } + + if (returnValueType != null) + { + if (returnValueType.IndexOf('<') > -1) + { + var firstTypeArray = returnValueType.Split("<")[0].Split("."); + var firstType = firstTypeArray[firstTypeArray.Length - 1]; + + var secondTypeArray = returnValueType.Split("<")[1].Split("."); + var secondType = secondTypeArray[secondTypeArray.Length - 1].TrimEnd('>'); + + var secondTypeModelName = secondType.PascalToKebabCase() + ".ts"; + var secondTypeModelPath = $"src/app/{rootPath}/shared/models/{secondTypeModelName}"; + if (firstType == "List" && !File.Exists(secondTypeModelPath)) + { + secondType = "any"; + } + + serviceFileText.AppendLine( + firstType == "List" + ? $" {actionName}({parametersText}): Observable<{secondType}[]> {{" + : $" {actionName}({parametersText}): Observable<{firstType}<{secondType}>> {{"); + + if (firstType != "List") + { + firstTypeList.Add(firstType); + } + + if (secondType != "any") + { + secondTypeList.Add(secondType); + } + } + else + { + var typeArray = returnValueType.Split("."); + var type = typeArray[typeArray.Length - 1].TrimEnd('>'); + + type = type switch + { + "Void" => "void", + "String" => "string", + "IActionResult" => "void", + "ActionResult" => "void", + _ => type + }; + + serviceFileText.AppendLine( + $" {actionName}({parametersText}): Observable<{type}> {{"); + + if (type != "void" && type != "string") + { + secondTypeList.Add(type); + } + } + + var modelIndex = CreateType(data, returnValueType, rootPath, modelIndexList); + + if (!string.IsNullOrWhiteSpace(modelIndex)) + { + modelIndexList.Add(modelIndex); + } + } + + if (modelBindingExtraList != null && modelBindingExtraList.Count > 0) + { + modelBindingExtra = ", params: { " + string.Join(", ", modelBindingExtraList.ToArray()) + " }"; + } + + var url = ((string)action["url"]).Replace("/{", "/${"); + var httpMethod = (string)action["httpMethod"]; + + serviceFileText.AppendLine( + url.Contains("${") + ? $" return this.restService.request({{ url: `/{url}`, method: '{httpMethod}'{bodyExtra}{modelBindingExtra} }});" + : $" return this.restService.request({{ url: '/{url}', method: '{httpMethod}'{bodyExtra}{modelBindingExtra} }});"); + + + serviceFileText.AppendLine(" }"); + } + + serviceIndexList.Add(controllerServiceName.Replace(".ts", "")); + + if (firstTypeList != null && firstTypeList.Count > 0) + { + var firstTypeListDistinct = ", " + string.Join(", ", firstTypeList.Where(p => p != "void").Distinct().ToArray()); + serviceFileText.Replace("[firstTypeList]", + $"import {{ RestService {firstTypeListDistinct}}} from '@abp/ng.core';"); + } + else + { + serviceFileText.Replace("[firstTypeList]", + $"import {{ RestService }} from '@abp/ng.core';"); + } + + if (secondTypeList != null && secondTypeList.Count > 0) + { + var secondTypeListDistinct = string.Join(", ", secondTypeList.Where(p => p != "void").Distinct().ToArray()); + serviceFileText.Replace("[secondTypeList]", + $"import {{{secondTypeListDistinct}}} from '../models';"); + } + else + { + serviceFileText.Replace("[secondTypeList]", ""); + } + + serviceFileText.AppendLine("}"); + + serviceFileText.Replace("[controllerName]", controllerName); + File.WriteAllText($"src/app/{rootPath}/shared/services/{controllerServiceName}", serviceFileText.ToString()); + } + + var serviceIndexFileText = new StringBuilder(); + + foreach (var serviceIndexItem in serviceIndexList.Distinct()) + { + serviceIndexFileText.AppendLine($"export * from './{serviceIndexItem}';"); + } + + File.WriteAllText($"src/app/{rootPath}/shared/services/index.ts", serviceIndexFileText.ToString()); + + var modelIndexFileText = new StringBuilder(); + + foreach (var modelIndexItem in modelIndexList.Distinct()) + { + modelIndexFileText.AppendLine($"export * from './{modelIndexItem}';"); + } + + File.WriteAllText($"src/app/{rootPath}/shared/models/index.ts", modelIndexFileText.ToString()); + } + + Logger.LogInformation("Completed!"); + } + + private Dictionary GetCombinedModules(JToken data, string module) + { + var moduleList = new Dictionary(); + + foreach (var moduleItem in data["modules"]) + { + var rootPath = ((string)moduleItem.First["rootPath"]).ToLower(); + + if (moduleList.Any(p => p.Key == rootPath)) + { + var value = moduleList[rootPath]; + + moduleList[rootPath] = value.TrimEnd('}') + "," + moduleItem.First["controllers"].ToString().TrimStart('{'); + } + else + { + moduleList.Add(rootPath, moduleItem.First["controllers"].ToString()); + } + } + + if (module != "all") + { + moduleList = moduleList.Where(p => p.Key.ToLower() == module).ToDictionary(p => p.Key, s => s.Value); + } + + return moduleList; + } + + private static string CreateType(JObject data, string returnValueType, string rootPath, List modelIndexList) + { + var type = data["types"][returnValueType]; + + if (type == null) + { + return null; + } + + if (returnValueType.StartsWith("Volo.Abp.Application.Dtos") + || returnValueType.StartsWith("System.Collections") + || returnValueType == "System.String" + || returnValueType == "System.Void" + || returnValueType.Contains("System.Net.HttpStatusCode?") + || returnValueType.Contains("IActionResult") + || returnValueType.Contains("ActionResult") + || returnValueType.Contains("IStringValueType") + || returnValueType.Contains("IValueValidator") + ) + { + return null; + } + + var typeNameSplit = returnValueType.Split("."); + var typeName = typeNameSplit[typeNameSplit.Length - 1]; + + if (typeName.Contains("HttpStatusCode")) + { + + } + + var typeModelName = typeName.Replace("<", "").Replace(">", "").Replace("?","").PascalToKebabCase() + ".ts"; + + var path = $"src/app/{rootPath}/shared/models/{typeModelName}"; + + var modelFileText = new StringBuilder(); + + var baseType = (string)type["baseType"]; + var extends = ""; + var customBaseTypeName = ""; + var baseTypeName = ""; + + if (!string.IsNullOrWhiteSpace(baseType)) + { + var baseTypeSplit = baseType.Split("."); + baseTypeName = baseTypeSplit[baseTypeSplit.Length - 1].Replace("<", "").Replace(">", ""); + var baseTypeKebabCase = "./" + baseTypeName.PascalToKebabCase(); + + if (baseType != "System.Enum") + { + if (baseType.Contains("Volo.Abp.Application.Dtos")) + { + baseTypeKebabCase = "@abp/ng.core"; + + baseTypeName = baseType.Split("Volo.Abp.Application.Dtos")[1].Split("<")[0].TrimStart('.'); + customBaseTypeName = baseType.Split("Volo.Abp.Application.Dtos")[1].Replace("System.Guid", "string").TrimStart('.'); + } + + if (baseTypeName.Contains("guid") || baseTypeName.Contains("Guid")) + { + baseTypeName = "string"; + } + + modelFileText.AppendLine($"import {{ {baseTypeName} }} from '{baseTypeKebabCase}';"); + extends = "extends " + (!string.IsNullOrWhiteSpace(customBaseTypeName) ? customBaseTypeName : baseTypeName); + + var modelIndex = CreateType(data, baseType, rootPath, modelIndexList); + if (!string.IsNullOrWhiteSpace(modelIndex)) + { + modelIndexList.Add(modelIndex); + } + } + } + + if (baseType == "System.Enum" && (bool)type["isEnum"]) + { + modelFileText.AppendLine($"export enum {typeName} {{"); + + var enumNameList = type["enumNames"].ToArray(); + var enumValueList = type["enumValues"].ToArray(); + + for (var i = 0; i < enumNameList.Length; i++) + { + modelFileText.AppendLine($" {enumNameList[i]} = {enumValueList[i]},"); + } + + modelFileText.AppendLine("}"); + } + else + { + modelFileText.AppendLine(""); + modelFileText.AppendLine($"export class {typeName} {extends} {{"); + + foreach (var property in type["properties"]) + { + var propertyName = (string)property["name"]; + propertyName = (char.ToLower(propertyName[0]) + propertyName.Substring(1)); + var typeSimple = (string)property["typeSimple"]; + + var modelIndex = CreateType(data, (string)property["type"], rootPath, modelIndexList); + + if (typeSimple.IndexOf("[") > -1 && typeSimple.IndexOf("]") > -1) + { + typeSimple = typeSimple.Replace("[", "").Replace("]", "") + "[]"; + } + + if (typeSimple.StartsWith("Volo.Abp")) + { + var typeSimpleSplit = typeSimple.Split("."); + typeSimple = typeSimpleSplit[typeSimpleSplit.Length - 1]; + } + + if (typeSimple.StartsWith("System.Object")) + { + typeSimple = typeSimple.Replace("System.Object", "object"); + } + + if (typeSimple.Contains("?")) + { + typeSimple = typeSimple.Replace("?", ""); + propertyName += "?"; + } + + if (typeSimple != "boolean" + && typeSimple != "string" + && typeSimple != "number" + && typeSimple != "boolean[]" + && typeSimple != "string[]" + && typeSimple != "number[]" + ) + { + var typeSimpleModelName = typeSimple.PascalToKebabCase() + ".ts"; + var modelPath = $"src/app/{rootPath}/shared/models/{typeSimpleModelName}"; + if (!File.Exists(modelPath)) + { + typeSimple = "any" + (typeSimple.Contains("[]") ? "[]" : ""); + } + } + + if (propertyList.Any(p => p.Key == baseTypeName && p.Value.Any(q => q.Key == propertyName && q.Value == typeSimple))) + { + continue; + } + + if (!string.IsNullOrWhiteSpace(modelIndex)) + { + var propertyTypeSplit = ((string)property["type"]).Split("."); + var propertyType = propertyTypeSplit[propertyTypeSplit.Length - 1]; + modelFileText.Insert(0, ""); + modelFileText.Insert(0, $"import {{ {propertyType} }} from '../models';"); + modelFileText.Insert(0, ""); + modelIndexList.Add(modelIndex); + } + + if (propertyList.Any(p => p.Key == typeName && !p.Value.Any(q => q.Key == propertyName))) + { + propertyList[typeName].Add(propertyName, typeSimple); + } + else if (!propertyList.Any(p => p.Key == typeName)) + { + var newProperty = new Dictionary(); + newProperty.Add(propertyName, typeSimple); + propertyList.Add(typeName, newProperty); + } + + modelFileText.AppendLine($" {propertyName}: {typeSimple};"); + } + + modelFileText.AppendLine(""); + + modelFileText.AppendLine($" constructor(initialValues: Partial<{typeName}> = {{}}) {{"); + + if (!string.IsNullOrWhiteSpace(baseType)) + { + modelFileText.AppendLine(" super(initialValues);"); + modelFileText.AppendLine(" }"); + } + else + { + modelFileText.AppendLine(" if (initialValues) {"); + modelFileText.AppendLine(" for (const key in initialValues) {"); + modelFileText.AppendLine(" if (initialValues.hasOwnProperty(key)) {"); + modelFileText.AppendLine(" this[key] = initialValues[key];"); + + modelFileText.AppendLine(" }"); + modelFileText.AppendLine(" }"); + modelFileText.AppendLine(" }"); + modelFileText.AppendLine(" }"); + } + + modelFileText.AppendLine("}"); + } + + File.WriteAllText($"src/app/{rootPath}/shared/models/{typeModelName}", modelFileText.ToString()); + + return typeModelName.Replace(".ts", ""); + } + + private static List AddParameter(string parameterName, string type, bool parameterIsOptional, string parameterDefaultValue, string bindingSourceId, List parameterModel) + { + if (parameterDefaultValue != "null") + { + parameterModel.Add(new ParameterModel + { + DisplayOrder = 3, + Name = parameterName, + Type = type, + Value = parameterDefaultValue, + BindingSourceId = bindingSourceId + }); + } + else if (parameterDefaultValue == "null" && parameterIsOptional) + { + parameterModel.Add(new ParameterModel + { + DisplayOrder = 2, + Name = parameterName, + IsOptional = true, + Type = type, + BindingSourceId = bindingSourceId + }); + } + else + { + parameterModel.Add(new ParameterModel + { + DisplayOrder = 1, + Name = parameterName, + Type = type, + BindingSourceId = bindingSourceId + }); + } + + return parameterModel; + } + + public string GetUsageInfo() + { + var sb = new StringBuilder(); + + sb.AppendLine(""); + sb.AppendLine("Usage:"); + sb.AppendLine(""); + sb.AppendLine(" abp generate-proxy [options]"); + sb.AppendLine(""); + sb.AppendLine("Options:"); + sb.AppendLine(""); + sb.AppendLine("-a|--apiUrl (default: environment.ts>apis>default>url)"); + sb.AppendLine("-u|--ui (default: angular)"); + sb.AppendLine("-m|--module (default: app)"); + sb.AppendLine(""); + sb.AppendLine("Examples:"); + sb.AppendLine(""); + sb.AppendLine(" abp generate-proxy --apiUrl https://www.volosoft.com"); + sb.AppendLine(""); + sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI"); + + return sb.ToString(); + } + + public string GetShortDescription() + { + return "Generates typescript service proxies and DTOs"; + } + + private UiFramework GetUiFramework(CommandLineArgs commandLineArgs) + { + var optionValue = commandLineArgs.Options.GetOrNull(Options.UiFramework.Short, Options.UiFramework.Long); + switch (optionValue) + { + case "none": + return UiFramework.None; + case "mvc": + return UiFramework.Mvc; + case "angular": + return UiFramework.Angular; + default: + return UiFramework.Angular; + } + } + + public static class Options + { + public static class Module + { + public const string Short = "m"; + public const string Long = "module"; + } + + public static class ApiUrl + { + public const string Short = "a"; + public const string Long = "apiUrl"; + } + + public static class UiFramework + { + public const string Short = "u"; + public const string Long = "ui"; + } + } + } + + public static class StringExtensions + { + public static string PascalToKebabCase(this string value) + { + if (string.IsNullOrEmpty(value)) + { + return value; + } + + return Regex.Replace( + value, + "(? + diff --git a/framework/src/Volo.Abp.Core/System/AbpTypeExtensions.cs b/framework/src/Volo.Abp.Core/System/AbpTypeExtensions.cs index a930096404..e7c600eff6 100644 --- a/framework/src/Volo.Abp.Core/System/AbpTypeExtensions.cs +++ b/framework/src/Volo.Abp.Core/System/AbpTypeExtensions.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using JetBrains.Annotations; +using Volo.Abp; namespace System { @@ -17,8 +18,10 @@ namespace System /// Internally uses . /// /// Target type (as reverse). - public static bool IsAssignableTo(this Type type) + public static bool IsAssignableTo([NotNull] this Type type) { + Check.NotNull(type, nameof(type)); + return type.IsAssignableTo(typeof(TTarget)); } @@ -30,8 +33,11 @@ namespace System /// /// this type /// Target type - public static bool IsAssignableTo(this Type type, Type targetType) + public static bool IsAssignableTo([NotNull] this Type type, [NotNull] Type targetType) { + Check.NotNull(type, nameof(type)); + Check.NotNull(targetType, nameof(targetType)); + return targetType.IsAssignableFrom(type); } @@ -40,8 +46,10 @@ namespace System /// /// The type to get its base classes. /// True, to include the standard type in the returned array. - public static Type[] GetBaseClasses(this Type type, bool includeObject = true) + public static Type[] GetBaseClasses([NotNull] this Type type, bool includeObject = true) { + Check.NotNull(type, nameof(type)); + var types = new List(); AddTypeAndBaseTypesRecursively(types, type.BaseType, includeObject); return types.ToArray(); @@ -52,6 +60,8 @@ namespace System [CanBeNull] Type type, bool includeObject) { + Check.NotNull(types, nameof(types)); + if (type == null) { return; diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs index a708ecc10d..a9f21c5a0d 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs @@ -1,4 +1,7 @@ using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using System.Reflection; @@ -54,6 +57,53 @@ namespace Volo.Abp.Reflection return t; } + public static bool IsEnumerable(Type type, out Type itemType, bool includePrimitives = true) + { + if (!includePrimitives && IsPrimitiveExtended(type)) + { + itemType = null; + return false; + } + + var enumerableTypes = ReflectionHelper.GetImplementedGenericTypes(type, typeof(IEnumerable<>)); + if (enumerableTypes.Count == 1) + { + itemType = enumerableTypes[0].GenericTypeArguments[0]; + return true; + } + + if (typeof(IEnumerable).IsAssignableFrom(type)) + { + itemType = typeof(object); + return true; + } + + itemType = null; + return false; + } + + public static bool IsDictionary(Type type, out Type keyType, out Type valueType) + { + var enumerableTypes = ReflectionHelper.GetImplementedGenericTypes(type, typeof(IDictionary<,>)); + if (enumerableTypes.Count == 1) + { + keyType = enumerableTypes[0].GenericTypeArguments[0]; + valueType = enumerableTypes[0].GenericTypeArguments[2]; + return true; + } + + if (typeof(IDictionary).IsAssignableFrom(type)) + { + keyType = typeof(object); + valueType = typeof(object); + return true; + } + + keyType = null; + valueType = null; + return false; + } + private static bool IsPrimitiveExtendedInternal(Type type, bool includeEnums) { if (type.IsPrimitive) diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Threading/AsyncHelper.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Threading/AsyncHelper.cs index dc678b8fd4..65e411d038 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Threading/AsyncHelper.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Threading/AsyncHelper.cs @@ -27,6 +27,11 @@ namespace Volo.Abp.Threading return type == typeof(Task) || (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Task<>)); } + public static bool IsTaskOfT([NotNull] this Type type) + { + return type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Task<>); + } + /// /// Returns void if given type is Task. /// Return T, if given type is Task{T}. @@ -41,7 +46,7 @@ namespace Volo.Abp.Threading return typeof(void); } - if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Task<>)) + if (type.IsTaskOfT()) { return type.GenericTypeArguments[0]; } diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/ApiDescriptionFinder.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/ApiDescriptionFinder.cs index 38e6878bed..2e8eb41844 100644 --- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/ApiDescriptionFinder.cs +++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/ApiDescriptionFinder.cs @@ -56,8 +56,8 @@ namespace Volo.Abp.Http.Client.DynamicProxying var found = true; for (int i = 0; i < methodParameters.Length; i++) - { - if (!TypeMatches(action.ParametersOnMethod[i], methodParameters[i])) + { + if (!TypeMatches(action.ParametersOnMethod[i], methodParameters[i])) { found = false; break; diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ApplicationApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ApplicationApiDescriptionModel.cs index a7def10625..fad94b3745 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ApplicationApiDescriptionModel.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ApplicationApiDescriptionModel.cs @@ -10,6 +10,8 @@ namespace Volo.Abp.Http.Modeling { public IDictionary Modules { get; set; } + public IDictionary Types { get; set; } + private ApplicationApiDescriptionModel() { @@ -19,8 +21,8 @@ namespace Volo.Abp.Http.Modeling { return new ApplicationApiDescriptionModel { - //TODO: Why ConcurrentDictionary? - Modules = new ConcurrentDictionary() + Modules = new ConcurrentDictionary(), //TODO: Why ConcurrentDictionary? + Types = new Dictionary() }; } diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ApplicationApiDescriptionModelRequestDto.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ApplicationApiDescriptionModelRequestDto.cs new file mode 100644 index 0000000000..bb50fc0d96 --- /dev/null +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ApplicationApiDescriptionModelRequestDto.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.Http.Modeling +{ + public class ApplicationApiDescriptionModelRequestDto + { + public bool IncludeTypes { get; set; } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerApiDescriptionModel.cs index 2bd9a301ab..db5a5eb37a 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerApiDescriptionModel.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerApiDescriptionModel.cs @@ -10,7 +10,7 @@ namespace Volo.Abp.Http.Modeling { public string ControllerName { get; set; } - public string TypeAsString { get; set; } + public string Type { get; set; } public List Interfaces { get; set; } @@ -26,7 +26,7 @@ namespace Volo.Abp.Http.Modeling return new ControllerApiDescriptionModel { ControllerName = controllerName, - TypeAsString = type.GetFullNameWithAssemblyName(), + Type = type.FullName, Actions = new Dictionary(), Interfaces = type .GetInterfaces() @@ -52,7 +52,7 @@ namespace Volo.Abp.Http.Modeling { var subModel = new ControllerApiDescriptionModel { - TypeAsString = TypeAsString, + Type = Type, Interfaces = Interfaces, ControllerName = ControllerName, Actions = new Dictionary() @@ -71,7 +71,7 @@ namespace Volo.Abp.Http.Modeling public bool Implements(Type interfaceType) { - return Interfaces.Any(i => i.TypeAsString == interfaceType.GetFullNameWithAssemblyName()); + return Interfaces.Any(i => i.Type == interfaceType.FullName); } public override string ToString() diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerInterfaceApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerInterfaceApiDescriptionModel.cs index 3605cf675b..04b5ea1bc9 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerInterfaceApiDescriptionModel.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ControllerInterfaceApiDescriptionModel.cs @@ -5,7 +5,7 @@ namespace Volo.Abp.Http.Modeling [Serializable] public class ControllerInterfaceApiDescriptionModel { - public string TypeAsString { get; set; } + public string Type { get; set; } private ControllerInterfaceApiDescriptionModel() { @@ -16,7 +16,7 @@ namespace Volo.Abp.Http.Modeling { return new ControllerInterfaceApiDescriptionModel { - TypeAsString = type.GetFullNameWithAssemblyName() + Type = type.FullName }; } } diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/IApiDescriptionModelProvider.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/IApiDescriptionModelProvider.cs index 800dc504db..6f8803ddb6 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/IApiDescriptionModelProvider.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/IApiDescriptionModelProvider.cs @@ -2,6 +2,6 @@ namespace Volo.Abp.Http.Modeling { public interface IApiDescriptionModelProvider { - ApplicationApiDescriptionModel CreateApiModel(); + ApplicationApiDescriptionModel CreateApiModel(ApplicationApiDescriptionModelRequestDto input); } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/MethodParameterApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/MethodParameterApiDescriptionModel.cs index 61ffc516f6..4199e29ec0 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/MethodParameterApiDescriptionModel.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/MethodParameterApiDescriptionModel.cs @@ -10,13 +10,17 @@ namespace Volo.Abp.Http.Modeling public string TypeAsString { get; set; } + public string Type { get; set; } + + public string TypeSimple { get; set; } + public bool IsOptional { get; set; } public object DefaultValue { get; set; } private MethodParameterApiDescriptionModel() { - + } public static MethodParameterApiDescriptionModel Create(ParameterInfo parameterInfo) @@ -25,6 +29,8 @@ namespace Volo.Abp.Http.Modeling { Name = parameterInfo.Name, TypeAsString = parameterInfo.ParameterType.GetFullNameWithAssemblyName(), + Type = parameterInfo.ParameterType != null ? ModelingTypeHelper.GetFullNameHandlingNullableAndGenerics(parameterInfo.ParameterType) : null, + TypeSimple = parameterInfo.ParameterType != null ? ModelingTypeHelper.GetSimplifiedName(parameterInfo.ParameterType) : null, IsOptional = parameterInfo.IsOptional, DefaultValue = parameterInfo.HasDefaultValue ? parameterInfo.DefaultValue : null }; diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ModelingTypeHelper.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ModelingTypeHelper.cs new file mode 100644 index 0000000000..2d4b2bac85 --- /dev/null +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ModelingTypeHelper.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using JetBrains.Annotations; + +namespace Volo.Abp.Http.Modeling +{ + public static class ModelingTypeHelper + { + public static string GetFullNameHandlingNullableAndGenerics([NotNull] Type type) + { + Check.NotNull(type, nameof(type)); + + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) + { + return type.GenericTypeArguments[0].FullName + "?"; + } + + if (type.IsGenericType) + { + var genericType = type.GetGenericTypeDefinition(); + return $"{genericType.FullName.Left(genericType.FullName.IndexOf('`'))}<{type.GenericTypeArguments.Select(GetFullNameHandlingNullableAndGenerics).JoinAsString(",")}>"; + } + + return type.FullName; + } + + public static string GetSimplifiedName([NotNull] Type type) + { + Check.NotNull(type, nameof(type)); + + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) + { + return GetSimplifiedName(type.GenericTypeArguments[0]) + "?"; + } + + if (type.IsGenericType) + { + var genericType = type.GetGenericTypeDefinition(); + return $"{genericType.FullName.Left(genericType.FullName.IndexOf('`'))}<{type.GenericTypeArguments.Select(GetSimplifiedName).JoinAsString(",")}>"; + } + + if (type == typeof(string)) + { + return "string"; + } + else if (type == typeof(int)) + { + return "number"; + } + else if (type == typeof(long)) + { + return "number"; + } + else if (type == typeof(bool)) + { + return "boolean"; + } + else if (type == typeof(char)) + { + return "string"; + } + else if (type == typeof(double)) + { + return "number"; + } + else if (type == typeof(float)) + { + return "number"; + } + else if (type == typeof(decimal)) + { + return "number"; + } + else if (type == typeof(DateTime)) + { + return "string"; + } + else if (type == typeof(DateTimeOffset)) + { + return "string"; + } + else if (type == typeof(TimeSpan)) + { + return "string"; + } + else if (type == typeof(Guid)) + { + return "string"; + } + else if (type == typeof(byte)) + { + return "number"; + } + else if (type == typeof(sbyte)) + { + return "number"; + } + else if (type == typeof(short)) + { + return "number"; + } + else if (type == typeof(ushort)) + { + return "number"; + } + else if (type == typeof(uint)) + { + return "number"; + } + else if (type == typeof(ulong)) + { + return "number"; + } + else if (type == typeof(IntPtr)) + { + return "number"; + } + else if (type == typeof(UIntPtr)) + { + return "number"; + } + + return type.FullName; + } + } +} diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ParameterApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ParameterApiDescriptionModel.cs index 257f06d3db..0be791053c 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ParameterApiDescriptionModel.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ParameterApiDescriptionModel.cs @@ -9,7 +9,9 @@ namespace Volo.Abp.Http.Modeling public string Name { get; set; } - public string TypeAsString { get; set; } + public string Type { get; set; } + + public string TypeSimple { get; set; } public bool IsOptional { get; set; } @@ -32,7 +34,8 @@ namespace Volo.Abp.Http.Modeling { Name = name, NameOnMethod = nameOnMethod, - TypeAsString = type?.GetFullNameWithAssemblyName(), + Type = type != null ? ModelingTypeHelper.GetFullNameHandlingNullableAndGenerics(type) : null, + TypeSimple = type != null ? ModelingTypeHelper.GetSimplifiedName(type) : null, IsOptional = isOptional, DefaultValue = defaultValue, ConstraintTypes = constraintTypes, diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/PropertyApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/PropertyApiDescriptionModel.cs new file mode 100644 index 0000000000..ca8af7f22a --- /dev/null +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/PropertyApiDescriptionModel.cs @@ -0,0 +1,46 @@ +using System; +using System.Reflection; +using Volo.Abp.Reflection; + +namespace Volo.Abp.Http.Modeling +{ + [Serializable] + public class PropertyApiDescriptionModel + { + public string Name { get; set; } + + public string Type { get; set; } + + public string TypeSimple { get; set; } + + //TODO: Validation rules for this property + public static PropertyApiDescriptionModel Create(PropertyInfo propertyInfo) + { + string typeName; + string simpleTypeName; + + if (TypeHelper.IsEnumerable(propertyInfo.PropertyType, out var itemType, includePrimitives: false)) + { + typeName = $"[{ModelingTypeHelper.GetFullNameHandlingNullableAndGenerics(itemType)}]"; + simpleTypeName = $"[{ModelingTypeHelper.GetSimplifiedName(itemType)}]"; + } + else if (TypeHelper.IsDictionary(propertyInfo.PropertyType, out var keyType, out var valueType)) + { + typeName = $"{{{ModelingTypeHelper.GetFullNameHandlingNullableAndGenerics(keyType)}:{ModelingTypeHelper.GetFullNameHandlingNullableAndGenerics(valueType)}}}"; + simpleTypeName = $"{{{ModelingTypeHelper.GetSimplifiedName(keyType)}:{ModelingTypeHelper.GetSimplifiedName(valueType)}}}"; + } + else + { + typeName = ModelingTypeHelper.GetFullNameHandlingNullableAndGenerics(propertyInfo.PropertyType); + simpleTypeName = ModelingTypeHelper.GetSimplifiedName(propertyInfo.PropertyType); + } + + return new PropertyApiDescriptionModel + { + Name = propertyInfo.Name, + Type = typeName, + TypeSimple = simpleTypeName + }; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ReturnValueApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ReturnValueApiDescriptionModel.cs index 001bd1b9c9..d6532be6d4 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ReturnValueApiDescriptionModel.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/ReturnValueApiDescriptionModel.cs @@ -6,7 +6,9 @@ namespace Volo.Abp.Http.Modeling [Serializable] public class ReturnValueApiDescriptionModel { - public string TypeAsString { get; set; } + public string Type { get; set; } + + public string TypeSimple { get; set; } private ReturnValueApiDescriptionModel() { @@ -15,9 +17,12 @@ namespace Volo.Abp.Http.Modeling public static ReturnValueApiDescriptionModel Create(Type type) { + var unwrappedType = AsyncHelper.UnwrapTask(type); + return new ReturnValueApiDescriptionModel { - TypeAsString = AsyncHelper.UnwrapTask(type).GetFullNameWithAssemblyName() + Type = ModelingTypeHelper.GetFullNameHandlingNullableAndGenerics(unwrappedType), + TypeSimple = ModelingTypeHelper.GetSimplifiedName(unwrappedType) }; } } diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/TypeApiDescriptionModel.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/TypeApiDescriptionModel.cs new file mode 100644 index 0000000000..fcc4b034ea --- /dev/null +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/Modeling/TypeApiDescriptionModel.cs @@ -0,0 +1,54 @@ +using System; +using System.Linq; + +namespace Volo.Abp.Http.Modeling +{ + [Serializable] + public class TypeApiDescriptionModel + { + public string BaseType { get; set; } + + public bool IsEnum { get; set; } + + public string[] EnumNames { get; set; } + + public object[] EnumValues { get; set; } + + public PropertyApiDescriptionModel[] Properties { get; set; } + + private TypeApiDescriptionModel() + { + + } + + public static TypeApiDescriptionModel Create(Type type) + { + var baseType = type.BaseType; + if (baseType == typeof(object)) + { + baseType = null; + } + + var typeModel = new TypeApiDescriptionModel + { + IsEnum = type.IsEnum, + BaseType = baseType != null ? ModelingTypeHelper.GetFullNameHandlingNullableAndGenerics(baseType) : null + }; + + if (typeModel.IsEnum) + { + typeModel.EnumNames = type.GetEnumNames(); + typeModel.EnumValues = type.GetEnumValues().Cast().ToArray(); + } + else + { + typeModel.Properties = type + .GetProperties() + .Select(PropertyApiDescriptionModel.Create) + .ToArray(); + } + + return typeModel; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/JQuery/JQueryProxyScriptGenerator.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/JQuery/JQueryProxyScriptGenerator.cs index b267c26296..c9a10c5432 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/JQuery/JQueryProxyScriptGenerator.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/Generators/JQuery/JQueryProxyScriptGenerator.cs @@ -56,7 +56,7 @@ namespace Volo.Abp.Http.ProxyScripting.Generators.JQuery private static void AddControllerScript(StringBuilder script, ControllerApiDescriptionModel controller) { - var controllerName = GetNormalizedTypeName(controller.TypeAsString); + var controllerName = GetNormalizedTypeName(controller.Type); script.AppendLine($" // controller {controllerName}"); script.AppendLine(); @@ -129,7 +129,7 @@ namespace Volo.Abp.Http.ProxyScripting.Generators.JQuery script.AppendLine(" url: abp.appPath + '" + ProxyScriptingHelper.GenerateUrlWithParameters(action) + "',"); script.Append(" type: '" + httpMethod + "'"); - if (action.ReturnValue.TypeAsString == typeof(void).GetFullNameWithAssemblyName()) + if (action.ReturnValue.Type == typeof(void).FullName) { script.AppendLine(","); script.Append(" dataType: null"); diff --git a/framework/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/ProxyScriptManager.cs b/framework/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/ProxyScriptManager.cs index a3df2c70a2..7ac3651ad9 100644 --- a/framework/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/ProxyScriptManager.cs +++ b/framework/src/Volo.Abp.Http/Volo/Abp/Http/ProxyScripting/ProxyScriptManager.cs @@ -49,7 +49,7 @@ namespace Volo.Abp.Http.ProxyScripting private string CreateScript(ProxyScriptingModel scriptingModel) { - var apiModel = _modelProvider.CreateApiModel(); + var apiModel = _modelProvider.CreateApiModel(new ApplicationApiDescriptionModelRequestDto {IncludeTypes = false}); if (scriptingModel.IsPartialRequest()) { diff --git a/npm/ng-packs/packages/core/src/lib/models/dtos.ts b/npm/ng-packs/packages/core/src/lib/models/dtos.ts new file mode 100644 index 0000000000..21bf136aa5 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/models/dtos.ts @@ -0,0 +1,124 @@ +export class ListResultDto { + items?: T[]; + + constructor(initialValues: Partial> = {}) { + for (const key in initialValues) { + if (initialValues.hasOwnProperty(key)) { + this[key] = initialValues[key]; + } + } + } +} + +export class PagedResultDto extends ListResultDto { + totalCount?: number; + + constructor(initialValues: Partial> = {}) { + super(initialValues); + } +} + +export class LimitedResultRequestDto { + maxResultCount = 10; + + constructor(initialValues: Partial = {}) { + for (const key in initialValues) { + if (initialValues.hasOwnProperty(key)) { + this[key] = initialValues[key]; + } + } + } +} + +export class PagedResultRequestDto extends LimitedResultRequestDto { + skipCount?: number; + + constructor(initialValues: Partial = {}) { + super(initialValues); + } +} + +export class PagedAndSortedResultRequestDto extends PagedResultRequestDto { + sorting?: string; + + constructor(initialValues: Partial = {}) { + super(initialValues); + } +} + +export class EntityDto { + id?: TKey; + + constructor(initialValues: Partial> = {}) { + for (const key in initialValues) { + if (initialValues.hasOwnProperty(key)) { + this[key] = initialValues[key]; + } + } + } +} + +export class CreationAuditedEntityDto extends EntityDto { + creationTime?: string | Date; + creatorId?: string; + + constructor(initialValues: Partial> = {}) { + super(initialValues); + } +} + +export class CreationAuditedEntityWithUserDto< + TUserDto, + TPrimaryKey = string +> extends CreationAuditedEntityDto { + creator?: TUserDto; + + constructor( + initialValues: Partial> = {}, + ) { + super(initialValues); + } +} + +export class AuditedEntityDto extends CreationAuditedEntityDto { + lastModificationTime?: string | Date; + lastModifierId?: string; + + constructor(initialValues: Partial> = {}) { + super(initialValues); + } +} + +export class AuditedEntityWithUserDto extends AuditedEntityDto< + TPrimaryKey +> { + creator?: TUserDto; + lastModifier?: TUserDto; + + constructor(initialValues: Partial> = {}) { + super(initialValues); + } +} + +export class FullAuditedEntityDto extends AuditedEntityDto { + isDeleted?: boolean; + deleterId?: string; + deletionTime?: Date | string; + + constructor(initialValues: Partial> = {}) { + super(initialValues); + } +} + +export class FullAuditedEntityWithUserDto< + TUserDto, + TPrimaryKey = string +> extends FullAuditedEntityDto { + creator?: TUserDto; + lastModifier?: TUserDto; + deleter?: TUserDto; + + constructor(initialValues: Partial> = {}) { + super(initialValues); + } +} diff --git a/npm/ng-packs/packages/core/src/lib/models/index.ts b/npm/ng-packs/packages/core/src/lib/models/index.ts index c19dcbc96a..4d4ed321c4 100644 --- a/npm/ng-packs/packages/core/src/lib/models/index.ts +++ b/npm/ng-packs/packages/core/src/lib/models/index.ts @@ -1,7 +1,8 @@ export * from './application-configuration'; export * from './common'; export * from './config'; +export * from './dtos'; export * from './profile'; -export * from './replaceable-components'; +export * from './replaceable-components'; export * from './rest'; export * from './session';