文章目录
前言
WCF,全程Windows Communication Foundation 。这是微软提供的一个面向SOAP服务设计的一个框架。与之类似的还有WebService,它也是一种基于 XML 的跨平台的远程调用技术,也是使用 SOAP协议进行通信。
其实应该也就传统的老项目还会使用到这类技术,写这篇文章的原因主要是工作中碰到老项目的维护需要和这类传统框架打交道。本文也主要是记录这个传统框架的一些使用和坑点。
后续我会把WCF上如何使用JWT Token和生成请求频率之类的功能一一介绍,希望能帮助到有需要的朋友,本文聚焦于搭建RESTful服务。
SOAP(Simple Object Access Protocol) 协议使用 XML 格式来定义消息的结构和内容。在使用 SOAP 进行通信时,发送方会将消息打包成 XML 格式,然后通过 HTTP 或 SMTP 等协议发送给接收方。接收方会解析 XML 格式的消息,从中提取出所需的信息。
SOAP 协议提供了一组规范来定义消息的结构和内容。这些规范包括 SOAP Envelope、SOAP Header 和 SOAP Body。SOAP Envelope 是 SOAP 消息的根元素,它包含了整个 SOAP 消息的描述信息。SOAP Header 是可选的,用于传递与消息相关的其他信息,如安全认证信息。SOAP Body 包含了实际的消息内容。
本文WCF搭建的RESTful服务基于.NET Framework 4.8,宿主程序采用WinFroms。
提示:以下是本篇文章正文内容,下面案例可供参考
一、WCF 核心组件
WCF实现服务一般是划分为三个部分。契约,服务和宿主。
1.1 契约(Contract)
契约分为服务契约和数据契约,服务契约可以简单的理解为API接口,不包含具体实现的内容,具体的操作由操作契约指定,用来指定API的结构,使用到的HTTP方法。
服务契约和操作契约的指定,通过在接口上用ServiceContract特性描述,和方法上用OperationContract描述
[ServiceContract]
public interface IServiceRESTful
{
/// <summary>
/// 获取所有学生
/// </summary>
/// <returns></returns>
[OperationContract]
[WebGet(UriTemplate = "api/students", ResponseFormat = WebMessageFormat.Json)]
JsonResult<List<Student>> GetStudents();
}
JsonResult为定义的一个返回参数
public class JsonResult<T>
{
public string code { get; set; }
public string msg { get; set; }
public List<T> data { get; set; }
}
如上例服务契约代码所示,其中有用WebGet和WebInvoke修饰的Uri
- WebGet用于标记服务接口中的方法,使其支持 HTTP GET 请求
- WebInvoke:定义 REST 风格的端点
- Method = “GET”:使用 HTTP GET 方法,其中 Method 支持 GET,PUT,PATCH,POST,DELETE
- UriTemplate = “wcfApi/students”:URL 路径,手动指定
- BodyStyle = WebMessageBodyStyle.Bare:直接返回数据对象
- RequestFormat = WebMessageFormat.Json:请求格式为 JSON,根据需要是否设置
- ResponseFormat = WebMessageFormat.Json:响应格式为 JSON
数据契约则是接口的参数。比分说我们定义一个接口里用到的对象Student。
数据契约的指定,类上用DataContract特性描述,具体的成员属性上用DataMember描述
[DataContract]
public class Student
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string Name { get; set; }
}
WCF 直接支持像整数、布尔值、日期时间等常见的基础数据。在属性上面使用DataMember描述,
日期时间DateTime涉及格式转换的问题,建议实际工作中定义属性的时候使用string,在代码里面转换。
1.2 服务(Service)
Service可以理解成实现服务契约的服务。实现服务契约里定义的各种接口
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall,
ConcurrencyMode = ConcurrencyMode.Multiple,
IncludeExceptionDetailInFaults = true)]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class ServiceRESTful : IServiceRESTful
{
/// <summary>
/// 获取所有学生
/// </summary>
/// <returns></returns>
public JsonResult<List<Student>> GetStudents()
{
try
{
return new JsonResult<List<Student>>
{
code = "200",
msg = "success",
data = _students
};
}
catch (Exception ex)
{
return new JsonResult<List<Student>>
{
code = "500",
msg = $"获取学生列表失败: {ex.Message}",
data = null
};
}
}
}
在服务中,我们需要定义ServiceBehavior特性,用于控制服务实例的创建和管理方式,以及服务的一些全局行为。
ServiceBehavior 特性提供了丰富的属性来控制服务的行为,比如
- InstanceContextMode
实例生命周期
- Single 整个服务生命周期仅创建一个实例(单例模式)。所有客户端共享此实例。
- PerCall 每个请求创建一个新实例,请求处理完成后立即释放。适合无状态服务(RESTful)。
- PerSession 每个会话创建一个实例(基于会话的单例)。会话结束后释放实例。
- ConcurrencyMode
控制服务实例如何处理并发请求
- Single 同一时间仅处理一个请求,其他请求排队等待。适合非线程安全的服务。
- Multiple 允许多线程并发处理请求。服务必须是线程安全的(如使用锁)。
- Reentrant 允许服务在处理请求时接收回调请求(需配合双工契约使用)。
- IncludeExceptionDetailInFaults
控制是否将详细的异常信息返回给客户端
- true 返回完整的异常堆栈信息。
- false(默认):返回通用错误信息
- CrossDomainScriptAccessEnabled
跨域
- true 允许跨域
1.3 宿主(Host)
private WebServiceHost _serviceHostRESTful;
private string urlRESTful = "";
#region RESTful
private void RESTfulServiceStart()
{
// 确保没有重复启动
if (_serviceHostRESTful?.State == CommunicationState.Opened)
{
return;
}
// 清理可能存在的旧实例
_serviceHostRESTful?.Abort();
// 创建服务实例和主机
var service = new ServiceRESTful();
var baseAddress = new Uri(urlRESTful);
_serviceHostRESTful = new WebServiceHost(service, baseAddress);
WebHttpBinding binding =new WebHttpBinding
{
TransferMode = TransferMode.Buffered,
MaxBufferSize = 655360,
MaxReceivedMessageSize = 655360,
MaxBufferPoolSize = 5242880, // 5MB缓冲池
ReaderQuotas =
{
MaxArrayLength = 163840, // 数组最大长度
MaxStringContentLength = 655360 // 字符串最大长度
},
Security = { Mode = WebHttpSecurityMode.None } // 注意:生产环境应考虑安全性
};
_serviceHostRESTful.AddServiceEndpoint(typeof(IServiceRESTful), binding, baseAddress);
// 打开服务主机
_serviceHostRESTful.Open();
}
private void RESTfulServiceClose()
{
try
{
if (_serviceHostRESTful == null)
return;
switch (_serviceHostRESTful.State)
{
case CommunicationState.Opened:
_serviceHostRESTful.Close();
break;
case CommunicationState.Faulted:
_serviceHostRESTful.Abort();
break;
}
}
finally
{
_serviceHostRESTful = null; // 释放引用
}
}
#endregion
宿主服务的启动非常重要,这一步是将整个WCF服务串联起来的核心。在WCF中通过Endpoint提供访问的入口,它定义了客户端如何与服务进行通信。一个完整的 Endpoint 由 ABC 三要素组成:地址 (Address)、绑定 (Binding) 和契约 (Contract)。
注意到我们启动宿主服务前也便是这个顺序,再根据实际业务设置各类配置。需要注意的是结束服务的时候要手动释放实例。
MaxReceivedMessageSize等参数的生产环境配置策略,根据业务数据量调整
二、RESTful风格
REST(Representational State Transfer)是一种基于 HTTP 协议的软件架构风格,它使用 URL 表示资源,使用 HTTP 方法(GET、POST、PUT、DELETE)表示对资源的操作。RESTful API 具有以下特点:
- 无状态:每个请求都是独立的,服务器不保存客户端的状态
- 统一接口:使用标准的 HTTP 方法和状态码
- 资源导向:通过 URL 识别资源
- 分层系统:可以通过中间层(如缓存、代理)提高性能
三、通过WCF搭建RESTful服务
3.1 HTTP GET
资源后面没有参数
/// <summary>
/// 获取所有学生
/// </summary>
/// <returns></returns>
[OperationContract]
[WebGet(UriTemplate = "api/students", ResponseFormat = WebMessageFormat.Json)]
JsonResult<List<Student>> GetStudents();
资源后面有参数,且参数的类型必须为string
/// <summary>
/// 获取指定学生
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[OperationContract]
[WebGet(UriTemplate = "api/students/{id}", ResponseFormat = WebMessageFormat.Json)]
JsonResult<Student> GetStudent(string id);
也可以通过WebInvoke特性显示指定 Method = “GET”
[WebInvoke(Method = "GET", UriTemplate = "api/students",
ResponseFormat = WebMessageFormat.Json)]
3.2 HTTP PUT
/// <summary>
/// 更新指定学生
/// </summary>
/// <param name="student"></param>
/// <returns></returns>
[OperationContract]
[WebInvoke(Method = "PUT", UriTemplate = "api/students",
RequestFormat = WebMessageFormat.Json,
ResponseFormat = WebMessageFormat.Json)]
JsonResult<Student> UpdateStudent(Student student);
3.3 HTTP PATCH
使用patch需要注意的是,请求的参数里如果包含了多个,需要使用BodyStyle = WebMessageBodyStyle.WrappedRequest
/// <summary>
/// 更新学生姓名
/// </summary>
/// <param name="id"></param>
/// <param name="name"></param>
/// <returns></returns>
[OperationContract]
[WebInvoke(Method = "PATCH", UriTemplate = "api/students/{id}",
BodyStyle = WebMessageBodyStyle.WrappedRequest,
RequestFormat = WebMessageFormat.Json,
ResponseFormat = WebMessageFormat.Json)]
JsonResult<Student> UpdateStudentName(string id, [MessageParameter(Name = "name")] string name);
MessageParameter包裹的参数,在PATCH请求里,是在raw里传递
3.4 HTTP POST
/// <summary>
/// 添加学生
/// </summary>
/// <param name="student"></param>
/// <returns></returns>
[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "api/students",
BodyStyle = WebMessageBodyStyle.Bare,
RequestFormat = WebMessageFormat.Json,
ResponseFormat = WebMessageFormat.Json)]
JsonResult<Student> AddStudent(Student student);
3.5 HTTP DELETE
/// <summary>
/// 删除指定学生
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[OperationContract]
[WebInvoke(Method = "DELETE", UriTemplate = "api/students/{id}",
ResponseFormat = WebMessageFormat.Json)]
JsonResult<bool> DeleteStudent(string id);
3.6 完整服务代码
[ServiceBehavior(
InstanceContextMode = InstanceContextMode.PerCall,
ConcurrencyMode = ConcurrencyMode.Multiple,
IncludeExceptionDetailInFaults = true)]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class ServiceRESTful : IServiceRESTful
{
// 模拟数据存储
private static readonly List<Student> _students = new List<Student>
{
new Student { Id = 1, Name = "张三" },
new Student { Id = 2, Name = "李四" }
};
/// <summary>
/// 获取所有学生
/// </summary>
/// <returns></returns>
public JsonResult<List<Student>> GetStudents()
{
try
{
return new JsonResult<List<Student>>
{
code = "200",
msg = "success",
data = _students
};
}
catch (Exception ex)
{
return new JsonResult<List<Student>>
{
code = "500",
msg = $"获取学生列表失败: {ex.Message}",
data = null
};
}
}
/// <summary>
/// 获取指定学生
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public JsonResult<Student> GetStudent(string id)
{
try
{
if (!int.TryParse(id, out int studentId))
{
return new JsonResult<Student>
{
code = "400",
msg = "无效的学生ID",
data = null
};
}
var student = _students.FirstOrDefault(s => s.Id == studentId);
if (student == null)
{
return new JsonResult<Student>
{
code = "404",
msg = "未找到指定学生",
data = null
};
}
return new JsonResult<Student>
{
code = "200",
msg = "success",
data = student
};
}
catch (Exception ex)
{
return new JsonResult<Student>
{
code = "500",
msg = $"获取学生失败: {ex.Message}",
data = null
};
}
}
/// <summary>
/// 修改学生信息
/// </summary>
/// <param name="student"></param>
/// <returns></returns>
public JsonResult<Student> UpdateStudent(Student student)
{
try
{
if (student == null || student.Id <= 0)
{
return new JsonResult<Student>
{
code = "400",
msg = "无效的学生信息",
data = null
};
}
var existingStudent = _students.FirstOrDefault(s => s.Id == student.Id);
if (existingStudent == null)
{
return new JsonResult<Student>
{
code = "404",
msg = "未找到指定学生",
data = null
};
}
// 更新学生信息
existingStudent.Name = student.Name;
return new JsonResult<Student>
{
code = "200",
msg = "success",
data = existingStudent
};
}
catch (Exception ex)
{
return new JsonResult<Student>
{
code = "500",
msg = $"更新学生失败: {ex.Message}",
data = null
};
}
}
/// <summary>
/// 修改学生名称
/// </summary>
/// <param name="id"></param>
/// <param name="name"></param>
/// <returns></returns>
public JsonResult<Student> UpdateStudentName(string id, string name)
{
try
{
if (!int.TryParse(id, out int studentId))
{
return new JsonResult<Student>
{
code = "400",
msg = "无效的学生ID",
data = null
};
}
if (string.IsNullOrEmpty(name))
{
return new JsonResult<Student>
{
code = "400",
msg = "学生姓名不能为空",
data = null
};
}
var student = _students.FirstOrDefault(s => s.Id == studentId);
if (student == null)
{
return new JsonResult<Student>
{
code = "404",
msg = "未找到指定学生",
data = null
};
}
student.Name = name;
return new JsonResult<Student>
{
code = "200",
msg = "success",
data = student
};
}
catch (Exception ex)
{
return new JsonResult<Student>
{
code = "500",
msg = $"更新学生姓名失败: {ex.Message}",
data = null
};
}
}
/// <summary>
/// 添加学生
/// </summary>
/// <param name="student"></param>
/// <returns></returns>
public JsonResult<Student> AddStudent(Student student)
{
try
{
if (student == null || string.IsNullOrEmpty(student.Name))
{
return new JsonResult<Student>
{
code = "400",
msg = "学生信息不完整",
data = null
};
}
// 生成唯一ID
int newId = _students.Any() ? _students.Max(s => s.Id) + 1 : 1;
student.Id = newId;
_students.Add(student);
return new JsonResult<Student>
{
code = "201", // 创建成功
msg = "success",
data = student
};
}
catch (Exception ex)
{
return new JsonResult<Student>
{
code = "500",
msg = $"添加学生失败: {ex.Message}",
data = null
};
}
}
/// <summary>
/// 删除指定学生
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public JsonResult<bool> DeleteStudent(string id)
{
try
{
if (!int.TryParse(id, out int studentId))
{
return new JsonResult<bool>
{
code = "400",
msg = "无效的学生ID",
data = false
};
}
var student = _students.FirstOrDefault(s => s.Id == studentId);
if (student == null)
{
return new JsonResult<bool>
{
code = "404",
msg = "未找到指定学生",
data = false
};
}
_students.Remove(student);
return new JsonResult<bool>
{
code = "200",
msg = "success",
data = true
};
}
catch (Exception ex)
{
return new JsonResult<bool>
{
code = "500",
msg = $"删除学生失败: {ex.Message}",
data = false
};
}
}
}
总结
本文系统讲解了基于.NET Framework 4.8 的 WCF 框架搭建 RESTful 服务的全流程,涵盖契约定义、服务实现、宿主配置及各 HTTP 方法实践,适合老项目维护中与传统框架交互的场景。
在后续的文章中,我将进一步探讨如何在WCF服务中集成JWT Token认证、实现请求频率限制和缓存之类等高级功能,希望能帮助到大家。