From 60963a3dc175fa89d23a018c88c2980127d3fadb Mon Sep 17 00:00:00 2001 From: Alper Ebicoglu Date: Mon, 30 Mar 2020 18:29:33 +0300 Subject: [PATCH 1/8] add connection string param to CLI --- .../Volo/Abp/Cli/Commands/NewCommand.cs | 17 +++++++- .../Steps/ConnectionStringChangeStep.cs | 41 +++++++++++++++++++ .../TemplateProjectBuildPipelineBuilder.cs | 5 +++ .../Cli/ProjectBuilding/ProjectBuildArgs.cs | 7 +++- 4 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ConnectionStringChangeStep.cs diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs index c7d7b4a87b..86345da19c 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs @@ -73,6 +73,14 @@ namespace Volo.Abp.Cli.Commands Logger.LogInformation("UI Framework: " + uiFramework); } + var connectionString = commandLineArgs + .Options + .GetOrNull(Options.ConnectionString.Short, Options.ConnectionString.Long); + if (connectionString != null) + { + Logger.LogInformation("Connection string: " + connectionString); + } + var mobileApp = GetMobilePreference(commandLineArgs); if (mobileApp != MobileApp.None) { @@ -122,7 +130,8 @@ namespace Volo.Abp.Cli.Commands mobileApp, gitHubLocalRepositoryPath, templateSource, - commandLineArgs.Options + commandLineArgs.Options, + connectionString ) ); @@ -309,6 +318,12 @@ namespace Volo.Abp.Cli.Commands public const string Long = "template-source"; } + public static class ConnectionString + { + public const string Short = "cs"; + public const string Long = "connection-string"; + } + public static class CreateSolutionFolder { public const string Short = "csf"; diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ConnectionStringChangeStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ConnectionStringChangeStep.cs new file mode 100644 index 0000000000..ba42500007 --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ConnectionStringChangeStep.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using Volo.Abp.Cli.ProjectBuilding.Files; + +namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps +{ + public class ConnectionStringChangeStep : ProjectBuildPipelineStep + { + public override void Execute(ProjectBuildContext context) + { + var newConnectionString = context.BuildArgs.ConnectionString; + + var appSettingsJsonFiles = context.Files.Where(f => + f.Name.EndsWith("appsettings.json", StringComparison.OrdinalIgnoreCase)); + + foreach (var appSettingsJson in appSettingsJsonFiles) + { + var appSettingsObject = JsonConvert.DeserializeObject(appSettingsJson.Content); + var oldConnectionString = appSettingsObject.ConnectionStrings.Default; + appSettingsJson.ReplaceText(oldConnectionString, newConnectionString); + } + } + + public class ConnectionStringModel + { + public string Default { get; set; } + } + + public class AppSettingsConnectionStringModel + { + public ConnectionStringModel ConnectionStrings { get; set; } + + public string GetDefaultConnectionString() + { + return ConnectionStrings?.Default; + } + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/TemplateProjectBuildPipelineBuilder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/TemplateProjectBuildPipelineBuilder.cs index a68de8ef62..607ec4f1c7 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/TemplateProjectBuildPipelineBuilder.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/TemplateProjectBuildPipelineBuilder.cs @@ -28,6 +28,11 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building pipeline.Steps.Add(new RemoveRootFolderStep()); } + if (context.BuildArgs.ConnectionString != null) + { + pipeline.Steps.Add(new ConnectionStringChangeStep()); + } + pipeline.Steps.Add(new CreateProjectResultZipStep()); return pipeline; diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ProjectBuildArgs.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ProjectBuildArgs.cs index ea207bc426..70cb98dff0 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ProjectBuildArgs.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ProjectBuildArgs.cs @@ -27,6 +27,9 @@ namespace Volo.Abp.Cli.ProjectBuilding [CanBeNull] public string TemplateSource { get; set; } + [CanBeNull] + public string ConnectionString { get; set; } + [NotNull] public Dictionary ExtraProperties { get; set; } @@ -39,7 +42,8 @@ namespace Volo.Abp.Cli.ProjectBuilding MobileApp? mobileApp = null, [CanBeNull] string abpGitHubLocalRepositoryPath = null, [CanBeNull] string templateSource = null, - Dictionary extraProperties = null) + Dictionary extraProperties = null, + [CanBeNull] string connectionString = null) { SolutionName = Check.NotNull(solutionName, nameof(solutionName)); TemplateName = templateName; @@ -50,6 +54,7 @@ namespace Volo.Abp.Cli.ProjectBuilding AbpGitHubLocalRepositoryPath = abpGitHubLocalRepositoryPath; TemplateSource = templateSource; ExtraProperties = extraProperties ?? new Dictionary(); + ConnectionString = connectionString; } } } \ No newline at end of file From 25c50b295c3f0dd8cb7c18bc2623e17dac7da008 Mon Sep 17 00:00:00 2001 From: Alper Ebicoglu Date: Mon, 30 Mar 2020 18:38:20 +0300 Subject: [PATCH 2/8] Update NewCommand.cs --- .../Volo/Abp/Cli/Commands/NewCommand.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs index 86345da19c..222bfee307 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs @@ -73,9 +73,7 @@ namespace Volo.Abp.Cli.Commands Logger.LogInformation("UI Framework: " + uiFramework); } - var connectionString = commandLineArgs - .Options - .GetOrNull(Options.ConnectionString.Short, Options.ConnectionString.Long); + var connectionString = GetConnectionString(commandLineArgs); if (connectionString != null) { Logger.LogInformation("Connection string: " + connectionString); @@ -177,6 +175,12 @@ namespace Volo.Abp.Cli.Commands Logger.LogInformation($"'{projectName}' has been successfully created to '{outputFolder}'"); } + private static string GetConnectionString(CommandLineArgs commandLineArgs) + { + var connectionString = commandLineArgs.Options.GetOrNull(Options.ConnectionString.Short, Options.ConnectionString.Long); + return string.IsNullOrWhiteSpace(connectionString) ? null : connectionString; + } + public string GetUsageInfo() { var sb = new StringBuilder(); From 733dbd44606e892689a0adad4e3c48e034b353df Mon Sep 17 00:00:00 2001 From: Alper Ebicoglu Date: Mon, 30 Mar 2020 19:21:21 +0300 Subject: [PATCH 3/8] Update ConnectionStringChangeStep.cs --- .../Steps/ConnectionStringChangeStep.cs | 31 ++++++++----------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ConnectionStringChangeStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ConnectionStringChangeStep.cs index ba42500007..d12bc45866 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ConnectionStringChangeStep.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ConnectionStringChangeStep.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using Volo.Abp.Cli.ProjectBuilding.Files; namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps @@ -10,31 +12,24 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps { public override void Execute(ProjectBuildContext context) { - var newConnectionString = context.BuildArgs.ConnectionString; + var newConnectionString = "\"Default\": \"" + context.BuildArgs.ConnectionString + "\""; var appSettingsJsonFiles = context.Files.Where(f => f.Name.EndsWith("appsettings.json", StringComparison.OrdinalIgnoreCase)); foreach (var appSettingsJson in appSettingsJsonFiles) { - var appSettingsObject = JsonConvert.DeserializeObject(appSettingsJson.Content); - var oldConnectionString = appSettingsObject.ConnectionStrings.Default; - appSettingsJson.ReplaceText(oldConnectionString, newConnectionString); - } - } - - public class ConnectionStringModel - { - public string Default { get; set; } - } - - public class AppSettingsConnectionStringModel - { - public ConnectionStringModel ConnectionStrings { get; set; } + try + { + var jsonObject = JObject.Parse(appSettingsJson.Content); + var defaultConnectionString = ((Newtonsoft.Json.Linq.JContainer)jsonObject["ConnectionStrings"]).First.ToString(); - public string GetDefaultConnectionString() - { - return ConnectionStrings?.Default; + appSettingsJson.ReplaceText(defaultConnectionString, newConnectionString); + } + catch (Exception ex) + { + Console.WriteLine("Cannot change the connection string in " + appSettingsJson.Name + ". Error: " + ex.Message); + } } } } From 765d967a42d5c37e5c063226d8cd09b3f10b2e06 Mon Sep 17 00:00:00 2001 From: Alper Ebicoglu Date: Mon, 30 Mar 2020 19:25:54 +0300 Subject: [PATCH 4/8] Update ConnectionStringChangeStep.cs --- .../Building/Steps/ConnectionStringChangeStep.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ConnectionStringChangeStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ConnectionStringChangeStep.cs index d12bc45866..ea53868812 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ConnectionStringChangeStep.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ConnectionStringChangeStep.cs @@ -22,7 +22,9 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps try { var jsonObject = JObject.Parse(appSettingsJson.Content); - var defaultConnectionString = ((Newtonsoft.Json.Linq.JContainer)jsonObject["ConnectionStrings"]).First.ToString(); + var connectionStringContainer = (JContainer)jsonObject["ConnectionStrings"]; + var firstConnectionString = connectionStringContainer.First; + var defaultConnectionString = firstConnectionString.ToString(); appSettingsJson.ReplaceText(defaultConnectionString, newConnectionString); } From bc547cd05ac9fddffb3793d42fbbd85410adc08d Mon Sep 17 00:00:00 2001 From: Alper Ebicoglu Date: Mon, 30 Mar 2020 20:37:33 +0300 Subject: [PATCH 5/8] Add ReadAllLinesAsync method. --- .../Steps/ConnectionStringChangeStep.cs | 24 +++++++++- .../Volo.Abp.Core/Volo/Abp/IO/FileHelper.cs | 48 ++++++++++++++++++- 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ConnectionStringChangeStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ConnectionStringChangeStep.cs index ea53868812..491955650b 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ConnectionStringChangeStep.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ConnectionStringChangeStep.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Text; using System.Text.RegularExpressions; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -21,7 +23,8 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps { try { - var jsonObject = JObject.Parse(appSettingsJson.Content); + var appSettingJsonContentWithoutBom = GetStringWithoutBom(appSettingsJson.Bytes); + var jsonObject = JObject.Parse(appSettingJsonContentWithoutBom); var connectionStringContainer = (JContainer)jsonObject["ConnectionStrings"]; var firstConnectionString = connectionStringContainer.First; var defaultConnectionString = firstConnectionString.ToString(); @@ -34,5 +37,24 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps } } } + + private static string GetStringWithoutBom(byte[] bytes) + { + if (bytes == null) + { + return null; + } + + var hasBom = bytes.Length >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF; + + if (hasBom) + { + return Encoding.UTF8.GetString(bytes, 3, bytes.Length - 3); + } + else + { + return Encoding.UTF8.GetString(bytes); + } + } } } diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/IO/FileHelper.cs b/framework/src/Volo.Abp.Core/Volo/Abp/IO/FileHelper.cs index 21ca7c2086..4c258dfbeb 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/IO/FileHelper.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/IO/FileHelper.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Text; using System.Threading.Tasks; @@ -73,6 +74,51 @@ namespace Volo.Abp.IO } } - //TODO: ReadAllLinesAsync + /// + /// Opens a text file, reads all lines of the file, and then closes the file. + /// + /// The file to open for reading. + /// Encoding of the file. Default is UTF8 + /// Specifies how the operating system should open a file. Default is Open + /// Defines constants for read, write, or read/write access to a file. Default is Read + /// Contains constants for controlling the kind of access other FileStream objects can have to the same file. Default is Read + /// Length of StreamReader buffer. Default is 4096. + /// Indicates FileStream options. Default is Asynchronous (The file is to be used for asynchronous reading.) and SequentialScan (The file is to be accessed sequentially from beginning to end.) + /// A string containing all lines of the file. + public static async Task ReadAllLinesAsync(string path, + Encoding encoding = null, + FileMode fileMode = FileMode.Open, + FileAccess fileAccess = FileAccess.Read, + FileShare fileShare = FileShare.Read, + int bufferSize = 4096, + FileOptions fileOptions = FileOptions.Asynchronous | FileOptions.SequentialScan) + { + if (encoding == null) + { + encoding = Encoding.UTF8; + } + + var lines = new List(); + + using (var stream = new FileStream( + path, + fileMode, + fileAccess, + fileShare, + bufferSize, + fileOptions)) + { + using (var reader = new StreamReader(stream, encoding)) + { + string line; + while ((line = await reader.ReadLineAsync()) != null) + { + lines.Add(line); + } + } + } + + return lines.ToArray(); + } } } \ No newline at end of file From 619d7665c40f4bcf2346b93d04d8452d7e29ccc8 Mon Sep 17 00:00:00 2001 From: Alper Ebicoglu Date: Mon, 30 Mar 2020 20:51:43 +0300 Subject: [PATCH 6/8] Add ConvertFromBytesWithoutBom to convert BOM encoded texts without BOM --- .../Volo/Abp/Text/StringHelper.cs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 framework/src/Volo.Abp.Core/Volo/Abp/Text/StringHelper.cs diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Text/StringHelper.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Text/StringHelper.cs new file mode 100644 index 0000000000..c6be5db02b --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Text/StringHelper.cs @@ -0,0 +1,37 @@ +using System.Text; + +namespace Volo.Abp.Text +{ + public class StringHelper + { + /// + /// Converts a byte[] to string without BOM (byte order mark). + /// + /// The byte[] to be converted to string + /// The encoding to get string. Default is UTF8 + /// + public static string ConvertFromBytesWithoutBom(byte[] bytes, Encoding encoding = null) + { + if (bytes == null) + { + return null; + } + + if (encoding == null) + { + encoding = Encoding.UTF8; + } + + var hasBom = bytes.Length >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF; + + if (hasBom) + { + return encoding.GetString(bytes, 3, bytes.Length - 3); + } + else + { + return encoding.GetString(bytes); + } + } + } +} \ No newline at end of file From b591a55bf28c3f7c829b35336d4920610d6d3599 Mon Sep 17 00:00:00 2001 From: Alper Ebicoglu Date: Mon, 30 Mar 2020 20:52:11 +0300 Subject: [PATCH 7/8] Add ReadFileWithoutBom to get content when the file encoded with Byte Order Mark. --- .../src/Volo.Abp.Core/Volo/Abp/IO/FileHelper.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/IO/FileHelper.cs b/framework/src/Volo.Abp.Core/Volo/Abp/IO/FileHelper.cs index 4c258dfbeb..a945784352 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/IO/FileHelper.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/IO/FileHelper.cs @@ -4,6 +4,7 @@ using System.IO; using System.Text; using System.Threading.Tasks; using JetBrains.Annotations; +using Volo.Abp.Text; namespace Volo.Abp.IO { @@ -120,5 +121,17 @@ namespace Volo.Abp.IO return lines.ToArray(); } + + /// + /// Opens a text file, reads content without BOM + /// + /// The file to open for reading. + /// A string containing all lines of the file. + public static async Task ReadFileWithoutBomAsync(string path) + { + var content = await ReadAllBytesAsync(path); + + return StringHelper.ConvertFromBytesWithoutBom(content); + } } } \ No newline at end of file From 0b2546ed04838fdcf84ecb2fccdec7d6f9f19bbb Mon Sep 17 00:00:00 2001 From: Alper Ebicoglu Date: Mon, 30 Mar 2020 21:09:54 +0300 Subject: [PATCH 8/8] closes #3208 --- .../Steps/ConnectionStringChangeStep.cs | 70 ++++++++++++------- 1 file changed, 43 insertions(+), 27 deletions(-) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ConnectionStringChangeStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ConnectionStringChangeStep.cs index 491955650b..02bc8219b8 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ConnectionStringChangeStep.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ConnectionStringChangeStep.cs @@ -7,29 +7,64 @@ using System.Text.RegularExpressions; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Volo.Abp.Cli.ProjectBuilding.Files; +using Volo.Abp.Text; namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps { public class ConnectionStringChangeStep : ProjectBuildPipelineStep { + private const string DefaultConnectionStringKey = "Default"; + public override void Execute(ProjectBuildContext context) { - var newConnectionString = "\"Default\": \"" + context.BuildArgs.ConnectionString + "\""; - var appSettingsJsonFiles = context.Files.Where(f => - f.Name.EndsWith("appsettings.json", StringComparison.OrdinalIgnoreCase)); + f.Name.EndsWith("appsettings.json", StringComparison.OrdinalIgnoreCase)) + .ToArray(); + + if (!appSettingsJsonFiles.Any()) + { + return; + } + + var newConnectionString = $"\"{DefaultConnectionStringKey}: \"{context.BuildArgs.ConnectionString}\""; foreach (var appSettingsJson in appSettingsJsonFiles) { try { - var appSettingJsonContentWithoutBom = GetStringWithoutBom(appSettingsJson.Bytes); + var appSettingJsonContentWithoutBom = StringHelper.ConvertFromBytesWithoutBom(appSettingsJson.Bytes); var jsonObject = JObject.Parse(appSettingJsonContentWithoutBom); - var connectionStringContainer = (JContainer)jsonObject["ConnectionStrings"]; - var firstConnectionString = connectionStringContainer.First; - var defaultConnectionString = firstConnectionString.ToString(); - appSettingsJson.ReplaceText(defaultConnectionString, newConnectionString); + var connectionStringContainer = (JContainer)jsonObject?["ConnectionStrings"]; + if (connectionStringContainer == null) + { + continue; + } + + if (!connectionStringContainer.Any()) + { + continue; + } + + var connectionStrings = connectionStringContainer.ToList(); + + foreach (var connectionString in connectionStrings) + { + var property = ((JProperty)connectionString); + var connectionStringName = property.Name; + + if (connectionStringName == DefaultConnectionStringKey) + { + var defaultConnectionString = property.ToString(); + if (defaultConnectionString == null) + { + continue; + } + + appSettingsJson.ReplaceText(defaultConnectionString, newConnectionString); + break; + } + } } catch (Exception ex) { @@ -37,24 +72,5 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps } } } - - private static string GetStringWithoutBom(byte[] bytes) - { - if (bytes == null) - { - return null; - } - - var hasBom = bytes.Length >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF; - - if (hasBom) - { - return Encoding.UTF8.GetString(bytes, 3, bytes.Length - 3); - } - else - { - return Encoding.UTF8.GetString(bytes); - } - } } }