Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions DataverseGen.Cli/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,8 @@ private static void Main(string[] args)
MappingEntity[] data = dataConverter.GetMappedEntities();
WriteLine(@"Finish Load data");

CustomApiModel[] customApis = Array.Empty<CustomApiModel>();
if (config.TemplateEngine.Type.Equals("ts", StringComparison.InvariantCultureIgnoreCase))
{
DataverseCustomApiConverter customApiConverter = new(dataverseConnector, config.CustomApis);
customApis = customApiConverter.GetCustomApis();
}
DataverseCustomApiConverter customApiConverter = new(dataverseConnector, config.CustomApis);
CustomApiModel[] customApis = customApiConverter.GetCustomApis();

Context context = new()
{
Expand Down
16 changes: 2 additions & 14 deletions DataverseGen.Core/Config/ConfigValidation.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Linq;

namespace DataverseGen.Core.Config;

Expand All @@ -12,20 +11,9 @@ public static void Validate(ConfigModel config)
throw new ArgumentNullException(nameof(config));
}

if (!HasCustomApis(config.CustomApis))
if (config.TemplateEngine == null)
{
return;
throw new InvalidOperationException("TemplateEngine must be configured.");
}

if (config.TemplateEngine == null ||
!string.Equals(config.TemplateEngine.Type, "ts", StringComparison.InvariantCultureIgnoreCase))
{
throw new InvalidOperationException("CustomApis can only be used when TemplateEngine.Type is set to 'ts'.");
}
}

private static bool HasCustomApis(string[] customApis)
{
return customApis != null && customApis.Any(api => !string.IsNullOrWhiteSpace(api));
}
}
3 changes: 3 additions & 0 deletions DataverseGen.Core/CustomApi/CustomApiParameterModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ public class CustomApiParameterModel
[JsonProperty]
public string TypeScriptType { get; set; }

[JsonProperty]
public string CSharpTypeName { get; set; }

[JsonProperty]
public string WebApiTypeName { get; set; }

Expand Down
30 changes: 28 additions & 2 deletions DataverseGen.Core/DataConverter/DataverseCustomApiConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,12 @@ private static CustomApiParameterModel ParseParameter(Entity entity)
string typeLabel = GetFormattedOrRawValue(entity, "type");
string logicalEntityName = GetString(entity, "logicalentityname");

NormalizeCustomApiParameterType(typeLabel, logicalEntityName, out string tsType, out string webApiTypeName, out string structuralProperty);
NormalizeCustomApiParameterType(typeLabel,
logicalEntityName,
out string tsType,
out string cSharpTypeName,
out string webApiTypeName,
out string structuralProperty);

return new CustomApiParameterModel
{
Expand All @@ -233,6 +238,7 @@ private static CustomApiParameterModel ParseParameter(Entity entity)
RequestPropertyName = MetadataNamingExtensions.GetProperVariableName(string.IsNullOrWhiteSpace(uniqueName) ? propertyName : uniqueName),
ConstructorParameterName = MetadataNamingExtensions.GetProperVariableName(string.IsNullOrWhiteSpace(displayName) ? propertyName : displayName),
TypeScriptType = tsType,
CSharpTypeName = cSharpTypeName,
WebApiTypeName = webApiTypeName,
WebApiStructuralProperty = structuralProperty
};
Expand Down Expand Up @@ -299,14 +305,27 @@ private static void NormalizeCustomApiParameterType(
string typeLabel,
string logicalEntityName,
out string typeScriptType,
out string cSharpTypeName,
out string webApiTypeName,
out string structuralProperty)
{
string normalizedType = typeLabel.Trim().ToLowerInvariant();
string normalizedType = (typeLabel ?? string.Empty).Trim().ToLowerInvariant();

if (string.IsNullOrWhiteSpace(normalizedType))
{
typeScriptType = "string";
cSharpTypeName = "string";
webApiTypeName = "Edm.String";
structuralProperty = "WebApiRequestStructuralProperty.PrimitiveType";
return;
}

if (normalizedType.Contains("collection"))
{
typeScriptType = "string[]";
cSharpTypeName = string.IsNullOrWhiteSpace(logicalEntityName)
? "string[]"
: "EntityReference[]";
webApiTypeName = string.IsNullOrWhiteSpace(logicalEntityName)
? "Collection(Edm.String)"
: $"Collection(mscrm.{logicalEntityName})";
Expand All @@ -317,6 +336,7 @@ private static void NormalizeCustomApiParameterType(
if (normalizedType.Contains("entity"))
{
typeScriptType = "any";
cSharpTypeName = "EntityReference";
webApiTypeName = string.IsNullOrWhiteSpace(logicalEntityName)
? "mscrm.crmbaseentity"
: $"mscrm.{logicalEntityName}";
Expand All @@ -327,6 +347,7 @@ private static void NormalizeCustomApiParameterType(
if (normalizedType.Contains("bool"))
{
typeScriptType = "boolean";
cSharpTypeName = "bool";
webApiTypeName = "Edm.Boolean";
structuralProperty = "WebApiRequestStructuralProperty.PrimitiveType";
return;
Expand All @@ -335,6 +356,7 @@ private static void NormalizeCustomApiParameterType(
if (normalizedType.Contains("int") || normalizedType.Contains("whole"))
{
typeScriptType = "number";
cSharpTypeName = "int";
webApiTypeName = "Edm.Int32";
structuralProperty = "WebApiRequestStructuralProperty.PrimitiveType";
return;
Expand All @@ -343,6 +365,7 @@ private static void NormalizeCustomApiParameterType(
if (normalizedType.Contains("decimal") || normalizedType.Contains("double") || normalizedType.Contains("money") || normalizedType.Contains("float"))
{
typeScriptType = "number";
cSharpTypeName = "decimal";
webApiTypeName = "Edm.Decimal";
structuralProperty = "WebApiRequestStructuralProperty.PrimitiveType";
return;
Expand All @@ -351,6 +374,7 @@ private static void NormalizeCustomApiParameterType(
if (normalizedType.Contains("date"))
{
typeScriptType = "Date";
cSharpTypeName = "DateTime";
webApiTypeName = "Edm.DateTimeOffset";
structuralProperty = "WebApiRequestStructuralProperty.PrimitiveType";
return;
Expand All @@ -359,12 +383,14 @@ private static void NormalizeCustomApiParameterType(
if (normalizedType.Contains("guid") || normalizedType.Contains("unique"))
{
typeScriptType = "string";
cSharpTypeName = "Guid";
webApiTypeName = "Edm.Guid";
structuralProperty = "WebApiRequestStructuralProperty.PrimitiveType";
return;
}

typeScriptType = "string";
cSharpTypeName = "string";
webApiTypeName = "Edm.String";
structuralProperty = "WebApiRequestStructuralProperty.PrimitiveType";
}
Expand Down
227 changes: 227 additions & 0 deletions DataverseGen.Core/Generators/CustomApiCSharpGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
using System.Text;
using DataverseGen.Core.CustomApi;
using DataverseGen.Core.Extensions;

namespace DataverseGen.Core.Generators;

public static class CustomApiCSharpGenerator
{
public static void WriteCustomApis(string outPath, string namespaceName, IEnumerable<CustomApiModel> 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()}.cs");
File.WriteAllText(filePath, RenderRequestClass(namespaceName, customApi), Encoding.UTF8);
}
}

internal static string RenderRequestClass(string namespaceName, CustomApiModel customApi)
{
StringBuilder sb = new();
string className = customApi.RequestClassName;
List<CustomApiParameterModel> constructorParameters = customApi.RequestParameters.ToList();
CustomApiParameterModel boundParameter = GetBoundParameter(customApi);

sb.AppendLine("using System;");
sb.AppendLine("using Microsoft.Xrm.Sdk;");
sb.AppendLine();
sb.AppendLine($"namespace {namespaceName};");
sb.AppendLine();
sb.AppendLine($"public sealed class {className} : OrganizationRequest");
sb.AppendLine("{");
sb.AppendLine($"\tpublic const string RequestNameValue = \"{EscapeString(customApi.OperationName)}\";");
sb.AppendLine();

if (constructorParameters.Count > 0 || boundParameter != null)
{
sb.Append($"\tpublic {className}(");
sb.Append(string.Join(", ", constructorParameters.Select(p => $"{GetCSharpType(p)} {GetParameterName(p)}")));

if (boundParameter != null)
{
if (constructorParameters.Count > 0)
{
sb.Append(", ");
}

sb.Append($"EntityReference {GetParameterName(boundParameter)}");
}

sb.AppendLine(")");
sb.AppendLine("\t{");

sb.AppendLine("\t\tRequestName = RequestNameValue;");

foreach (CustomApiParameterModel parameter in constructorParameters)
{
sb.AppendLine($"\t\t{GetPropertyName(parameter)} = {GetParameterName(parameter)};");
}

if (boundParameter != null)
{
sb.AppendLine($"\t\t{GetPropertyName(boundParameter)} = {GetParameterName(boundParameter)};");
}

sb.AppendLine("\t}");
sb.AppendLine();
}
else
{
sb.AppendLine($"\tpublic {className}()");
sb.AppendLine("\t{");
sb.AppendLine("\t\tRequestName = RequestNameValue;");
sb.AppendLine("\t}");
sb.AppendLine();
}

if (boundParameter != null)
{
sb.AppendLine($"\tpublic EntityReference {GetPropertyName(boundParameter)}");
sb.AppendLine("\t{");
sb.AppendLine($"\t\tget => Parameters.ContainsKey(\"{EscapeString(boundParameter.RequestPropertyName)}\")");
sb.AppendLine($"\t\t\t? (EntityReference)Parameters[\"{EscapeString(boundParameter.RequestPropertyName)}\"]");
sb.AppendLine("\t\t\t: default;");
sb.AppendLine($"\t\tset => Parameters[\"{EscapeString(boundParameter.RequestPropertyName)}\"] = value;");
sb.AppendLine("\t}");
sb.AppendLine();
}

foreach (CustomApiParameterModel parameter in constructorParameters)
{
sb.AppendLine($"\tpublic {GetCSharpType(parameter)} {GetPropertyName(parameter)}");
sb.AppendLine("\t{");
sb.AppendLine($"\t\tget => Parameters.ContainsKey(\"{EscapeString(parameter.RequestPropertyName)}\")");
sb.AppendLine($"\t\t\t? ({GetCSharpType(parameter)})Parameters[\"{EscapeString(parameter.RequestPropertyName)}\"]");
sb.AppendLine("\t\t\t: default;");
sb.AppendLine($"\t\tset => Parameters[\"{EscapeString(parameter.RequestPropertyName)}\"] = value;");
sb.AppendLine("\t}");
sb.AppendLine();
}

sb.AppendLine("}");

return sb.ToString();
}

private static CustomApiParameterModel GetBoundParameter(CustomApiModel customApi)
{
if (string.IsNullOrWhiteSpace(customApi.BoundEntityLogicalName))
{
return null;
}

return new CustomApiParameterModel
{
DisplayName = "Entity",
UniqueName = "entity",
PropertyName = "entity",
RequestPropertyName = "entity",
ConstructorParameterName = "entity",
TypeScriptType = "any",
CSharpTypeName = "EntityReference",
WebApiTypeName = $"mscrm.{customApi.BoundEntityLogicalName}",
WebApiStructuralProperty = "WebApiRequestStructuralProperty.EntityType"
};
}

private static string GetPropertyName(CustomApiParameterModel parameter)
{
string source = string.IsNullOrWhiteSpace(parameter.DisplayName)
? parameter.UniqueName
: parameter.DisplayName;

string sanitized = MetadataNamingExtensions.GetProperVariableName(source);
if (sanitized.StartsWith("_", StringComparison.InvariantCulture) &&
sanitized.Length > 1 &&
char.IsDigit(sanitized[1]))
{
return sanitized;
}

return ToPascalCase(sanitized);
}

private static string GetParameterName(CustomApiParameterModel parameter)
{
string propertyName = GetPropertyName(parameter);

if (string.IsNullOrWhiteSpace(propertyName))
{
return "value";
}

string parameterName = char.ToLowerInvariant(propertyName[0]) + propertyName[1..];
return IsCSharpKeyword(parameterName)
? $"@{parameterName}"
: parameterName;
}

private static string GetCSharpType(CustomApiParameterModel parameter)
{
return string.IsNullOrWhiteSpace(parameter.CSharpTypeName)
? "string"
: parameter.CSharpTypeName;
}

private static string EscapeString(string value)
{
return string.IsNullOrEmpty(value)
? string.Empty
: value.Replace("\\", "\\\\").Replace("\"", "\\\"");
}

private static string ToPascalCase(string value)
{
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;
}

builder.Append(char.ToUpperInvariant(part[0]));
if (part.Length > 1)
{
builder.Append(part[1..]);
}
}

return builder.ToString();
}

private static bool IsCSharpKeyword(string value)
{
return value.Equals("public", StringComparison.InvariantCultureIgnoreCase)
|| value.Equals("private", StringComparison.InvariantCultureIgnoreCase)
|| value.Equals("single", StringComparison.InvariantCultureIgnoreCase)
|| value.Equals("new", StringComparison.InvariantCultureIgnoreCase)
|| value.Equals("partial", StringComparison.InvariantCultureIgnoreCase)
|| value.Equals("to", StringComparison.InvariantCultureIgnoreCase)
|| value.Equals("error", StringComparison.InvariantCultureIgnoreCase)
|| value.Equals("readonly", StringComparison.InvariantCultureIgnoreCase)
|| value.Equals("case", StringComparison.InvariantCultureIgnoreCase)
|| value.Equals("object", StringComparison.InvariantCultureIgnoreCase)
|| value.Equals("global", StringComparison.InvariantCultureIgnoreCase)
|| value.Equals("true", StringComparison.InvariantCultureIgnoreCase)
|| value.Equals("false", StringComparison.InvariantCultureIgnoreCase);
}
}
Loading
Loading