From 0f5f0be800980215cf13085d051b2065387b37bb Mon Sep 17 00:00:00 2001 From: mr-sven Date: Wed, 4 Feb 2026 19:03:52 +0100 Subject: [PATCH 1/5] Implement command switch for separating type files --- .../lib/src/CommandProcessorOptions.cs | 1 + src/dotnet-svcutil/lib/src/HelpGenerator.cs | 3 +- src/dotnet-svcutil/lib/src/SR.resx | 3 ++ .../lib/src/Shared/Options/UpdateOptions.cs | 5 ++- src/dotnet-svcutil/lib/src/Tool.cs | 32 +++++++++++++++---- src/dotnet-svcutil/lib/src/xlf/SR.cs.xlf | 7 +++- src/dotnet-svcutil/lib/src/xlf/SR.de.xlf | 7 +++- src/dotnet-svcutil/lib/src/xlf/SR.es.xlf | 7 +++- src/dotnet-svcutil/lib/src/xlf/SR.fr.xlf | 7 +++- src/dotnet-svcutil/lib/src/xlf/SR.it.xlf | 7 +++- src/dotnet-svcutil/lib/src/xlf/SR.ja.xlf | 7 +++- src/dotnet-svcutil/lib/src/xlf/SR.ko.xlf | 7 +++- src/dotnet-svcutil/lib/src/xlf/SR.pl.xlf | 7 +++- src/dotnet-svcutil/lib/src/xlf/SR.pt-BR.xlf | 7 +++- src/dotnet-svcutil/lib/src/xlf/SR.ru.xlf | 7 +++- src/dotnet-svcutil/lib/src/xlf/SR.tr.xlf | 7 +++- src/dotnet-svcutil/lib/src/xlf/SR.zh-Hans.xlf | 7 +++- src/dotnet-svcutil/lib/src/xlf/SR.zh-Hant.xlf | 7 +++- 18 files changed, 114 insertions(+), 21 deletions(-) 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 39db9b0038e..713dfe2acc0 100644 --- a/src/dotnet-svcutil/lib/src/SR.resx +++ b/src/dotnet-svcutil/lib/src/SR.resx @@ -637,4 +637,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..eb671237a72 100644 --- a/src/dotnet-svcutil/lib/src/Tool.cs +++ b/src/dotnet-svcutil/lib/src/Tool.cs @@ -2,7 +2,6 @@ // 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.Generic; using System.Diagnostics; @@ -13,6 +12,8 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeDom; +using Microsoft.Tools.ServiceModel.Svcutil.Metadata; using DcNS = System.Runtime.Serialization; namespace Microsoft.Tools.ServiceModel.Svcutil @@ -244,12 +245,31 @@ await serviceDescriptor.ImportMetadataAsync( using (await SafeLogger.WriteStartOperationAsync(options.Logger, "Processing Code DOM ...").ConfigureAwait(false)) { ToolConsole.WriteLine(SR.GeneratingFiles); + if (options.SeparateFiles == true) + { + foreach (CodeNamespace @namespace in importModule.CodeCompileUnit.Namespaces) + { + foreach (CodeTypeDeclaration type in @namespace.Types) + { + options.OutputFile = new FileInfo(Path.Combine(options.OutputDir.FullName, $"{type.Name}{CodeSerializer.GetOutputFileExtension(options)}")); + CodeSerializer codeSerializer = new CodeSerializer(options, serviceDescriptor.MetadataDocuments); + CodeCompileUnit compileUnit = new CodeCompileUnit(); + CodeNamespace splitNamespace = new CodeNamespace(@namespace.Name); + compileUnit.Namespaces.Add(splitNamespace); + splitNamespace.Types.Add(type); + var filePath = codeSerializer.Save(compileUnit); + ToolConsole.WriteLine(filePath, LogTag.Important); + } + } + } + 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; diff --git a/src/dotnet-svcutil/lib/src/xlf/SR.cs.xlf b/src/dotnet-svcutil/lib/src/xlf/SR.cs.xlf index 53fd7857951..b979a59d17b 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}) @@ -873,4 +878,4 @@ Vaše přihlašovací údaje se odešlou na server v podobě prostého textu. - \ No newline at end of file + diff --git a/src/dotnet-svcutil/lib/src/xlf/SR.de.xlf b/src/dotnet-svcutil/lib/src/xlf/SR.de.xlf index 31775ab3fe7..36f26f79cc9 100644 --- a/src/dotnet-svcutil/lib/src/xlf/SR.de.xlf +++ b/src/dotnet-svcutil/lib/src/xlf/SR.de.xlf @@ -617,6 +617,11 @@ Dokumentbezeichner: "{0}". Generieren Sie Code für Dienstleistungsverträge. Die Clientklasse wird nicht generiert. (Kurzform: -{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}) Hiermit werden synchrone Methoden für Vorgänge generiert, die zusätzlich zu asynchronen Vorgängen ausgeführt werden. (Kurzform: -{0}) @@ -873,4 +878,4 @@ Die Anmeldeinformationen werden in Klartext an den Server gesendet. - \ No newline at end of file + diff --git a/src/dotnet-svcutil/lib/src/xlf/SR.es.xlf b/src/dotnet-svcutil/lib/src/xlf/SR.es.xlf index 10d1a0dd041..c522593ca9d 100644 --- a/src/dotnet-svcutil/lib/src/xlf/SR.es.xlf +++ b/src/dotnet-svcutil/lib/src/xlf/SR.es.xlf @@ -617,6 +617,11 @@ Identificador del documento: "{0}". Generar código para Service Contracts. La clase cliente no se generará. (Forma corta: {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}) Genere métodos sincrónicos para las operaciones además de los asincrónicos. (Forma corta: -{0}) @@ -873,4 +878,4 @@ Las credenciales se enviarán al servidor en texto no cifrado. - \ No newline at end of file + diff --git a/src/dotnet-svcutil/lib/src/xlf/SR.fr.xlf b/src/dotnet-svcutil/lib/src/xlf/SR.fr.xlf index 66db4c81738..4084f53b293 100644 --- a/src/dotnet-svcutil/lib/src/xlf/SR.fr.xlf +++ b/src/dotnet-svcutil/lib/src/xlf/SR.fr.xlf @@ -617,6 +617,11 @@ Identificateur de document : '{0}'. Générez du code pour les contrats de service. La classe cliente ne sera pas générée. (Forme abrégée : -{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}) Générez des méthodes synchrones pour les opérations en plus des méthodes asynchrones. (Forme abrégée : -{0}) @@ -873,4 +878,4 @@ Vos informations d'identification vont être envoyées au serveur en texte clair - \ No newline at end of file + diff --git a/src/dotnet-svcutil/lib/src/xlf/SR.it.xlf b/src/dotnet-svcutil/lib/src/xlf/SR.it.xlf index 16d90b20e4c..a785efb5185 100644 --- a/src/dotnet-svcutil/lib/src/xlf/SR.it.xlf +++ b/src/dotnet-svcutil/lib/src/xlf/SR.it.xlf @@ -617,6 +617,11 @@ Identificatore di documento: '{0}'. Generare il codice per i contratti di servizio. La classe client non verrà generata. (Forma breve: -{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}) Genera metodi sincroni per le operazioni oltre a quelli asincroni. Forma breve: -{0} @@ -873,4 +878,4 @@ Le credenziali verranno inviate al server come testo non crittografato. - \ No newline at end of file + diff --git a/src/dotnet-svcutil/lib/src/xlf/SR.ja.xlf b/src/dotnet-svcutil/lib/src/xlf/SR.ja.xlf index c9233a0e974..df56c434a7f 100644 --- a/src/dotnet-svcutil/lib/src/xlf/SR.ja.xlf +++ b/src/dotnet-svcutil/lib/src/xlf/SR.ja.xlf @@ -617,6 +617,11 @@ Document Identifier: '{0}'. サービス コントラクトのコードを生成します。クライアント クラスは生成されません。(短い形式: -{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}) 非同期のものに加えて、操作の同期メソッドを生成します。(短縮形: -{0}) @@ -873,4 +878,4 @@ Your credentials will be sent to the server in clear text. - \ No newline at end of file + diff --git a/src/dotnet-svcutil/lib/src/xlf/SR.ko.xlf b/src/dotnet-svcutil/lib/src/xlf/SR.ko.xlf index 485c6dc756e..c1a00cc2109 100644 --- a/src/dotnet-svcutil/lib/src/xlf/SR.ko.xlf +++ b/src/dotnet-svcutil/lib/src/xlf/SR.ko.xlf @@ -617,6 +617,11 @@ Document Identifier: '{0}'. 서비스 계약에 대한 코드를 생성합니다. 클라이언트 클래스는 생성되지 않습니다. (약식: -{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}) 비동기 외에 작업에 대한 동기 메서드를 생성합니다. (약식: -{0}) @@ -873,4 +878,4 @@ Your credentials will be sent to the server in clear text. - \ No newline at end of file + diff --git a/src/dotnet-svcutil/lib/src/xlf/SR.pl.xlf b/src/dotnet-svcutil/lib/src/xlf/SR.pl.xlf index 840f98a138f..0261a6121ab 100644 --- a/src/dotnet-svcutil/lib/src/xlf/SR.pl.xlf +++ b/src/dotnet-svcutil/lib/src/xlf/SR.pl.xlf @@ -617,6 +617,11 @@ Identyfikator dokumentu: „{0}”. Generuj kod dla kontraktów usług. Klasa klienta nie zostanie wygenerowana. (Krótka wersja: —{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}) Generuj metody synchroniczne dla operacji, oprócz metod asynchronicznych. (Krótka forma: -{0}) @@ -873,4 +878,4 @@ Twoje poświadczenia zostaną wysłane do serwera jako zwykły tekst. - \ No newline at end of file + diff --git a/src/dotnet-svcutil/lib/src/xlf/SR.pt-BR.xlf b/src/dotnet-svcutil/lib/src/xlf/SR.pt-BR.xlf index 343a85ffdc5..c2dba21e53a 100644 --- a/src/dotnet-svcutil/lib/src/xlf/SR.pt-BR.xlf +++ b/src/dotnet-svcutil/lib/src/xlf/SR.pt-BR.xlf @@ -617,6 +617,11 @@ Identificador do Documento: '{0}'. Gerar código para Contratos de Serviço. A classe de cliente não será gerada. (Forma resumida: -{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}) Gere métodos síncronos para operações, além de assíncronos. (Forma Abreviada: -{0}) @@ -873,4 +878,4 @@ As credenciais serão enviadas para o servidor em texto não criptografado. - \ No newline at end of file + diff --git a/src/dotnet-svcutil/lib/src/xlf/SR.ru.xlf b/src/dotnet-svcutil/lib/src/xlf/SR.ru.xlf index 02186b1bccc..6175d3733c5 100644 --- a/src/dotnet-svcutil/lib/src/xlf/SR.ru.xlf +++ b/src/dotnet-svcutil/lib/src/xlf/SR.ru.xlf @@ -617,6 +617,11 @@ Document Identifier: '{0}'. Создание кода для контрактов на предоставление услуг. Класс клиента не будет создан. (Короткая форма: -{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}) Создавать синхронные методы для операций в дополнение к асинхронным. (Краткая форма: -{0}) @@ -873,4 +878,4 @@ Your credentials will be sent to the server in clear text. - \ No newline at end of file + diff --git a/src/dotnet-svcutil/lib/src/xlf/SR.tr.xlf b/src/dotnet-svcutil/lib/src/xlf/SR.tr.xlf index e5baaaa661c..c478c3e4ccb 100644 --- a/src/dotnet-svcutil/lib/src/xlf/SR.tr.xlf +++ b/src/dotnet-svcutil/lib/src/xlf/SR.tr.xlf @@ -617,6 +617,11 @@ Belge Tanımlayıcısı: '{0}'. Hizmet Sözleşmeleri için kod oluşturun. İstemci sınıfı oluşturulmaz. (Kısa Biçim: -{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}) İşlemler için zaman uyumsuz yöntemlerin yanı sıra zaman uyumlu yöntemler oluştur. (Kısa Biçim: -{0}) @@ -873,4 +878,4 @@ Kimlik bilgileriniz sunucuya düz metin olarak gönderilecek. - \ No newline at end of file + diff --git a/src/dotnet-svcutil/lib/src/xlf/SR.zh-Hans.xlf b/src/dotnet-svcutil/lib/src/xlf/SR.zh-Hans.xlf index ae4d13d4f51..a323b252142 100644 --- a/src/dotnet-svcutil/lib/src/xlf/SR.zh-Hans.xlf +++ b/src/dotnet-svcutil/lib/src/xlf/SR.zh-Hans.xlf @@ -617,6 +617,11 @@ Document Identifier: '{0}'. 为服务协定生成代码。将不会生成客户端类。(缩写: -{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}) 为除异步以外的操作生成同步方法。(缩写: -{0}) @@ -873,4 +878,4 @@ Your credentials will be sent to the server in clear text. - \ No newline at end of file + diff --git a/src/dotnet-svcutil/lib/src/xlf/SR.zh-Hant.xlf b/src/dotnet-svcutil/lib/src/xlf/SR.zh-Hant.xlf index 0f3492fbcf7..a990ef63bee 100644 --- a/src/dotnet-svcutil/lib/src/xlf/SR.zh-Hant.xlf +++ b/src/dotnet-svcutil/lib/src/xlf/SR.zh-Hant.xlf @@ -617,6 +617,11 @@ Document Identifier: '{0}'. 產生服務合約的程式碼。將不會產生用戶端類別。(簡短形式: -{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}) 除了非同步方法外,也會為作業產生同步方法。(簡短形式: -{0}) @@ -873,4 +878,4 @@ Your credentials will be sent to the server in clear text. - \ No newline at end of file + From 03a51aff1f8ad5fe95f136946b17db524b23a376 Mon Sep 17 00:00:00 2001 From: mr-sven Date: Mon, 13 Apr 2026 10:43:39 +0200 Subject: [PATCH 2/5] restore output file --- src/dotnet-svcutil/lib/src/Tool.cs | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/dotnet-svcutil/lib/src/Tool.cs b/src/dotnet-svcutil/lib/src/Tool.cs index eb671237a72..4284684f3cc 100644 --- a/src/dotnet-svcutil/lib/src/Tool.cs +++ b/src/dotnet-svcutil/lib/src/Tool.cs @@ -247,20 +247,28 @@ await serviceDescriptor.ImportMetadataAsync( ToolConsole.WriteLine(SR.GeneratingFiles); if (options.SeparateFiles == true) { - foreach (CodeNamespace @namespace in importModule.CodeCompileUnit.Namespaces) + var originalOutputFile = options.OutputFile; + try { - foreach (CodeTypeDeclaration type in @namespace.Types) + foreach (CodeNamespace @namespace in importModule.CodeCompileUnit.Namespaces) { - options.OutputFile = new FileInfo(Path.Combine(options.OutputDir.FullName, $"{type.Name}{CodeSerializer.GetOutputFileExtension(options)}")); - CodeSerializer codeSerializer = new CodeSerializer(options, serviceDescriptor.MetadataDocuments); - CodeCompileUnit compileUnit = new CodeCompileUnit(); - CodeNamespace splitNamespace = new CodeNamespace(@namespace.Name); - compileUnit.Namespaces.Add(splitNamespace); - splitNamespace.Types.Add(type); - var filePath = codeSerializer.Save(compileUnit); - ToolConsole.WriteLine(filePath, LogTag.Important); + foreach (CodeTypeDeclaration type in @namespace.Types) + { + options.OutputFile = new FileInfo(Path.Combine(options.OutputDir.FullName, $"{type.Name}{CodeSerializer.GetOutputFileExtension(options)}")); + CodeSerializer codeSerializer = new CodeSerializer(options, serviceDescriptor.MetadataDocuments); + CodeCompileUnit compileUnit = new CodeCompileUnit(); + CodeNamespace splitNamespace = new CodeNamespace(@namespace.Name); + compileUnit.Namespaces.Add(splitNamespace); + splitNamespace.Types.Add(type); + var filePath = codeSerializer.Save(compileUnit); + ToolConsole.WriteLine(filePath, LogTag.Important); + } } } + finally + { + options.OutputFile = originalOutputFile; + } } else { From dab071ccc8540f7f326d83c0cc0397788b6c5a22 Mon Sep 17 00:00:00 2001 From: mr-sven Date: Mon, 13 Apr 2026 10:46:27 +0200 Subject: [PATCH 3/5] check file collision and safe names --- src/dotnet-svcutil/lib/src/Tool.cs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/dotnet-svcutil/lib/src/Tool.cs b/src/dotnet-svcutil/lib/src/Tool.cs index 4284684f3cc..aab12514856 100644 --- a/src/dotnet-svcutil/lib/src/Tool.cs +++ b/src/dotnet-svcutil/lib/src/Tool.cs @@ -250,11 +250,19 @@ await serviceDescriptor.ImportMetadataAsync( var originalOutputFile = options.OutputFile; try { + var generatedOutputPaths = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (CodeNamespace @namespace in importModule.CodeCompileUnit.Namespaces) { foreach (CodeTypeDeclaration type in @namespace.Types) { - options.OutputFile = new FileInfo(Path.Combine(options.OutputDir.FullName, $"{type.Name}{CodeSerializer.GetOutputFileExtension(options)}")); + 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); @@ -283,6 +291,16 @@ await serviceDescriptor.ImportMetadataAsync( 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; From 2cfda84d0e88fd4dbb5bc633f4e9cdb5abec4aa6 Mon Sep 17 00:00:00 2001 From: mr-sven Date: Mon, 13 Apr 2026 11:20:47 +0200 Subject: [PATCH 4/5] changed logging --- src/dotnet-svcutil/lib/src/Tool.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotnet-svcutil/lib/src/Tool.cs b/src/dotnet-svcutil/lib/src/Tool.cs index aab12514856..5f62cf22a36 100644 --- a/src/dotnet-svcutil/lib/src/Tool.cs +++ b/src/dotnet-svcutil/lib/src/Tool.cs @@ -269,7 +269,7 @@ await serviceDescriptor.ImportMetadataAsync( compileUnit.Namespaces.Add(splitNamespace); splitNamespace.Types.Add(type); var filePath = codeSerializer.Save(compileUnit); - ToolConsole.WriteLine(filePath, LogTag.Important); + ToolConsole.WriteLineIf(options.ToolContext != OperationalContext.Infrastructure, filePath, LogTag.Important); } } } From 270c28919a065a5ac5436039263f3edd4ef91878 Mon Sep 17 00:00:00 2001 From: mr-sven Date: Mon, 13 Apr 2026 13:37:10 +0200 Subject: [PATCH 5/5] copy import module props --- src/dotnet-svcutil/lib/src/Tool.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/dotnet-svcutil/lib/src/Tool.cs b/src/dotnet-svcutil/lib/src/Tool.cs index 5f62cf22a36..dbf7fe825fa 100644 --- a/src/dotnet-svcutil/lib/src/Tool.cs +++ b/src/dotnet-svcutil/lib/src/Tool.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; @@ -14,6 +15,8 @@ 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 @@ -260,12 +263,25 @@ await serviceDescriptor.ImportMetadataAsync( 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)); + 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);