WSDL生成CS文件实战指南 从零开始教你如何快速生成CS文件并解决常见错误与兼容性问题

引言:理解WSDL与C#客户端代码生成的重要性
Web服务描述语言(WSDL)是描述Web服务的XML格式标准,它定义了服务的位置、操作、消息格式等信息。在.NET开发中,我们经常需要调用外部的SOAP Web服务,而生成C#客户端代理类(CS文件)是实现这一目标的关键步骤。本文将从零开始,详细介绍如何使用各种工具和方法从WSDL生成C#客户端代码,并深入探讨常见错误的解决方案和兼容性处理技巧。
为什么需要生成C#客户端代码?
当我们需要调用一个外部SOAP Web服务时,手动编写所有必要的类、方法和数据结构是非常繁琐且容易出错的。通过从WSDL自动生成C#客户端代码,我们可以:
节省开发时间
:自动生成所有必要的代理类和数据类型
减少错误
:避免手动编写可能出现的语法或结构错误
保持同步
:当服务更新时,可以轻松重新生成代码以保持同步
提高可维护性
:生成的代码结构清晰,易于理解和维护
第一部分:使用Visual Studio的”添加服务引用”功能
基本步骤
Visual Studio提供了最简单的方法来生成WSDL客户端代码。以下是详细步骤:
在项目中添加服务引用
右键点击项目 → 添加 → 服务引用
在地址框中输入WSDL的URL或本地文件路径
点击”转到”按钮加载服务
命名空间(例如:MyServiceReference)
点击”确定”
配置服务引用(可选但重要)
右键点击服务引用 → 配置服务引用
可以设置数据合同命名空间、服务端点地址等
对于复杂服务,可能需要调整”集合类型”和”异步操作”设置
代码示例:使用生成的客户端代理
// 假设我们生成的服务引用命名空间为 MyServiceReference
using System;
using MyServiceReference;

class Program
{
static void Main()
{
// 创建客户端实例
// 注意:生成的客户端类名通常是 “服务名Client”
using (var client = new CalculatorClient())
{
try
{
// 调用服务方法
double result = client.Add(10.5, 20.3);
Console.WriteLine($”10.5 + 20.3 = {result}”);

// 异步调用示例
double asyncResult = client.AddAsync(5.5, 4.5).Result;
Console.WriteLine($”5.5 + 4.5 = {asyncResult}”);
}
catch (Exception ex)
{
Console.WriteLine($”调用出错: {ex.Message}”);
}
}
}
}
高级配置:自定义绑定和端点
有时默认配置无法满足需求,我们可以通过配置文件或代码自定义:
// 通过配置文件配置(App.config)
/*
















*/

// 通过代码配置
var binding = new BasicHttpBinding();
binding.Security.Mode = BasicHttpSecurityMode.Transport;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
binding.MaxReceivedMessageSize = 2147483647;

var endpoint = new EndpointAddress(“https://example.com/service.svc”);
var client = new CalculatorClient(binding, endpoint);
第二部分:使用svcutil.exe命令行工具
基本用法
svcutil.exe是.NET Framework SDK的一部分,提供了更灵活的代码生成方式。
# 基本语法
svcutil.exe /out:<输出文件名> /config:<配置文件名>

# 示例1:从URL生成代码
svcutil.exe http://example.com/service.svc?wsdl /out:ServiceClient.cs /config:app.config

# 示例2:从本地WSDL文件生成
svcutil.exe C:\services\myservice.wsdl /out:MyService.cs /config:service.config

# 示例3:生成异步方法
svcutil.exe http://example.com/service.svc?wsdl /out:ServiceClient.cs /async
常用参数说明
参数
说明
/out:
指定输出的C#文件名
/config:
生成配置文件
/namespace:
指定生成代码的命名空间
/async
生成异步方法
/internal
生成internal访问级别的类
/serializer:Auto
自动选择序列化器(推荐)
/reference:
引用其他程序集
完整示例:生成带配置的客户端
# 生成代码和配置,指定命名空间,包含异步方法
svcutil.exe http://services.odata.org/Northwind/Northwind.svc?wsdl `
/out:NorthwindService.cs `
/config:app.config `
/namespace:MyApp.Services `
/async `
/serializer:Auto
生成的代码结构示例
// 生成的客户端类大致结构
[System.CodeDom.Compiler.GeneratedCodeAttribute(“svcutil”, “4.8.3928.0”)]
[System.ServiceModel.ServiceContractAttribute(ConfigurationName=”INorthwindService”)]
public interface INorthwindService
{
[System.ServiceModel.OperationContractAttribute(Action=”http://tempuri.org/INorthwindService/GetCustomers”, ReplyAction=”http://tempuri.org/INorthwindService/GetCustomersResponse”)]
MyServiceReference.Customer[] GetCustomers();

[System.ServiceModel.OperationContractAttribute(AsyncPattern=true, Action=”http://tempuri.org/INorthwindService/GetCustomers”, ReplyAction=”http://tempuri.org/INorthwindService/GetCustomersResponse”)]
System.IAsyncResult BeginGetCustomers(System.AsyncCallback callback, object state);
MyServiceReference.Customer[] EndGetCustomers(System.IAsyncResult result);
}

[System.Runtime.Serialization.DataContractAttribute(Name=”Customer”, Namespace=”http://schemas.datacontract.org/2004/07/MyService”)]
public partial class Customer
{
private string CustomerIDField;
private string CompanyNameField;

[System.Runtime.Serialization.DataMemberAttribute()]
public string CustomerID
{
get { return this.CustomerIDField; }
set { this.CustomerIDField = value; }
}

[System.Runtime.Serialization.DataMemberAttribute()]
public string CompanyName
{
get { return this.CompanyNameField; }
set { this.CompanyNameField = value; }
}
}
第三部分:使用dotnet-svcutil工具(.NET Core/.NET 5+)
安装和基本使用
对于.NET Core和.NET 5+项目,推荐使用dotnet-svcutil工具:
# 安装全局工具
dotnet tool install –global dotnet-svcutil

# 在项目目录中运行
dotnet-svcutil http://example.com/service.svc?wsdl

# 指定输出文件和命名空间
dotnet-svcutil http://example.com/service.svc?wsdl –outputDir . –namespace “MyApp.Services”
在.NET Core项目中配置
// Program.cs (.NET 6+)
using System.ServiceModel;

// 配置依赖注入
var builder = WebApplication.CreateBuilder(args);

// 添加WCF客户端服务
builder.Services.AddServiceModelServices();
builder.Services.AddServiceModelMetadata();
builder.Services.AddSingleton();

// 注册你的WCF客户端
builder.Services.AddHttpClient(client =>
{
client.BaseAddress = new Uri(“https://example.com/service.svc”);
});

var app = builder.Build();
第四部分:常见错误及解决方案
错误1:无法连接到服务或WSDL不可访问
错误信息示例
:
无法获取元数据…
如果在以下位置无法获取元数据…
解决方案
:
检查网络连接和URL
// 测试WSDL是否可访问
using (var client = new HttpClient())
{
var response = await client.GetAsync(“http://example.com/service.svc?wsdl”);
if (response.IsSuccessStatusCode)
{
string wsdlContent = await response.Content.ReadAsStringAsync();
Console.WriteLine(“WSDL可访问,内容长度: ” + wsdlContent.Length);
}
}
配置代理
# 在svcutil中使用代理
svcutil.exe http://example.com/service.svc?wsdl /out:Service.cs /proxy:http://proxy:8080
禁用证书验证(仅测试环境)
// 临时禁用证书验证(不推荐生产环境)
System.Net.ServicePointManager.ServerCertificateValidationCallback +=
(sender, cert, chain, errors) => true;
错误2:命名空间冲突或重复定义
错误信息
:
类型已存在于命名空间中
解决方案
:
使用不同的命名空间
svcutil.exe service.wsdl /namespace:MyApp.Services.V1
使用不同的输出文件名
svcutil.exe service.wsdl /out:ServiceV1.cs
在Visual Studio中配置
服务引用 → 配置服务引用
修改”命名空间”和”数据合同命名空间”
错误3:数据类型不兼容
错误信息
:
无法将类型 ‘X’ 转换为 ‘Y’
解决方案
:
使用自定义序列化器
// 在配置文件中指定







手动映射复杂类型
// 创建转换器类
public static class TypeConverter
{
public static MyLocalType ToLocalType(RemoteType remote)
{
return new MyLocalType
{
Id = remote.Id,
Name = remote.Name,
// 其他属性映射
};
}
}
错误4:安全/认证问题
错误信息
:
访问被拒绝
401 Unauthorized
解决方案
:
配置用户名/密码
// 代码方式配置
client.ClientCredentials.UserName.UserName = “username”;
client.ClientCredentials.UserName.Password = “password”;
配置Windows认证
var binding = new BasicHttpBinding();
binding.Security.Mode = BasicHttpSecurityMode.TransportCredentialOnly;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Windows;
配置证书
client.ClientCredentials.ClientCertificate.Certificate =
new X509Certificate2(“cert.pfx”, “password”);
错误5:版本兼容性问题
问题描述
:不同.NET版本生成的代码不兼容
解决方案
:
使用兼容的生成工具
.NET Framework: 使用svcutil.exe
.NET Core/5+: 使用dotnet-svcutil
.NET Standard: 使用dotnet-svcutil
目标框架兼容性


netstandard2.0

第五部分:高级技巧和最佳实践
1. 处理大型WSDL文件
对于包含大量操作和复杂类型的WSDL:
# 分批生成
svcutil.exe service.wsdl /out:Service.cs /reference:OtherService.dll

# 使用特定的序列化器
svcutil.exe service.wsdl /serializer:DataContractSerializer
svcutil.exe service.wsdl /serializer:XmlSerializer
2. 生成异步方法
# 生成异步方法
svcutil.exe service.wsdl /async

# 生成的异步方法示例
public System.Threading.Tasks.Task MyOperationAsync(MyRequest request)
{
return base.Channel.MyOperationAsync(request);
}
3. 自定义消息头
// 创建自定义消息头
public class CustomHeader : MessageHeader
{
public string Token { get; set; }

public override string Name => “CustomHeader”;
public override string Namespace => “http://example.com/headers”;

protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
{
writer.WriteElementString(“Token”, Namespace, Token);
}
}

// 使用自定义头
using (var scope = new OperationContextScope(client.InnerChannel))
{
var header = new CustomHeader { Token = “your-token” };
OperationContext.Current.OutgoingMessageHeaders.Add(header);

// 调用服务
var result = client.MyOperation();
}
4. 错误处理和重试机制
public class ResilientServiceClient
{
private readonly CalculatorClient _client;
private readonly int _maxRetries = 3;

public async Task AddWithRetry(double a, double b)
{
for (int i = 0; i < _maxRetries; i++) { try { return await _client.AddAsync(a, b); } catch (Exception ex) when (i < _maxRetries - 1) { Console.WriteLine($"尝试 {i + 1} 失败: {ex.Message}"); await Task.Delay(1000 * (i + 1)); // 指数退避 } } throw new InvalidOperationException("所有重试都失败"); } } 5. 性能优化 // 1. 重用客户端实例 public class ServiceClientPool { private static readonly ConcurrentBag _pool = new();

public static CalculatorClient GetClient()
{
if (_pool.TryTake(out var client))
return client;
return new CalculatorClient();
}

public static void ReturnClient(CalculatorClient client)
{
if (client.State == CommunicationState.Faulted)
{
client.Abort();
}
else
{
_pool.Add(client);
}
}
}

// 2. 调整超时时间
var binding = new BasicHttpBinding();
binding.OpenTimeout = TimeSpan.FromMinutes(1);
binding.CloseTimeout = TimeSpan.FromMinutes(1);
binding.SendTimeout = TimeSpan.FromMinutes(10);
binding.ReceiveTimeout = TimeSpan.FromMinutes(10);
第六部分:调试和故障排除
1. 启用WCF跟踪




2. 使用Service Trace Viewer工具
下载:Windows SDK的一部分
打开.svclog文件查看详细跟踪
分析消息流和错误点
3. 捕获原始SOAP消息
// 创建消息拦截器
public class MessageInspector : IClientMessageInspector
{
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
// 记录请求
Console.WriteLine(“Request: ” + request.ToString());
return null;
}

public void AfterReceiveReply(ref Message reply, object correlationState)
{
// 记录响应
Console.WriteLine(“Reply: ” + reply.ToString());
}
}

// 应用拦截器
public class EndpointBehavior : IEndpointBehavior
{
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.ClientMessageInspectors.Add(new MessageInspector());
}

// 其他接口方法…
}
第七部分:替代方案和现代方法
1. 使用HttpClient直接调用SOAP
对于简单服务,可以避免生成代理:
public class SoapClient
{
private readonly HttpClient _httpClient;
private readonly string _serviceUrl;

public SoapClient(string serviceUrl)
{
_serviceUrl = serviceUrl;
_httpClient = new HttpClient();
}

public async Task CallServiceAsync(string soapAction, string request)
{
var content = new StringContent(request, Encoding.UTF8, “text/xml”);
content.Headers.Add(“SOAPAction”, soapAction);

var response = await _httpClient.PostAsync(_serviceUrl, content);
return await response.Content.ReadAsStringAsync();
}
}

// 使用示例
var client = new SoapClient(“https://example.com/service.svc”);
string request = @”



10.5
20.3


“;

string response = await client.CallServiceAsync(“http://tempuri.org/ICalculator/Add”, request);
2. 使用第三方库
ServiceStack.Text
:高效的SOAP/REST客户端
NSwag
:可以生成客户端代码(主要针对REST,但也支持SOAP)
AutoRest
:Microsoft的REST客户端生成器
总结
从WSDL生成C#客户端代码是一个常见但可能复杂的任务。通过本文的指南,您应该能够:
使用Visual Studio
快速生成客户端代码
使用命令行工具
进行更灵活的控制
识别和解决
常见错误和兼容性问题
应用高级技巧
优化性能和可靠性
记住,选择合适的工具和方法取决于您的具体需求:.NET Framework项目使用svcutil.exe,.NET Core/5+项目使用dotnet-svcutil,而Visual Studio的”添加服务引用”则提供了最便捷的图形界面方式。
当遇到问题时,始终首先检查WSDL是否可访问,然后验证配置,最后使用跟踪工具分析具体问题。保持代码的模块化和可测试性,将使您的WCF客户端更加健壮和易于维护。