From 9bf8e3929e8b7a496fa068c0ac78825d183c6c51 Mon Sep 17 00:00:00 2001 From: Marcin Grabski Date: Thu, 18 Jun 2026 15:34:25 +0200 Subject: [PATCH] feat: Add support for custom APIs with TypeScript generation and related models #36 --- DataverseGen.Cli/Program.cs | 14 +- DataverseGen.Core/CustomApi/CustomApiModel.cs | 48 +++ .../CustomApi/CustomApiParameterModel.cs | 48 +++ .../DataverseCustomApiConverter.cs | 360 ++++++++++++++++++ .../CustomApiTypeScriptGenerator.cs | 138 +++++++ .../Generators/Scriban/ScribanGenerator.cs | 16 +- DataverseGen.Core/Metadata/Context.cs | 4 +- .../CustomApiTypeScriptGeneratorTests.cs | 53 +++ 8 files changed, 677 insertions(+), 4 deletions(-) create mode 100644 DataverseGen.Core/CustomApi/CustomApiModel.cs create mode 100644 DataverseGen.Core/CustomApi/CustomApiParameterModel.cs create mode 100644 DataverseGen.Core/DataConverter/DataverseCustomApiConverter.cs create mode 100644 DataverseGen.Core/Generators/CustomApiTypeScriptGenerator.cs create mode 100644 Tests/DataverseGen.Core.Tests/Generators/CustomApiTypeScriptGeneratorTests.cs diff --git a/DataverseGen.Cli/Program.cs b/DataverseGen.Cli/Program.cs index 6843184..aab6ac2 100644 --- a/DataverseGen.Cli/Program.cs +++ b/DataverseGen.Cli/Program.cs @@ -2,6 +2,7 @@ using CommunityToolkit.Diagnostics; using DataverseGen.Core.Config; using DataverseGen.Core.DataConverter; +using DataverseGen.Core.CustomApi; using DataverseGen.Core.Generators.Scriban; using DataverseGen.Core.Metadata; using Meziantou.Framework.Win32; @@ -92,10 +93,19 @@ private static void Main(string[] args) MappingEntity[] data = dataConverter.GetMappedEntities(); WriteLine(@"Finish Load data"); + + CustomApiModel[] customApis = Array.Empty(); + if (config.TemplateEngine.Type.Equals("ts", StringComparison.InvariantCultureIgnoreCase)) + { + DataverseCustomApiConverter customApiConverter = new(dataverseConnector); + customApis = customApiConverter.GetCustomApis(); + } + Context context = new() { Namespace = config.Namespace, - Entities = data + Entities = data, + CustomApis = customApis }; WriteLine(@"Start generator"); @@ -154,4 +164,4 @@ private static void ScribanRun(ConfigModel config, Context context) scribanGenerator.GenerateTemplate(); } -} \ No newline at end of file +} diff --git a/DataverseGen.Core/CustomApi/CustomApiModel.cs b/DataverseGen.Core/CustomApi/CustomApiModel.cs new file mode 100644 index 0000000..3cf3b1f --- /dev/null +++ b/DataverseGen.Core/CustomApi/CustomApiModel.cs @@ -0,0 +1,48 @@ +using System.Runtime.Serialization; +using Newtonsoft.Json; + +namespace DataverseGen.Core.CustomApi; + +[Serializable] +[DataContract] +public class CustomApiModel +{ + [JsonProperty] + public string Id { get; set; } + + [JsonProperty] + public string UniqueName { get; set; } + + [JsonProperty] + public string DisplayName { get; set; } + + [JsonProperty] + public string Description { get; set; } + + [JsonProperty] + public string OperationName { get; set; } + + [JsonProperty] + public string BindingType { get; set; } + + [JsonProperty] + public string BoundEntityLogicalName { get; set; } + + [JsonProperty] + public string ExecutePrivilegeName { get; set; } + + [JsonProperty] + public bool IsFunction { get; set; } + + [JsonProperty] + public bool IsPrivate { get; set; } + + [JsonProperty] + public string RequestClassName { get; set; } + + [JsonProperty] + public CustomApiParameterModel[] RequestParameters { get; set; } = Array.Empty(); + + [JsonProperty] + public CustomApiParameterModel[] ResponseProperties { get; set; } = Array.Empty(); +} diff --git a/DataverseGen.Core/CustomApi/CustomApiParameterModel.cs b/DataverseGen.Core/CustomApi/CustomApiParameterModel.cs new file mode 100644 index 0000000..a1f59bb --- /dev/null +++ b/DataverseGen.Core/CustomApi/CustomApiParameterModel.cs @@ -0,0 +1,48 @@ +using System.Runtime.Serialization; +using Newtonsoft.Json; + +namespace DataverseGen.Core.CustomApi; + +[Serializable] +[DataContract] +public class CustomApiParameterModel +{ + [JsonProperty] + public string Id { get; set; } + + [JsonProperty] + public string CustomApiId { get; set; } + + [JsonProperty] + public string UniqueName { get; set; } + + [JsonProperty] + public string DisplayName { get; set; } + + [JsonProperty] + public string Description { get; set; } + + [JsonProperty] + public string Type { get; set; } + + [JsonProperty] + public string LogicalEntityName { get; set; } + + [JsonProperty] + public bool IsOptional { get; set; } + + [JsonProperty] + public int Position { get; set; } + + [JsonProperty] + public string PropertyName { get; set; } + + [JsonProperty] + public string TypeScriptType { get; set; } + + [JsonProperty] + public string WebApiTypeName { get; set; } + + [JsonProperty] + public string WebApiStructuralProperty { get; set; } +} diff --git a/DataverseGen.Core/DataConverter/DataverseCustomApiConverter.cs b/DataverseGen.Core/DataConverter/DataverseCustomApiConverter.cs new file mode 100644 index 0000000..2da3280 --- /dev/null +++ b/DataverseGen.Core/DataConverter/DataverseCustomApiConverter.cs @@ -0,0 +1,360 @@ +using System.Diagnostics; +using System.Globalization; +using System.Text; +using DataverseGen.Core.CustomApi; +using DataverseGen.Core.Extensions; +using Microsoft.Xrm.Sdk; +using Microsoft.Xrm.Sdk.Query; +using static DataverseGen.Core.ColorConsole; + +namespace DataverseGen.Core.DataConverter; + +public class DataverseCustomApiConverter +{ + private const string CustomApiTableName = "customapi"; + private const string CustomApiRequestParameterTableName = "customapirequestparameter"; + private const string CustomApiResponsePropertyTableName = "customapiresponseproperty"; + + private readonly DataverseConnector _dataverseConnector; + + public DataverseCustomApiConverter(DataverseConnector dataverseConnector) + { + _dataverseConnector = dataverseConnector; + } + + public CustomApiModel[] GetCustomApis() + { + Stopwatch stopper = Stopwatch.StartNew(); + WriteInfo(@"Loading custom APIs"); + + List customApiEntities = RetrieveCustomApiEntities(); + List requestParameterEntities = RetrieveRelatedEntities(CustomApiRequestParameterTableName); + List responsePropertyEntities = RetrieveRelatedEntities(CustomApiResponsePropertyTableName); + + Dictionary> requestParametersByApiId = + GroupParametersByApiId(requestParameterEntities); + Dictionary> responsePropertiesByApiId = + GroupParametersByApiId(responsePropertyEntities); + + List result = customApiEntities + .Select(entity => ParseCustomApi(entity, + requestParametersByApiId.GetValueOrDefault(GetEntityId(entity)) ?? new List(), + responsePropertiesByApiId.GetValueOrDefault(GetEntityId(entity)) ?? new List())) + .OrderBy(api => api.RequestClassName) + .ToList(); + + stopper.Stop(); + WriteInfo($@"Loaded custom APIs in: {stopper.Elapsed:g}"); + + return result.ToArray(); + } + + private static Dictionary> GroupParametersByApiId(IEnumerable entities) + { + Dictionary> grouped = new(StringComparer.InvariantCultureIgnoreCase); + + foreach (Entity entity in entities) + { + string apiId = GetLookupId(entity, "customapiid"); + + if (string.IsNullOrWhiteSpace(apiId)) + { + continue; + } + + CustomApiParameterModel parameter = ParseParameter(entity); + + if (!grouped.TryGetValue(apiId, out List parameters)) + { + parameters = new List(); + grouped[apiId] = parameters; + } + + parameters.Add(parameter); + } + + return grouped; + } + + private List RetrieveCustomApiEntities() + { + QueryExpression query = new(CustomApiTableName) + { + ColumnSet = new ColumnSet( + "customapiid", + "uniquename", + "name", + "description", + "bindingtype", + "boundentitylogicalname", + "executeprivilegename", + "isfunction", + "isprivate") + }; + + EntityCollection response = _dataverseConnector.OrganizationService.RetrieveMultiple(query); + WriteInfo($@"Found custom APIs: {response.Entities.Count}"); + return response.Entities.ToList(); + } + + private List RetrieveRelatedEntities(string tableName) + { + QueryExpression query = new(tableName) + { + ColumnSet = new ColumnSet( + $"{tableName}id", + "customapiid", + "uniquename", + "name", + "description", + "type", + "logicalentityname", + "isoptional", + "position") + }; + + EntityCollection response = _dataverseConnector.OrganizationService.RetrieveMultiple(query); + WriteInfo($@"Found {tableName}: {response.Entities.Count}"); + return response.Entities.ToList(); + } + + private static CustomApiModel ParseCustomApi( + Entity entity, + IReadOnlyCollection requestParameters, + IReadOnlyCollection responseProperties) + { + string id = GetEntityId(entity); + string displayName = GetString(entity, "name"); + string uniqueName = GetString(entity, "uniquename"); + string classNameSource = string.IsNullOrWhiteSpace(displayName) ? uniqueName : displayName; + string className = ToPascalCase(classNameSource); + if (!className.EndsWith("Request", StringComparison.InvariantCultureIgnoreCase)) + { + className += "Request"; + } + + return new CustomApiModel + { + Id = id, + UniqueName = uniqueName, + DisplayName = displayName, + Description = GetString(entity, "description"), + OperationName = string.IsNullOrWhiteSpace(uniqueName) ? className : uniqueName, + BindingType = GetFormattedOrRawValue(entity, "bindingtype"), + BoundEntityLogicalName = GetString(entity, "boundentitylogicalname"), + ExecutePrivilegeName = GetString(entity, "executeprivilegename"), + IsFunction = GetBool(entity, "isfunction"), + IsPrivate = GetBool(entity, "isprivate"), + RequestClassName = className, + RequestParameters = requestParameters + .OrderBy(p => p.Position) + .ThenBy(p => p.PropertyName) + .ToArray(), + ResponseProperties = responseProperties + .OrderBy(p => p.Position) + .ThenBy(p => p.PropertyName) + .ToArray() + }; + } + + private static CustomApiParameterModel ParseParameter(Entity entity) + { + string displayName = GetString(entity, "name"); + string uniqueName = GetString(entity, "uniquename"); + string sourceName = string.IsNullOrWhiteSpace(uniqueName) ? displayName : uniqueName; + string propertyName = ToPascalCase(sourceName, false); + string typeLabel = GetFormattedOrRawValue(entity, "type"); + string logicalEntityName = GetString(entity, "logicalentityname"); + + NormalizeCustomApiParameterType(typeLabel, logicalEntityName, out string tsType, out string webApiTypeName, out string structuralProperty); + + return new CustomApiParameterModel + { + Id = GetEntityId(entity), + CustomApiId = GetLookupId(entity, "customapiid"), + UniqueName = uniqueName, + DisplayName = displayName, + Description = GetString(entity, "description"), + Type = typeLabel, + LogicalEntityName = logicalEntityName, + IsOptional = GetBool(entity, "isoptional"), + Position = GetInt(entity, "position"), + PropertyName = propertyName, + TypeScriptType = tsType, + WebApiTypeName = webApiTypeName, + WebApiStructuralProperty = structuralProperty + }; + } + + private static string GetEntityId(Entity entity) + { + return entity.Id.ToString("D"); + } + + private static string GetFormattedOrRawValue(Entity entity, string attributeName) + { + if (entity.FormattedValues.TryGetValue(attributeName, out string formattedValue) && + !string.IsNullOrWhiteSpace(formattedValue)) + { + return formattedValue; + } + + object rawValue = entity.Contains(attributeName) ? entity[attributeName] : null; + return rawValue switch + { + OptionSetValue optionSetValue => optionSetValue.Value.ToString(CultureInfo.InvariantCulture), + Money money => money.Value.ToString(CultureInfo.InvariantCulture), + _ => rawValue?.ToString() ?? string.Empty + }; + } + + private static string GetLookupId(Entity entity, string attributeName) + { + EntityReference reference = entity.GetAttributeValue(attributeName); + return reference?.Id.ToString("D") ?? string.Empty; + } + + private static bool GetBool(Entity entity, string attributeName) + { + object value = entity.Contains(attributeName) ? entity[attributeName] : null; + return value switch + { + bool b => b, + OptionSetValue optionSetValue => optionSetValue.Value != 0, + _ => false + }; + } + + private static int GetInt(Entity entity, string attributeName) + { + object value = entity.Contains(attributeName) ? entity[attributeName] : null; + return value switch + { + int i => i, + OptionSetValue optionSetValue => optionSetValue.Value, + decimal decimalValue => (int)decimalValue, + double doubleValue => (int)doubleValue, + _ => 0 + }; + } + + private static string GetString(Entity entity, string attributeName) + { + return entity.GetAttributeValue(attributeName) ?? string.Empty; + } + + private static void NormalizeCustomApiParameterType( + string typeLabel, + string logicalEntityName, + out string typeScriptType, + out string webApiTypeName, + out string structuralProperty) + { + string normalizedType = typeLabel.Trim().ToLowerInvariant(); + + if (normalizedType.Contains("collection")) + { + typeScriptType = "string[]"; + webApiTypeName = string.IsNullOrWhiteSpace(logicalEntityName) + ? "Collection(Edm.String)" + : $"Collection(mscrm.{logicalEntityName})"; + structuralProperty = "WebApiRequestStructuralProperty.Collection"; + return; + } + + if (normalizedType.Contains("entity")) + { + typeScriptType = "any"; + webApiTypeName = string.IsNullOrWhiteSpace(logicalEntityName) + ? "mscrm.crmbaseentity" + : $"mscrm.{logicalEntityName}"; + structuralProperty = "WebApiRequestStructuralProperty.EntityType"; + return; + } + + if (normalizedType.Contains("bool")) + { + typeScriptType = "boolean"; + webApiTypeName = "Edm.Boolean"; + structuralProperty = "WebApiRequestStructuralProperty.PrimitiveType"; + return; + } + + if (normalizedType.Contains("int") || normalizedType.Contains("whole")) + { + typeScriptType = "number"; + webApiTypeName = "Edm.Int32"; + structuralProperty = "WebApiRequestStructuralProperty.PrimitiveType"; + return; + } + + if (normalizedType.Contains("decimal") || normalizedType.Contains("double") || normalizedType.Contains("money") || normalizedType.Contains("float")) + { + typeScriptType = "number"; + webApiTypeName = "Edm.Decimal"; + structuralProperty = "WebApiRequestStructuralProperty.PrimitiveType"; + return; + } + + if (normalizedType.Contains("date")) + { + typeScriptType = "Date"; + webApiTypeName = "Edm.DateTimeOffset"; + structuralProperty = "WebApiRequestStructuralProperty.PrimitiveType"; + return; + } + + if (normalizedType.Contains("guid") || normalizedType.Contains("unique")) + { + typeScriptType = "string"; + webApiTypeName = "Edm.Guid"; + structuralProperty = "WebApiRequestStructuralProperty.PrimitiveType"; + return; + } + + typeScriptType = "string"; + webApiTypeName = "Edm.String"; + structuralProperty = "WebApiRequestStructuralProperty.PrimitiveType"; + } + + private static string ToPascalCase(string value, bool capitalizeFirstWord = true) + { + if (string.IsNullOrWhiteSpace(value)) + { + return string.Empty; + } + + char[] separators = [' ', '_', '-', '.', '/', '\\']; + string[] parts = value.Split(separators, StringSplitOptions.RemoveEmptyEntries); + StringBuilder builder = new(); + + for (int i = 0; i < parts.Length; i++) + { + string part = parts[i].Trim(); + + if (part.Length == 0) + { + continue; + } + + if (i == 0 && !capitalizeFirstWord) + { + builder.Append(char.ToLowerInvariant(part[0])); + if (part.Length > 1) + { + builder.Append(part[1..]); + } + } + else + { + builder.Append(char.ToUpperInvariant(part[0])); + if (part.Length > 1) + { + builder.Append(part[1..]); + } + } + } + + return builder.ToString(); + } +} diff --git a/DataverseGen.Core/Generators/CustomApiTypeScriptGenerator.cs b/DataverseGen.Core/Generators/CustomApiTypeScriptGenerator.cs new file mode 100644 index 0000000..370ebb5 --- /dev/null +++ b/DataverseGen.Core/Generators/CustomApiTypeScriptGenerator.cs @@ -0,0 +1,138 @@ +using System.Text; +using DataverseGen.Core.CustomApi; +using DataverseGen.Core.Extensions; + +namespace DataverseGen.Core.Generators; + +public static class CustomApiTypeScriptGenerator +{ + public static void WriteCustomApis(string outPath, IEnumerable customApis) + { + if (customApis == null) + { + return; + } + + string customApiDirectory = Path.Combine(outPath, "customapi"); + Directory.CreateDirectory(customApiDirectory); + + foreach (CustomApiModel customApi in customApis) + { + string filePath = Path.Combine(customApiDirectory, $"{customApi.RequestClassName.ToLowerInvariant()}.ts"); + File.WriteAllText(filePath, RenderRequestClass(customApi), Encoding.UTF8); + } + } + + internal static string RenderRequestClass(CustomApiModel customApi) + { + StringBuilder sb = new(); + string className = customApi.RequestClassName; + List constructorParameters = customApi.RequestParameters.ToList(); + CustomApiParameterModel? boundParameter = GetBoundParameter(customApi); + + if (boundParameter != null) + { + constructorParameters.Insert(0, boundParameter); + } + + sb.AppendLine($"export default class {className} implements IWebApiRequest {{"); + sb.AppendLine("\tprivate operationName: string;"); + + foreach (CustomApiParameterModel parameter in constructorParameters) + { + sb.AppendLine($"\tprivate {parameter.PropertyName}: {parameter.TypeScriptType};"); + } + + sb.AppendLine(); + sb.Append($"\tconstructor("); + sb.Append(string.Join(", ", constructorParameters.Select(p => $"private {p.PropertyName}: {p.TypeScriptType}"))); + sb.AppendLine(") {"); + sb.AppendLine($"\t\tthis.operationName = \"{EscapeString(customApi.OperationName)}\";"); + + foreach (CustomApiParameterModel parameter in customApi.RequestParameters) + { + sb.AppendLine($"\t\tthis.{parameter.PropertyName} = {parameter.PropertyName};"); + } + + sb.AppendLine("\t}"); + sb.AppendLine("\tgetInternalRequest(): IWebApiInternalRequest {"); + sb.AppendLine("\t\treturn {"); + + if (boundParameter != null) + { + sb.AppendLine($"\t\t\t{boundParameter.PropertyName}: this.{boundParameter.PropertyName},"); + } + + foreach (CustomApiParameterModel parameter in customApi.RequestParameters) + { + sb.AppendLine($"\t\t\t{parameter.PropertyName}: this.{parameter.PropertyName},"); + } + + sb.AppendLine("\t\t\tgetMetadata: () => ({"); + sb.AppendLine($"\t\t\t\tboundParameter: {GetBoundParameterName(boundParameter)},"); + sb.AppendLine("\t\t\t\tparameterTypes: {"); + + if (boundParameter != null) + { + sb.AppendLine($"\t\t\t\t\t{boundParameter.PropertyName}: {{"); + sb.AppendLine($"\t\t\t\t\t\ttypeName: \"{boundParameter.WebApiTypeName}\","); + sb.AppendLine($"\t\t\t\t\t\tstructuralProperty: {boundParameter.WebApiStructuralProperty}"); + sb.AppendLine("\t\t\t\t\t},"); + } + + foreach (CustomApiParameterModel parameter in customApi.RequestParameters) + { + sb.AppendLine($"\t\t\t\t\t{parameter.PropertyName}: {{"); + sb.AppendLine($"\t\t\t\t\t\ttypeName: \"{parameter.WebApiTypeName}\","); + sb.AppendLine($"\t\t\t\t\t\tstructuralProperty: {parameter.WebApiStructuralProperty}"); + sb.AppendLine("\t\t\t\t\t},"); + } + + sb.AppendLine("\t\t\t\t},"); + sb.AppendLine($"\t\t\t\toperationType: {GetOperationType(customApi)},"); + sb.AppendLine("\t\t\t\toperationName: this.operationName"); + sb.AppendLine("\t\t\t})"); + sb.AppendLine("\t\t} as IWebApiInternalRequest;"); + sb.AppendLine("\t}"); + sb.AppendLine("}"); + + return sb.ToString(); + } + + private static CustomApiParameterModel? GetBoundParameter(CustomApiModel customApi) + { + if (string.IsNullOrWhiteSpace(customApi.BoundEntityLogicalName)) + { + return null; + } + + return new CustomApiParameterModel + { + PropertyName = "entity", + TypeScriptType = "any", + WebApiTypeName = $"mscrm.{customApi.BoundEntityLogicalName}", + WebApiStructuralProperty = "WebApiRequestStructuralProperty.EntityType" + }; + } + + private static string GetBoundParameterName(CustomApiParameterModel? boundParameter) + { + return boundParameter == null + ? "null" + : $"\"{EscapeString(boundParameter.PropertyName)}\""; + } + + private static string GetOperationType(CustomApiModel customApi) + { + return customApi.IsFunction + ? "WebApiRequestOperationType.Function" + : "WebApiRequestOperationType.Action"; + } + + private static string EscapeString(string value) + { + return string.IsNullOrEmpty(value) + ? string.Empty + : value.Replace("\\", "\\\\").Replace("\"", "\\\""); + } +} diff --git a/DataverseGen.Core/Generators/Scriban/ScribanGenerator.cs b/DataverseGen.Core/Generators/Scriban/ScribanGenerator.cs index 5421a8f..248318e 100644 --- a/DataverseGen.Core/Generators/Scriban/ScribanGenerator.cs +++ b/DataverseGen.Core/Generators/Scriban/ScribanGenerator.cs @@ -2,6 +2,8 @@ using System.Text; using System.Text.RegularExpressions; using DataverseGen.Core.Config; +using DataverseGen.Core.CustomApi; +using DataverseGen.Core.Generators; using DataverseGen.Core.Generators.Scriban.Templates; using DataverseGen.Core.Generators.Scriban.Templates.TemplateFileManager; using DataverseGen.Core.Metadata; @@ -122,11 +124,13 @@ private void RunTypeScriptGenerator() if (TemplateEngineModel.IsSingleOutput) { SingleFileTypescript(); + GenerateCustomApis(); return; } MultiOutputGeneratorTypeScript(); + GenerateCustomApis(); } private void SingleFileCSharp() @@ -219,6 +223,16 @@ private void WriteEntityTemplateToFile( Encoding.UTF8); } + private void GenerateCustomApis() + { + if (Context.CustomApis == null || Context.CustomApis.Length == 0) + { + return; + } + + CustomApiTypeScriptGenerator.WriteCustomApis(_fullOutputPath, Context.CustomApis); + } + [GeneratedRegex("^\\s*$\\n|\\r", RegexOptions.Multiline)] private static partial Regex RemoveEmptyLinesRegexCompiled(); -} \ No newline at end of file +} diff --git a/DataverseGen.Core/Metadata/Context.cs b/DataverseGen.Core/Metadata/Context.cs index 8f95d06..12eebe4 100644 --- a/DataverseGen.Core/Metadata/Context.cs +++ b/DataverseGen.Core/Metadata/Context.cs @@ -8,4 +8,6 @@ public class Context public GeneratorInfo Info { get; set; } = new(); public string Namespace { get; set; } -} \ No newline at end of file + + public CustomApi.CustomApiModel[] CustomApis { get; set; } = Array.Empty(); +} diff --git a/Tests/DataverseGen.Core.Tests/Generators/CustomApiTypeScriptGeneratorTests.cs b/Tests/DataverseGen.Core.Tests/Generators/CustomApiTypeScriptGeneratorTests.cs new file mode 100644 index 0000000..aad95a4 --- /dev/null +++ b/Tests/DataverseGen.Core.Tests/Generators/CustomApiTypeScriptGeneratorTests.cs @@ -0,0 +1,53 @@ +using DataverseGen.Core.CustomApi; +using DataverseGen.Core.Generators; + +namespace DataverseGen.Core.Tests.Generators; + +[TestClass] +public class CustomApiTypeScriptGeneratorTests +{ + [TestMethod] + public void WriteCustomApis_CreatesTypeScriptRequestClass() + { + string tempRoot = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(tempRoot); + + try + { + CustomApiTypeScriptGenerator.WriteCustomApis(tempRoot, new[] + { + new CustomApiModel + { + RequestClassName = "ProcessStatusRequest", + OperationName = "newupdatestatus", + RequestParameters = new[] + { + new CustomApiParameterModel + { + PropertyName = "customerid", + TypeScriptType = "string[]", + WebApiTypeName = "Collection(Edm.String)", + WebApiStructuralProperty = "WebApiRequestStructuralProperty.Collection" + } + } + } + }); + + string filePath = Path.Combine(tempRoot, "customapi", "processstatusrequest.ts"); + string content = File.ReadAllText(filePath); + + StringAssert.Contains(content, "export default class ProcessStatusRequest implements IWebApiRequest"); + StringAssert.Contains(content, "private customerid: string[];"); + StringAssert.Contains(content, "operationName = \"newupdatestatus\""); + StringAssert.Contains(content, "typeName: \"Collection(Edm.String)\""); + StringAssert.Contains(content, "structuralProperty: WebApiRequestStructuralProperty.Collection"); + } + finally + { + if (Directory.Exists(tempRoot)) + { + Directory.Delete(tempRoot, true); + } + } + } +}