Skip to content
Open
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
1 change: 1 addition & 0 deletions src/dotnet-svcutil/lib/src/CommandProcessorOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
}
Expand Down
3 changes: 2 additions & 1 deletion src/dotnet-svcutil/lib/src/HelpGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
};

Expand Down
3 changes: 3 additions & 0 deletions src/dotnet-svcutil/lib/src/SR.resx
Original file line number Diff line number Diff line change
Expand Up @@ -640,4 +640,7 @@ Your credentials will be sent to the server in clear text.</value>
<data name="HelpLanguage" xml:space="preserve">
<value>The programming language to use for generating code. Examples of language names to use are CS and VB. Default: C#. (Short Form: -{0})</value>
</data>
<data name="HelpSeparateFiles" xml:space="preserve">
<value>Creates separate files for each type in the specified output directory. (Short Form: -{0})</value>
</data>
</root>
5 changes: 4 additions & 1 deletion src/dotnet-svcutil/lib/src/Shared/Options/UpdateOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -49,6 +50,7 @@ internal partial class UpdateOptions : ApplicationOptions
public TypeReuseMode? TypeReuseMode { get { return GetValue<TypeReuseMode?>(TypeReuseModeKey); } set { SetValue(TypeReuseModeKey, value); } }
public bool? Wrapped { get { return GetValue<bool?>(WrappedKey); } set { SetValue(WrappedKey, value); } }
public string Language { get { return GetValue<string>(LanguageKey); } set { SetValue(LanguageKey, value); } }
public bool? SeparateFiles { get { return GetValue<bool?>(SeparateFilesKey); } set { SetValue(SeparateFilesKey, value); } }
#endregion

public UpdateOptions()
Expand All @@ -70,7 +72,8 @@ public UpdateOptions()
new SingleValueOption<FrameworkInfo>(TargetFrameworkKey),
new SingleValueOption<TypeReuseMode>(TypeReuseModeKey),
new SingleValueOption<bool>(WrappedKey),
new SingleValueOption<string>(LanguageKey));
new SingleValueOption<string>(LanguageKey),
new SingleValueOption<bool>(SeparateFilesKey) { SerializationName = "separateFiles" });
}

public static UpdateOptions FromFile(string filePath, bool throwOnError = true)
Expand Down
74 changes: 68 additions & 6 deletions src/dotnet-svcutil/lib/src/Tool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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<string>(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<string>().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;
}
}
Comment on lines +251 to +296
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When SeparateFiles is enabled, the tool writes many .cs files but does not appear to clean up previously generated type files in the output directory. This can leave stale types behind on reruns/updates and cause compilation conflicts. Consider deleting previously generated type files (or generating into a dedicated subfolder and managing it) before writing the new set.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mconnew I'm not sure about this, cleaning up the output directory is nothing I would recommend. That should be a user task.

Comment on lines +251 to +296
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New separate-files behavior is introduced here, but there’s no accompanying test coverage verifying (1) multiple files are produced, (2) names are deterministic/collision-safe, and (3) update/rerun behavior doesn’t leave stale files. Since this repo has an existing baseline/E2E test harness, it would be good to add a targeted test case for --separateFiles/-sf.

Copilot uses AI. Check for mistakes.
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;
Expand Down
7 changes: 6 additions & 1 deletion src/dotnet-svcutil/lib/src/xlf/SR.cs.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,11 @@ Identifikátor dokumentu: {0}</target>
<target state="translated">Vygenerovat kód pro kontrakty služeb Třída klienta se nevygeneruje. (Krátký tvar: -{0})</target>
<note />
</trans-unit>
<trans-unit id="HelpSeparateFiles">
<source>Creates separate files for each type in the specified output directory. (Short Form: -{0})</source>
<target state="new">Creates separate files for each type in the specified output directory. (Short Form: -{0})</target>
Comment thread
mr-sven marked this conversation as resolved.
<note />
</trans-unit>
<trans-unit id="HelpSyncFormat">
<source>Generate synchronous methods for operations in addition to async. (Short Form: -{0})</source>
<target state="translated">Kromě asynchronních metod generovat pro operace i synchronní metody (krátký tvar: -{0})</target>
Expand Down Expand Up @@ -878,4 +883,4 @@ Vaše přihlašovací údaje se odešlou na server v podobě prostého textu.</t
</trans-unit>
</body>
</file>
</xliff>
</xliff>
7 changes: 6 additions & 1 deletion src/dotnet-svcutil/lib/src/xlf/SR.de.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,11 @@ Dokumentbezeichner: "{0}".</target>
<target state="translated">Generieren Sie Code für Dienstleistungsverträge. Die Clientklasse wird nicht generiert. (Kurzform: -{0})</target>
<note />
</trans-unit>
<trans-unit id="HelpSeparateFiles">
<source>Creates separate files for each type in the specified output directory. (Short Form: -{0})</source>
<target state="new">Creates separate files for each type in the specified output directory. (Short Form: -{0})</target>
Comment thread
mr-sven marked this conversation as resolved.
<note />
</trans-unit>
<trans-unit id="HelpSyncFormat">
<source>Generate synchronous methods for operations in addition to async. (Short Form: -{0})</source>
<target state="translated">Hiermit werden synchrone Methoden für Vorgänge generiert, die zusätzlich zu asynchronen Vorgängen ausgeführt werden. (Kurzform: -{0})</target>
Expand Down Expand Up @@ -878,4 +883,4 @@ Die Anmeldeinformationen werden in Klartext an den Server gesendet.</target>
</trans-unit>
</body>
</file>
</xliff>
</xliff>
7 changes: 6 additions & 1 deletion src/dotnet-svcutil/lib/src/xlf/SR.es.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,11 @@ Identificador del documento: "{0}".</target>
<target state="translated">Generar código para Service Contracts. La clase cliente no se generará. (Forma corta: {0})</target>
<note />
</trans-unit>
<trans-unit id="HelpSeparateFiles">
<source>Creates separate files for each type in the specified output directory. (Short Form: -{0})</source>
<target state="new">Creates separate files for each type in the specified output directory. (Short Form: -{0})</target>
Comment thread
mr-sven marked this conversation as resolved.
<note />
</trans-unit>
<trans-unit id="HelpSyncFormat">
<source>Generate synchronous methods for operations in addition to async. (Short Form: -{0})</source>
<target state="translated">Genere métodos sincrónicos para las operaciones además de los asincrónicos. (Forma corta: -{0})</target>
Expand Down Expand Up @@ -878,4 +883,4 @@ Las credenciales se enviarán al servidor en texto no cifrado.</target>
</trans-unit>
</body>
</file>
</xliff>
</xliff>
7 changes: 6 additions & 1 deletion src/dotnet-svcutil/lib/src/xlf/SR.fr.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,11 @@ Identificateur de document : '{0}'.</target>
<target state="translated">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})</target>
<note />
</trans-unit>
<trans-unit id="HelpSeparateFiles">
<source>Creates separate files for each type in the specified output directory. (Short Form: -{0})</source>
<target state="new">Creates separate files for each type in the specified output directory. (Short Form: -{0})</target>
Comment thread
mr-sven marked this conversation as resolved.
<note />
</trans-unit>
<trans-unit id="HelpSyncFormat">
<source>Generate synchronous methods for operations in addition to async. (Short Form: -{0})</source>
<target state="translated">Générez des méthodes synchrones pour les opérations en plus des méthodes asynchrones. (Forme abrégée : -{0})</target>
Expand Down Expand Up @@ -878,4 +883,4 @@ Vos informations d'identification vont être envoyées au serveur en texte clair
</trans-unit>
</body>
</file>
</xliff>
</xliff>
7 changes: 6 additions & 1 deletion src/dotnet-svcutil/lib/src/xlf/SR.it.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,11 @@ Identificatore di documento: '{0}'.</target>
<target state="translated">Generare il codice per i contratti di servizio. La classe client non verrà generata. (Forma breve: -{0})</target>
<note />
</trans-unit>
<trans-unit id="HelpSeparateFiles">
<source>Creates separate files for each type in the specified output directory. (Short Form: -{0})</source>
<target state="new">Creates separate files for each type in the specified output directory. (Short Form: -{0})</target>
Comment thread
mr-sven marked this conversation as resolved.
<note />
</trans-unit>
<trans-unit id="HelpSyncFormat">
<source>Generate synchronous methods for operations in addition to async. (Short Form: -{0})</source>
<target state="translated">Genera metodi sincroni per le operazioni oltre a quelli asincroni. Forma breve: -{0}</target>
Expand Down Expand Up @@ -878,4 +883,4 @@ Le credenziali verranno inviate al server come testo non crittografato.</target>
</trans-unit>
</body>
</file>
</xliff>
</xliff>
7 changes: 6 additions & 1 deletion src/dotnet-svcutil/lib/src/xlf/SR.ja.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,11 @@ Document Identifier: '{0}'.</source>
<target state="translated">サービス コントラクトのコードを生成します。クライアント クラスは生成されません。(短い形式: -{0})</target>
<note />
</trans-unit>
<trans-unit id="HelpSeparateFiles">
<source>Creates separate files for each type in the specified output directory. (Short Form: -{0})</source>
<target state="new">Creates separate files for each type in the specified output directory. (Short Form: -{0})</target>
Comment thread
mr-sven marked this conversation as resolved.
<note />
</trans-unit>
<trans-unit id="HelpSyncFormat">
<source>Generate synchronous methods for operations in addition to async. (Short Form: -{0})</source>
<target state="translated">非同期のものに加えて、操作の同期メソッドを生成します。(短縮形: -{0})</target>
Expand Down Expand Up @@ -878,4 +883,4 @@ Your credentials will be sent to the server in clear text.</source>
</trans-unit>
</body>
</file>
</xliff>
</xliff>
7 changes: 6 additions & 1 deletion src/dotnet-svcutil/lib/src/xlf/SR.ko.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,11 @@ Document Identifier: '{0}'.</source>
<target state="translated">서비스 계약에 대한 코드를 생성합니다. 클라이언트 클래스는 생성되지 않습니다. (약식: -{0})</target>
<note />
</trans-unit>
<trans-unit id="HelpSeparateFiles">
<source>Creates separate files for each type in the specified output directory. (Short Form: -{0})</source>
<target state="new">Creates separate files for each type in the specified output directory. (Short Form: -{0})</target>
Comment thread
mr-sven marked this conversation as resolved.
<note />
</trans-unit>
<trans-unit id="HelpSyncFormat">
<source>Generate synchronous methods for operations in addition to async. (Short Form: -{0})</source>
<target state="translated">비동기 외에 작업에 대한 동기 메서드를 생성합니다. (약식: -{0})</target>
Expand Down Expand Up @@ -878,4 +883,4 @@ Your credentials will be sent to the server in clear text.</source>
</trans-unit>
</body>
</file>
</xliff>
</xliff>
7 changes: 6 additions & 1 deletion src/dotnet-svcutil/lib/src/xlf/SR.pl.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,11 @@ Identyfikator dokumentu: „{0}”.</target>
<target state="translated">Generuj kod dla kontraktów usług. Klasa klienta nie zostanie wygenerowana. (Krótka wersja: —{0})</target>
<note />
</trans-unit>
<trans-unit id="HelpSeparateFiles">
<source>Creates separate files for each type in the specified output directory. (Short Form: -{0})</source>
<target state="new">Creates separate files for each type in the specified output directory. (Short Form: -{0})</target>
Comment thread
mr-sven marked this conversation as resolved.
<note />
</trans-unit>
<trans-unit id="HelpSyncFormat">
<source>Generate synchronous methods for operations in addition to async. (Short Form: -{0})</source>
<target state="translated">Generuj metody synchroniczne dla operacji, oprócz metod asynchronicznych. (Krótka forma: -{0})</target>
Expand Down Expand Up @@ -878,4 +883,4 @@ Twoje poświadczenia zostaną wysłane do serwera jako zwykły tekst.</target>
</trans-unit>
</body>
</file>
</xliff>
</xliff>
7 changes: 6 additions & 1 deletion src/dotnet-svcutil/lib/src/xlf/SR.pt-BR.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,11 @@ Identificador do Documento: '{0}'.</target>
<target state="translated">Gerar código para Contratos de Serviço. A classe de cliente não será gerada. (Forma resumida: -{0})</target>
<note />
</trans-unit>
<trans-unit id="HelpSeparateFiles">
<source>Creates separate files for each type in the specified output directory. (Short Form: -{0})</source>
<target state="new">Creates separate files for each type in the specified output directory. (Short Form: -{0})</target>
Comment thread
mr-sven marked this conversation as resolved.
<note />
</trans-unit>
<trans-unit id="HelpSyncFormat">
<source>Generate synchronous methods for operations in addition to async. (Short Form: -{0})</source>
<target state="translated">Gere métodos síncronos para operações, além de assíncronos. (Forma Abreviada: -{0})</target>
Expand Down Expand Up @@ -878,4 +883,4 @@ As credenciais serão enviadas para o servidor em texto não criptografado.</tar
</trans-unit>
</body>
</file>
</xliff>
</xliff>
Loading