diff --git a/spkl/CrmSvcUtilFilteringService/CrmSvcUtil.FilteringService.csproj b/spkl/CrmSvcUtilFilteringService/CrmSvcUtil.FilteringService.csproj
index 04d907c..f17db8e 100644
--- a/spkl/CrmSvcUtilFilteringService/CrmSvcUtil.FilteringService.csproj
+++ b/spkl/CrmSvcUtilFilteringService/CrmSvcUtil.FilteringService.csproj
@@ -46,6 +46,7 @@
+
False
diff --git a/spkl/CrmSvcUtilFilteringService/FilteringService.cs b/spkl/CrmSvcUtilFilteringService/FilteringService.cs
index 79572a9..7234e33 100644
--- a/spkl/CrmSvcUtilFilteringService/FilteringService.cs
+++ b/spkl/CrmSvcUtilFilteringService/FilteringService.cs
@@ -24,7 +24,7 @@ public FilteringService(ICodeWriterFilterService defaultService)
public bool GenerateAttribute(AttributeMetadata attributeMetadata, IServiceProvider services)
{
return this.DefaultService.GenerateAttribute(attributeMetadata, services);
- }
+ }
public bool GenerateEntity(EntityMetadata entityMetadata, IServiceProvider services)
{
@@ -44,22 +44,25 @@ public bool GenerateOption(OptionMetadata optionMetadata, IServiceProvider servi
{
return this.DefaultService.GenerateOption(optionMetadata, services);
}
-
public bool GenerateOptionSet(OptionSetMetadataBase optionSetMetadata, IServiceProvider services)
{
-
// Should we output a enum optionset or just plain OptionSetValue?
var globalOptionsets = Config.GetConfig("globalEnums") == "true";
- var enums = Config.GetConfig("picklistEnums") == "true" && (optionSetMetadata.IsGlobal!=true || globalOptionsets);
+ var enums = Config.GetConfig("picklistEnums") == "true" && (optionSetMetadata?.IsGlobal != true || globalOptionsets);
var states = Config.GetConfig("stateEnums") == "true";
-
+
var optionsetValues = optionSetMetadata as OptionSetMetadata;
- if (optionsetValues!=null)
+ if (optionsetValues != null)
{
// check if there are any invalid names
foreach (var option in optionsetValues.Options)
{
- string optionSetName = Regex.Replace(option.Label.UserLocalizedLabel.Label, "[^a-zA-Z0-9]", string.Empty);
+ if (option.Label.UserLocalizedLabel == null)
+ {
+ option.Label.UserLocalizedLabel = new Microsoft.Xrm.Sdk.LocalizedLabel("", 1033);
+ }
+ var regexRule = new Regex("[^äöåa-z0-9]", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant);
+ string optionSetName = regexRule.Replace(option.Label.UserLocalizedLabel.Label, string.Empty);
if ((optionSetName.Length > 0) && !char.IsLetter(optionSetName, 0))
{
option.Label.UserLocalizedLabel.Label = "Number_" + optionSetName;
@@ -81,7 +84,7 @@ public bool GenerateOptionSet(OptionSetMetadataBase optionSetMetadata, IServiceP
// check if the names are unique in optionset values
var duplicateNames = optionsetValues.Options.GroupBy(x => x.Label.UserLocalizedLabel.Label).Where(g => g.Count() > 1).Select(y => y.Key).ToList();
- duplicateNames.ForEach(delegate(string duplicate){
+ duplicateNames.ForEach(delegate (string duplicate) {
var option = optionsetValues.Options.Where(x => x.Label.UserLocalizedLabel.Label == duplicate).First();
option.Label.UserLocalizedLabel.Label += option.Value.ToString();
// Also set the other labels
@@ -91,7 +94,7 @@ public bool GenerateOptionSet(OptionSetMetadataBase optionSetMetadata, IServiceP
// 1033 is hard coded in to the default naming service of CrmSvcUtil
label.LanguageCode = 1033;
}
- });
+ });
}
var optionType = (OptionSetType)optionSetMetadata.OptionSetType.Value;
@@ -104,7 +107,7 @@ public bool GenerateOptionSet(OptionSetMetadataBase optionSetMetadata, IServiceP
case OptionSetType.Picklist:
return enums;
default:
- return false;
+ return false;
}
}
diff --git a/spkl/CrmSvcUtilFilteringService/NamingService.cs b/spkl/CrmSvcUtilFilteringService/NamingService.cs
new file mode 100644
index 0000000..0f3cc08
--- /dev/null
+++ b/spkl/CrmSvcUtilFilteringService/NamingService.cs
@@ -0,0 +1,267 @@
+namespace spkl.CrmSvcUtilExtensions
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Globalization;
+ using System.Linq;
+ using System.Text;
+ using System.Text.RegularExpressions;
+ using Microsoft.Crm.Services.Utility;
+ using Microsoft.Xrm.Sdk;
+ using Microsoft.Xrm.Sdk.Metadata;
+
+ public sealed class NamingService : INamingService
+ {
+ ///
+ /// Implement this class if you want to provide your own logic for building names that
+ /// will appear in the generated code.
+ ///
+ private INamingService DefaultNamingService { get; }
+
+ ///
+ /// This field keeps track of the number of times that options with the same
+ /// name would have been defined.
+ ///
+ private readonly Dictionary> _optionNames;
+ public NamingService(INamingService namingService)
+ {
+ DefaultNamingService = namingService;
+ _optionNames = new Dictionary>();
+ }
+
+ ///
+ /// Provide a new implementation for finding a name for an OptionSet. If the
+ /// OptionSet is not global, we want the name to be the concatenation of the Entity's
+ /// name and the Attribute's name. Otherwise, we can use the default implementation.
+ ///
+ public String GetNameForOptionSet(EntityMetadata entityMetadata, OptionSetMetadataBase optionSetMetadata, IServiceProvider services)
+ {
+ // Ensure that the OptionSet is not global before using the custom
+ // implementation.
+ if (optionSetMetadata.IsGlobal.HasValue && !optionSetMetadata.IsGlobal.Value)
+ {
+ // Find the attribute which uses the specified OptionSet.
+ var attribute =
+ (from a in entityMetadata.Attributes
+ where a.AttributeType == AttributeTypeCode.Picklist && ((EnumAttributeMetadata)a).OptionSet.MetadataId == optionSetMetadata.MetadataId
+ select a).FirstOrDefault();
+
+ // Check for null, since statuscode attributes on custom entities are not
+ // global, but their optionsets are not included in the attribute
+ // metadata of the entity, either.
+ if (attribute != null)
+ {
+ // Concatenate the name of the entity and the name of the attribute
+ // together to form the OptionSet name.
+ return $"{DefaultNamingService.GetNameForEntity(entityMetadata, services)}{DefaultNamingService.GetNameForAttribute(entityMetadata, attribute, services)}";
+ }
+ }
+
+ return DefaultNamingService.GetNameForOptionSet(entityMetadata, optionSetMetadata, services);
+ }
+
+ #region other INamingService Methods
+
+ public String GetNameForAttribute(EntityMetadata entityMetadata, AttributeMetadata attributeMetadata, IServiceProvider services)
+ {
+ return DefaultNamingService.GetNameForAttribute(entityMetadata, attributeMetadata, services);
+ }
+
+ public String GetNameForEntity(EntityMetadata entityMetadata, IServiceProvider services)
+ {
+ return DefaultNamingService.GetNameForEntity(entityMetadata, services);
+ }
+
+ public String GetNameForEntitySet(EntityMetadata entityMetadata, IServiceProvider services)
+ {
+ return DefaultNamingService.GetNameForEntitySet(entityMetadata, services);
+ }
+
+ public String GetNameForMessagePair(SdkMessagePair messagePair, IServiceProvider services)
+ {
+ return DefaultNamingService.GetNameForMessagePair(messagePair, services);
+ }
+
+ ///
+ /// Handles building the name for an Option of an OptionSet.
+ ///
+ public string GetNameForOption(OptionSetMetadataBase optionSetMetadata, OptionMetadata optionMetadata, IServiceProvider services)
+ {
+ int lngCode = 0;
+ String[] arguments = Environment.GetCommandLineArgs();
+
+ foreach (var arg in arguments)
+ {
+ Console.WriteLine("Argument" + arg);
+
+ if (arg.Contains("lngCode"))
+ {
+ var split = arg.Split(':');
+ if (split.Length == 2)
+ {
+ int.TryParse(split[1], out lngCode);
+ }
+ }
+ }
+
+
+ var name = string.Empty;
+ if (optionMetadata is StateOptionMetadata stateOptionMetadata)
+ {
+ name = stateOptionMetadata.InvariantName;
+ }
+ else
+ {
+ var myLng = optionMetadata.Label.LocalizedLabels.FirstOrDefault(l => l.LanguageCode == lngCode);
+
+ if (myLng != null)
+ name = myLng.Label;
+ else
+ {
+ var defLng = optionMetadata.Label.LocalizedLabels.FirstOrDefault();
+ if (defLng != null)
+ name = defLng.Label;
+ }
+
+ foreach (var localizedLabel in optionMetadata.Label.LocalizedLabels)
+ {
+ if (localizedLabel.LanguageCode == 1033) // English or Finnish
+ name = localizedLabel.Label;
+ }
+ }
+
+ if (string.IsNullOrEmpty(name))
+ {
+ if (optionMetadata.Value != null) name = string.Format(CultureInfo.InvariantCulture, "UnknownLabel{0}", new object[] { optionMetadata.Value.Value });
+ }
+
+ name = CreateValidName(name);
+ name = EnsureValidIdentifier(name);
+ name = EnsureUniqueOptionName(optionSetMetadata, name);
+
+ return name;
+ }
+
+ private static readonly Regex NameRegex = new Regex("[äöåa-z0-9_]*", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant);
+ private static string CreateValidName(string name)
+ {
+ name = TransformToPascalCase(name);
+
+ string input = name.Replace("$", "CurrencySymbol_").Replace("(", "_");
+ var stringBuilder = new StringBuilder();
+ for (Match match = NameRegex.Match(input); match.Success; match = match.NextMatch())
+ stringBuilder.Append(match.Value);
+
+ var newName = stringBuilder.ToString();
+ Console.WriteLine(newName);
+
+ return newName;
+ }
+
+
+ ///
+ /// Checks to make sure that the name begins with a valid character. If the name
+ /// does not begin with a valid character, then add an underscore to the
+ /// beginning of the name.
+ ///
+ private static String EnsureValidIdentifier(String name)
+ {
+
+ // Check to make sure that the option set begins with a word character
+ // or underscore.
+ var pattern = @"^[ÄäÖöÅåA-Za-z_][ÄäÖöÅåA-Za-z0-9_]*$";
+ if (!Regex.IsMatch(name, pattern))
+ {
+ // Prepend an underscore to the name if it is not valid.
+ name = $"_{name}";
+ Trace.TraceInformation($"Name of the option changed to {name}");
+ }
+ return name;
+ }
+
+ ///
+ /// Checks to make sure that the name does not already exist for the OptionSet
+ /// to be generated.
+ ///
+ private String EnsureUniqueOptionName(OptionSetMetadataBase metadata, String name)
+ {
+ if (_optionNames.ContainsKey(metadata))
+ {
+ if (_optionNames[metadata].ContainsKey(name))
+ {
+ // Increment the number of times that an option with this name has
+ // been found.
+ ++_optionNames[metadata][name];
+
+ // Append the number to the name to create a new, unique name.
+ var newName = $"{name}_{_optionNames[metadata][name]}";
+
+ Trace.TraceInformation($"The {metadata.Name} OptionSet already contained a definition for {name}. Changed to {newName}");
+
+ // Call this function again to make sure that our new name is unique.
+ return EnsureUniqueOptionName(metadata, newName);
+ }
+ }
+ else
+ {
+ // This is the first time this OptionSet has been encountered. Add it to
+ // the dictionary.
+ _optionNames[metadata] = new Dictionary();
+ }
+
+ // This is the first time this name has been encountered. Begin keeping track
+ // of the times we've run across it.
+ _optionNames[metadata][name] = 1;
+
+ return name;
+ }
+
+ public String GetNameForRelationship(EntityMetadata entityMetadata, RelationshipMetadataBase relationshipMetadata, EntityRole? reflexiveRole, IServiceProvider services)
+ {
+ return DefaultNamingService.GetNameForRelationship(entityMetadata, relationshipMetadata, reflexiveRole, services);
+ }
+
+ public String GetNameForRequestField(SdkMessageRequest request, SdkMessageRequestField requestField, IServiceProvider services)
+ {
+ return DefaultNamingService.GetNameForRequestField(
+ request, requestField, services);
+ }
+
+ public String GetNameForResponseField(SdkMessageResponse response, SdkMessageResponseField responseField, IServiceProvider services)
+ {
+ return DefaultNamingService.GetNameForResponseField(response, responseField, services);
+ }
+
+ public String GetNameForServiceContext(IServiceProvider services)
+ {
+ return DefaultNamingService.GetNameForServiceContext(services);
+ }
+
+ #endregion
+
+ public static string TransformToPascalCase(string s)
+ {
+ TextInfo txtInfo = new CultureInfo(CultureInfo.InvariantCulture.Name, false).TextInfo;
+ return Regex.Replace(txtInfo.ToTitleCase(s), @"\W", "");
+ }
+
+
+ public static string RemoveDiacritics(string text)
+ {
+ var normalizedString = text.Normalize(NormalizationForm.FormD);
+ var stringBuilder = new StringBuilder();
+
+ foreach (var c in normalizedString)
+ {
+ var unicodeCategory = CharUnicodeInfo.GetUnicodeCategory(c);
+ if (unicodeCategory != UnicodeCategory.NonSpacingMark)
+ {
+ stringBuilder.Append(c);
+ }
+ }
+
+ return stringBuilder.ToString().Normalize(NormalizationForm.FormC);
+ }
+ }
+}
\ No newline at end of file
diff --git a/spkl/SparkleXrm.Tasks/Tasks/EarlyBoundClassGeneratorTask.cs b/spkl/SparkleXrm.Tasks/Tasks/EarlyBoundClassGeneratorTask.cs
index 585f90b..475e108 100644
--- a/spkl/SparkleXrm.Tasks/Tasks/EarlyBoundClassGeneratorTask.cs
+++ b/spkl/SparkleXrm.Tasks/Tasks/EarlyBoundClassGeneratorTask.cs
@@ -22,7 +22,7 @@ public class EarlyBoundClassGeneratorTask : BaseTask
/// Use method to
/// mask password from connection string.
///
- public string ConectionString {get;set;}
+ public string ConectionString { get; set; }
private string _folder;
public EarlyBoundClassGeneratorTask(IOrganizationService service, ITrace trace) : base(service, trace)
{
@@ -34,18 +34,19 @@ public EarlyBoundClassGeneratorTask(OrganizationServiceContext ctx, ITrace trace
protected override void ExecuteInternal(string folder, OrganizationServiceContext ctx)
{
-
+
_trace.WriteLine("Searching for plugin config in '{0}'", folder);
+
var configs = ServiceLocator.ConfigFileFactory.FindConfig(folder);
foreach (var config in configs)
{
_trace.WriteLine("Using Config '{0}'", config.filePath);
-
+
CreateEarlyBoundTypes(ctx, config);
}
_trace.WriteLine("Processed {0} config(s)", configs.Count);
-
+
}
public void CreateEarlyBoundTypes(OrganizationServiceContext ctx, ConfigFile config)
@@ -54,7 +55,7 @@ public void CreateEarlyBoundTypes(OrganizationServiceContext ctx, ConfigFile con
// locate the CrmSvcUtil package folder
var targetfolder = ServiceLocator.DirectoryService.GetApplicationDirectory();
-
+
// If we are running in VS, then move up past bin/Debug
if (targetfolder.Contains(@"bin\Debug") || targetfolder.Contains(@"bin\Release"))
{
@@ -73,7 +74,7 @@ public void CreateEarlyBoundTypes(OrganizationServiceContext ctx, ConfigFile con
// Copy the filtering assembly
var filteringAssemblyPathString = ServiceLocator.DirectoryService.SimpleSearch(targetfolder + @"\..\..", "spkl.CrmSvcUtilExtensions.dll");
-
+
if (string.IsNullOrEmpty(filteringAssemblyPathString))
{
throw new SparkleTaskException(SparkleTaskException.ExceptionTypes.UTILSNOTFOUND,
@@ -90,7 +91,7 @@ public void CreateEarlyBoundTypes(OrganizationServiceContext ctx, ConfigFile con
var earlyBoundTypeConfigs = config.GetEarlyBoundConfig(this.Profile);
foreach (var earlyboundconfig in earlyBoundTypeConfigs)
{
- if(string.IsNullOrEmpty(earlyboundconfig.entities) && earlyboundconfig.entityCollection?.Length > 0)
+ if (string.IsNullOrEmpty(earlyboundconfig.entities) && earlyboundconfig.entityCollection?.Length > 0)
{
earlyboundconfig.entities = string.Join(",", earlyboundconfig.entityCollection);
}
@@ -126,7 +127,7 @@ public void CreateEarlyBoundTypes(OrganizationServiceContext ctx, ConfigFile con
earlyboundconfig.serviceContextName
}"" /GenerateActions:""{
!String.IsNullOrEmpty(earlyboundconfig.actions)
- }"" /codewriterfilter:""spkl.CrmSvcUtilExtensions.FilteringService,spkl.CrmSvcUtilExtensions"" /codewritermessagefilter:""spkl.CrmSvcUtilExtensions.MessageFilteringService,spkl.CrmSvcUtilExtensions"" /codegenerationservice:""spkl.CrmSvcUtilExtensions.CodeGenerationService, spkl.CrmSvcUtilExtensions"" /metadataproviderqueryservice:""spkl.CrmSvcUtilExtensions.MetadataProviderQueryService,spkl.CrmSvcUtilExtensions""";
+ }"" /codewriterfilter:""spkl.CrmSvcUtilExtensions.FilteringService,spkl.CrmSvcUtilExtensions"" /namingservice:""spkl.CrmSvcUtilExtensions.NamingService,spkl.CrmSvcUtilExtensions"" /codewritermessagefilter:""spkl.CrmSvcUtilExtensions.MessageFilteringService,spkl.CrmSvcUtilExtensions"" /codegenerationservice:""spkl.CrmSvcUtilExtensions.CodeGenerationService, spkl.CrmSvcUtilExtensions"" /metadataproviderqueryservice:""spkl.CrmSvcUtilExtensions.MetadataProviderQueryService,spkl.CrmSvcUtilExtensions""";
var procStart = new ProcessStartInfo(crmsvcutilPath, parameters)
{
@@ -140,10 +141,13 @@ public void CreateEarlyBoundTypes(OrganizationServiceContext ctx, ConfigFile con
_trace.WriteLine("Running {0} {1}", crmsvcutilPath,
HideConnectionStringPassword(parameters));
+
var exitCode = 0;
Process proc = null;
+
try
{
+
proc = Process.Start(procStart);
proc.OutputDataReceived += Proc_OutputDataReceived;
proc.ErrorDataReceived += Proc_OutputDataReceived;
@@ -153,14 +157,14 @@ public void CreateEarlyBoundTypes(OrganizationServiceContext ctx, ConfigFile con
proc.CancelOutputRead();
proc.CancelErrorRead();
}
+
finally
{
exitCode = proc.ExitCode;
-
proc.Close();
}
- if (exitCode!=0)
+ if (exitCode != 0)
{
throw new SparkleTaskException(SparkleTaskException.ExceptionTypes.CRMSVCUTIL_ERROR, $"CrmSvcUtil exited with error {exitCode}");
}
@@ -212,7 +216,8 @@ private string HideConnectionStringPassword(string logMessage)
var indexOfPwOnConnStr = ConectionString.IndexOf("Password",
StringComparison.InvariantCultureIgnoreCase);
- if(indexOfPwOnConnStr < 0) {
+ if (indexOfPwOnConnStr < 0)
+ {
return logMessage;
}