diff --git a/src/dotnet-svcutil/lib/src/CommandProcessorOptions.cs b/src/dotnet-svcutil/lib/src/CommandProcessorOptions.cs
index 8206ca0572d..05403a36364 100644
--- a/src/dotnet-svcutil/lib/src/CommandProcessorOptions.cs
+++ b/src/dotnet-svcutil/lib/src/CommandProcessorOptions.cs
@@ -95,6 +95,7 @@ internal class CommandSwitches
public readonly CommandSwitch AcceptCertificate = new CommandSwitch(AccecptCertificateKey, "ac", SwitchType.Flag);
public readonly CommandSwitch ServiceContract = new CommandSwitch(ServiceContractKey, "sc", SwitchType.Flag);
public readonly CommandSwitch Language = new CommandSwitch(LanguageKey, "l", SwitchType.SingletonValue, OperationalContext.Global);
+ public readonly CommandSwitch SeparateFiles = new CommandSwitch(SeparateFilesKey, "sf", SwitchType.Flag);
public void Init() { } // provided as a way to get the static class Switches loaded early.
}
diff --git a/src/dotnet-svcutil/lib/src/HelpGenerator.cs b/src/dotnet-svcutil/lib/src/HelpGenerator.cs
index 38621edebc8..25139b0b372 100644
--- a/src/dotnet-svcutil/lib/src/HelpGenerator.cs
+++ b/src/dotnet-svcutil/lib/src/HelpGenerator.cs
@@ -72,7 +72,8 @@ private static void WriteCodeGenerationHelp()
ArgumentInfo.CreateParameterHelpInfo(CommandProcessorOptions.Switches.TargetFramework.Name, SR.ParametersTargetFramework, string.Format(SR.HelpTargetFrameworkFormat, CommandProcessorOptions.Switches.TargetFramework.Abbreviation)),
ArgumentInfo.CreateFlagHelpInfo( CommandProcessorOptions.Switches.AcceptCertificate.Name, string.Format(SR.HelpAcceptCertificateFormat, CommandProcessorOptions.Switches.AcceptCertificate.Abbreviation)),
ArgumentInfo.CreateFlagHelpInfo( CommandProcessorOptions.Switches.ServiceContract.Name, string.Format(SR.HelpServiceContractFormat, CommandProcessorOptions.Switches.ServiceContract.Abbreviation)),
- ArgumentInfo.CreateFlagHelpInfo( CommandProcessorOptions.Switches.Language.Name, string.Format(SR.HelpLanguage, CommandProcessorOptions.Switches.Language.Abbreviation))
+ ArgumentInfo.CreateFlagHelpInfo( CommandProcessorOptions.Switches.Language.Name, string.Format(SR.HelpLanguage, CommandProcessorOptions.Switches.Language.Abbreviation)),
+ ArgumentInfo.CreateFlagHelpInfo( CommandProcessorOptions.Switches.SeparateFiles.Name, string.Format(SR.HelpSeparateFiles, CommandProcessorOptions.Switches.SeparateFiles.Abbreviation))
}
};
diff --git a/src/dotnet-svcutil/lib/src/SR.resx b/src/dotnet-svcutil/lib/src/SR.resx
index 4bb86359434..27e58c89aa6 100644
--- a/src/dotnet-svcutil/lib/src/SR.resx
+++ b/src/dotnet-svcutil/lib/src/SR.resx
@@ -640,4 +640,7 @@ Your credentials will be sent to the server in clear text.
The programming language to use for generating code. Examples of language names to use are CS and VB. Default: C#. (Short Form: -{0})
+
+ Creates separate files for each type in the specified output directory. (Short Form: -{0})
+
\ No newline at end of file
diff --git a/src/dotnet-svcutil/lib/src/Shared/Options/UpdateOptions.cs b/src/dotnet-svcutil/lib/src/Shared/Options/UpdateOptions.cs
index 8fe3eee7ffb..c5183c0bb58 100644
--- a/src/dotnet-svcutil/lib/src/Shared/Options/UpdateOptions.cs
+++ b/src/dotnet-svcutil/lib/src/Shared/Options/UpdateOptions.cs
@@ -29,6 +29,7 @@ internal partial class UpdateOptions : ApplicationOptions
public const string TypeReuseModeKey = "typeReuseMode";
public const string WrappedKey = "wrapped";
public const string LanguageKey = "language";
+ public const string SeparateFilesKey = "separateFiles";
#endregion
#region properties
@@ -49,6 +50,7 @@ internal partial class UpdateOptions : ApplicationOptions
public TypeReuseMode? TypeReuseMode { get { return GetValue(TypeReuseModeKey); } set { SetValue(TypeReuseModeKey, value); } }
public bool? Wrapped { get { return GetValue(WrappedKey); } set { SetValue(WrappedKey, value); } }
public string Language { get { return GetValue(LanguageKey); } set { SetValue(LanguageKey, value); } }
+ public bool? SeparateFiles { get { return GetValue(SeparateFilesKey); } set { SetValue(SeparateFilesKey, value); } }
#endregion
public UpdateOptions()
@@ -70,7 +72,8 @@ public UpdateOptions()
new SingleValueOption(TargetFrameworkKey),
new SingleValueOption(TypeReuseModeKey),
new SingleValueOption(WrappedKey),
- new SingleValueOption(LanguageKey));
+ new SingleValueOption(LanguageKey),
+ new SingleValueOption(SeparateFilesKey) { SerializationName = "separateFiles" });
}
public static UpdateOptions FromFile(string filePath, bool throwOnError = true)
diff --git a/src/dotnet-svcutil/lib/src/Tool.cs b/src/dotnet-svcutil/lib/src/Tool.cs
index 6027a3c43fa..dbf7fe825fa 100644
--- a/src/dotnet-svcutil/lib/src/Tool.cs
+++ b/src/dotnet-svcutil/lib/src/Tool.cs
@@ -2,8 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-using Microsoft.Tools.ServiceModel.Svcutil.Metadata;
using System;
+using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
@@ -13,6 +13,10 @@
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.CodeDom;
+using Microsoft.Tools.ServiceModel.Svcutil.Metadata;
+using Newtonsoft.Json.Linq;
+using static System.ServiceModel.Channels.RequestReplyCorrelator;
using DcNS = System.Runtime.Serialization;
namespace Microsoft.Tools.ServiceModel.Svcutil
@@ -244,17 +248,75 @@ await serviceDescriptor.ImportMetadataAsync(
using (await SafeLogger.WriteStartOperationAsync(options.Logger, "Processing Code DOM ...").ConfigureAwait(false))
{
ToolConsole.WriteLine(SR.GeneratingFiles);
+ if (options.SeparateFiles == true)
+ {
+ var originalOutputFile = options.OutputFile;
+ try
+ {
+ var generatedOutputPaths = new HashSet(StringComparer.OrdinalIgnoreCase);
+ foreach (CodeNamespace @namespace in importModule.CodeCompileUnit.Namespaces)
+ {
+ foreach (CodeTypeDeclaration type in @namespace.Types)
+ {
+ var namespacePrefix = GetSafeNamespaceFilePrefix(@namespace.Name);
+ var outputFileName = $"{namespacePrefix}.{type.Name}{CodeSerializer.GetOutputFileExtension(options)}";
+ var outputPath = Path.Combine(options.OutputDir.FullName, outputFileName);
+ if (!generatedOutputPaths.Add(outputPath))
+ {
+ throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
+ "A generated output file collision was detected for type '{0}' in namespace '{1}'. The file path '{2}' is already assigned to another generated type.",
+ type.Name, @namespace.Name, outputPath));
+ }
+ options.OutputFile = new FileInfo(outputPath);
+ CodeSerializer codeSerializer = new CodeSerializer(options, serviceDescriptor.MetadataDocuments);
+ CodeCompileUnit compileUnit = new CodeCompileUnit();
+ CodeNamespace splitNamespace = new CodeNamespace(@namespace.Name);
+
+ // Transfer the assembly attributes, referenced assemblies, user data and directives to each split compile unit to ensure the generated code is correct.
+ compileUnit.AssemblyCustomAttributes.AddRange(importModule.CodeCompileUnit.AssemblyCustomAttributes);
+ compileUnit.StartDirectives.AddRange(importModule.CodeCompileUnit.StartDirectives);
+ compileUnit.EndDirectives.AddRange(importModule.CodeCompileUnit.EndDirectives);
+ compileUnit.ReferencedAssemblies.AddRange(importModule.CodeCompileUnit.ReferencedAssemblies.Cast().ToArray());
+ foreach (DictionaryEntry pair in importModule.CodeCompileUnit.UserData)
+ {
+ compileUnit.UserData.Add(pair.Key, pair.Value);
+ }
+
+ compileUnit.Namespaces.Add(splitNamespace);
+ splitNamespace.Types.Add(type);
+ var filePath = codeSerializer.Save(compileUnit);
+ ToolConsole.WriteLineIf(options.ToolContext != OperationalContext.Infrastructure, filePath, LogTag.Important);
+ }
+ }
+ }
+ finally
+ {
+ options.OutputFile = originalOutputFile;
+ }
+ }
+ else
+ {
+ CodeSerializer codeSerializer = new CodeSerializer(options, serviceDescriptor.MetadataDocuments);
+ var filePath = codeSerializer.Save(importModule.CodeCompileUnit);
- CodeSerializer codeSerializer = new CodeSerializer(options, serviceDescriptor.MetadataDocuments);
- var filePath = codeSerializer.Save(importModule.CodeCompileUnit);
-
- // When in Infrastructure mode (WCF CS) it is assumed the output file path have been provided so no need to display it.
- ToolConsole.WriteLineIf(options.ToolContext != OperationalContext.Infrastructure, filePath, LogTag.Important);
+ // When in Infrastructure mode (WCF CS) it is assumed the output file path have been provided so no need to display it.
+ ToolConsole.WriteLineIf(options.ToolContext != OperationalContext.Infrastructure, filePath, LogTag.Important);
+ }
}
return ToolConsole.ExitCode;
}
+ private static string GetSafeNamespaceFilePrefix(string namespaceName)
+ {
+ var prefix = string.IsNullOrWhiteSpace(namespaceName) ? "Global" : namespaceName;
+ foreach (var invalidChar in Path.GetInvalidFileNameChars())
+ {
+ prefix = prefix.Replace(invalidChar, '_');
+ }
+ return prefix.Replace('.', '_');
+ }
+
private static bool IsSuccess(int result)
{
return result == (int)ToolExitCode.Success || result == (int)ToolExitCode.ValidationErrorTurnedWarning;
diff --git a/src/dotnet-svcutil/lib/src/xlf/SR.cs.xlf b/src/dotnet-svcutil/lib/src/xlf/SR.cs.xlf
index 2d0e81a727f..3400553e826 100644
--- a/src/dotnet-svcutil/lib/src/xlf/SR.cs.xlf
+++ b/src/dotnet-svcutil/lib/src/xlf/SR.cs.xlf
@@ -617,6 +617,11 @@ Identifikátor dokumentu: {0}
Vygenerovat kód pro kontrakty služeb Třída klienta se nevygeneruje. (Krátký tvar: -{0})
+
+ Creates separate files for each type in the specified output directory. (Short Form: -{0})
+ Creates separate files for each type in the specified output directory. (Short Form: -{0})
+
+ Generate synchronous methods for operations in addition to async. (Short Form: -{0})Kromě asynchronních metod generovat pro operace i synchronní metody (krátký tvar: -{0})
@@ -878,4 +883,4 @@ Vaše přihlašovací údaje se odešlou na server v podobě prostého textu.