用于从调用 WCF 服务的 .NET Core 应用程序发送 cookie 的中间件
Middleware for sending cookies from .NET Core app making calls to WCF service
我目前正在开发一个连接到 API 的客户端,它是多个 SOAP 服务端点的外观。我正在使用 .Net Core 3.1
SOAP 服务由其他公司编写,无法更改。我们有多种服务,每一种都有“登录”方法。登录成功后,在header秒后返回session cookie。每次后续调用都需要附加cookie才能访问其他方法。
为此,我们编写了中间件,假设 捕获来自登录方法的响应,并存储 cookie。然后它应该改变对 WCF 服务的请求,将 cookie 添加到 headers.
不幸的是,只有在调用我们的 API 路径时才会触发中间件,而不会在进行 SOAP 服务调用时触发。假设我在 API 中调用路径“/test”。中间件已正确启动并执行。之后,我的后台代码被执行以进行 SOAP 服务调用,不幸的是中间件没有被触发。
我研究了很多主题,例如 or THIS
但我们希望能够全局更改消息,而不是在每次调用时显式“手动”添加 cookie。此外,当 session 过期时,我们希望捕获这种情况并再次登录,而无需用户注意。这就是为什么编写中间件如此重要 class.
所以我有我的连接服务(使用 Microsoft WCS Web 服务引用提供程序生成的代理),这样调用:
MyServiceClient client = new MyServiceClient();
var logged = await client.loginAsync(new loginRequest("login", "password"));
if (logged.@return) {
//doing stuff here (getting cookie, storing in places)
}
loginAsync 方法响应在其 header 中包含 cookie。我们如何注册某种中间件或拦截器来获取响应并从此方法中提取 cookie?
然后,我们有服务电话:
var data = await client.getSchedule(new getScheduleRequest(DateTime.Parse("2020-06-01"), DateTime.Parse("2020-06-23")));
现在我希望我的消息 inspector/middleware/interceptor 更改请求并将存储的 cookie 添加为 header。
中间件注册在Startup.cs:
app.UseMiddleware<WCFSessionMiddleware>();
我也试过使用行为,但问题是一样的——每次我创建 wcf 服务客户端时都需要调用它来改变行为,使用:
client.Endpoint.EndpointBehaviors.Add(myBehaviour);
我会感谢任何帮助,无论多么小。
这取决于您使用的绑定,但默认情况下,客户端应自动发送在它发出的先前请求中收到的 cookie。
例如:
var client = new ServiceClient(...);
var result = await client.MethodAsync(param); // If the response contains a HTTP Header 'Set-Cookie: cookieName=cookieValue'
var anotherResult = await client.AnotherMethodAsync(anotherParam); // Then this request will contain a HTTP Header 'Cookie: cookieName=cookieValue'
这是因为自动生成的绑定代码是这样的:
private static System.ServiceModel.Channels.Binding GetBindingForEndpoint(EndpointConfiguration endpointConfiguration)
{
if ((endpointConfiguration == EndpointConfiguration.BasicHttpBinding_IService))
{
System.ServiceModel.BasicHttpBinding result = new System.ServiceModel.BasicHttpBinding();
result.MaxBufferSize = int.MaxValue;
result.ReaderQuotas = System.Xml.XmlDictionaryReaderQuotas.Max;
result.MaxReceivedMessageSize = int.MaxValue;
result.AllowCookies = true; // <- THIS
return result;
}
throw new System.InvalidOperationException(string.Format("Could not find endpoint with name \'{0}\'.", endpointConfiguration));
}
如果您需要手动 read/set cookie,您可以使用 IEndpointBehavior,但请注意,这与中间件管道无关。中间件管道是处理对 ASP.NET 应用程序的传入请求的,我们要讨论的行为是处理从应用程序到 WCF 服务的请求。
public class MyEndpointBehavior : IEndpointBehavior
{
private MyMessageInspector messageInspector = new MyMessageInspector();
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.ClientMessageInspectors.Add(messageInspector);
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
}
public void Validate(ServiceEndpoint endpoint)
{
}
}
这是消息检查器:
// Reads a cookie named RESP_COOKIE from responses and put its value in a cookie named REQ_COOKIE in the requests
public class MyMessageInspector : IClientMessageInspector
{
private const string RESP_COOKIE_NAME = "RESP_COOKIE";
private const string REQ_COOKIE_NAME = "REQ_COOKIE";
private string cookieVal = null;
// Handles the service's responses
public void AfterReceiveReply(ref Message reply, object correlationState)
{
HttpResponseMessageProperty httpReplyMessage;
object httpReplyMessageObject;
// WCF can perform operations with many protocols, not only HTTP, so we need to make sure that we are using HTTP
if (reply.Properties.TryGetValue(HttpResponseMessageProperty.Name, out httpReplyMessageObject))
{
httpReplyMessage = httpReplyMessageObject as HttpResponseMessageProperty;
if (!string.IsNullOrEmpty(httpReplyMessage.Headers["Set-Cookie"]))
{
var cookies = httpReplyMessage.Headers["Set-Cookie"];
cookieVal = cookies.Split(";")
.Select(c => c.Split("="))
.Select(s => new { Name = s[0], Value = s[1] })
.FirstOrDefault(c => c.Name.Equals(RESP_COOKIE_NAME, StringComparison.InvariantCulture))
?.Value;
}
}
}
// Invoked when a request is made
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
HttpRequestMessageProperty httpRequestMessage;
object httpRequestMessageObject;
if (!string.IsNullOrEmpty(cookieVal))
{
var prop = new HttpRequestMessageProperty();
prop.Headers.Add(HttpRequestHeader.Cookie, $"{REQ_COOKIE_NAME}={cookieVal}");
request.Properties.Add(HttpRequestMessageProperty.Name, prop);
}
return null;
}
}
然后我们可以这样配置:
var client = new ServiceClient(...);
client.Endpoint.EndpointBehaviors.Add(new MyEndpointBehavior());
您可以使用属性来应用行为到界面,这里有一个演示:
public class ClientMessage : IClientMessageInspector
{
public void AfterReceiveReply(ref Message reply, object correlationState)
{
}
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
return null;
}
}
[AttributeUsage(AttributeTargets.Interface, AllowMultiple = false)]
public class MyContractBehaviorAttribute : Attribute, IContractBehavior
{
public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
return;
}
public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.ClientMessageInspectors.Add(new ClientMessage());
}
public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
{
return;
}
public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
{
return;
}
}
最后我们可以直接应用到界面上了:
我目前正在开发一个连接到 API 的客户端,它是多个 SOAP 服务端点的外观。我正在使用 .Net Core 3.1
SOAP 服务由其他公司编写,无法更改。我们有多种服务,每一种都有“登录”方法。登录成功后,在header秒后返回session cookie。每次后续调用都需要附加cookie才能访问其他方法。
为此,我们编写了中间件,假设 捕获来自登录方法的响应,并存储 cookie。然后它应该改变对 WCF 服务的请求,将 cookie 添加到 headers.
不幸的是,只有在调用我们的 API 路径时才会触发中间件,而不会在进行 SOAP 服务调用时触发。假设我在 API 中调用路径“/test”。中间件已正确启动并执行。之后,我的后台代码被执行以进行 SOAP 服务调用,不幸的是中间件没有被触发。
我研究了很多主题,例如
但我们希望能够全局更改消息,而不是在每次调用时显式“手动”添加 cookie。此外,当 session 过期时,我们希望捕获这种情况并再次登录,而无需用户注意。这就是为什么编写中间件如此重要 class.
所以我有我的连接服务(使用 Microsoft WCS Web 服务引用提供程序生成的代理),这样调用:
MyServiceClient client = new MyServiceClient();
var logged = await client.loginAsync(new loginRequest("login", "password"));
if (logged.@return) {
//doing stuff here (getting cookie, storing in places)
}
loginAsync 方法响应在其 header 中包含 cookie。我们如何注册某种中间件或拦截器来获取响应并从此方法中提取 cookie?
然后,我们有服务电话:
var data = await client.getSchedule(new getScheduleRequest(DateTime.Parse("2020-06-01"), DateTime.Parse("2020-06-23")));
现在我希望我的消息 inspector/middleware/interceptor 更改请求并将存储的 cookie 添加为 header。
中间件注册在Startup.cs:
app.UseMiddleware<WCFSessionMiddleware>();
我也试过使用行为,但问题是一样的——每次我创建 wcf 服务客户端时都需要调用它来改变行为,使用:
client.Endpoint.EndpointBehaviors.Add(myBehaviour);
我会感谢任何帮助,无论多么小。
这取决于您使用的绑定,但默认情况下,客户端应自动发送在它发出的先前请求中收到的 cookie。
例如:
var client = new ServiceClient(...);
var result = await client.MethodAsync(param); // If the response contains a HTTP Header 'Set-Cookie: cookieName=cookieValue'
var anotherResult = await client.AnotherMethodAsync(anotherParam); // Then this request will contain a HTTP Header 'Cookie: cookieName=cookieValue'
这是因为自动生成的绑定代码是这样的:
private static System.ServiceModel.Channels.Binding GetBindingForEndpoint(EndpointConfiguration endpointConfiguration)
{
if ((endpointConfiguration == EndpointConfiguration.BasicHttpBinding_IService))
{
System.ServiceModel.BasicHttpBinding result = new System.ServiceModel.BasicHttpBinding();
result.MaxBufferSize = int.MaxValue;
result.ReaderQuotas = System.Xml.XmlDictionaryReaderQuotas.Max;
result.MaxReceivedMessageSize = int.MaxValue;
result.AllowCookies = true; // <- THIS
return result;
}
throw new System.InvalidOperationException(string.Format("Could not find endpoint with name \'{0}\'.", endpointConfiguration));
}
如果您需要手动 read/set cookie,您可以使用 IEndpointBehavior,但请注意,这与中间件管道无关。中间件管道是处理对 ASP.NET 应用程序的传入请求的,我们要讨论的行为是处理从应用程序到 WCF 服务的请求。
public class MyEndpointBehavior : IEndpointBehavior
{
private MyMessageInspector messageInspector = new MyMessageInspector();
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.ClientMessageInspectors.Add(messageInspector);
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
}
public void Validate(ServiceEndpoint endpoint)
{
}
}
这是消息检查器:
// Reads a cookie named RESP_COOKIE from responses and put its value in a cookie named REQ_COOKIE in the requests
public class MyMessageInspector : IClientMessageInspector
{
private const string RESP_COOKIE_NAME = "RESP_COOKIE";
private const string REQ_COOKIE_NAME = "REQ_COOKIE";
private string cookieVal = null;
// Handles the service's responses
public void AfterReceiveReply(ref Message reply, object correlationState)
{
HttpResponseMessageProperty httpReplyMessage;
object httpReplyMessageObject;
// WCF can perform operations with many protocols, not only HTTP, so we need to make sure that we are using HTTP
if (reply.Properties.TryGetValue(HttpResponseMessageProperty.Name, out httpReplyMessageObject))
{
httpReplyMessage = httpReplyMessageObject as HttpResponseMessageProperty;
if (!string.IsNullOrEmpty(httpReplyMessage.Headers["Set-Cookie"]))
{
var cookies = httpReplyMessage.Headers["Set-Cookie"];
cookieVal = cookies.Split(";")
.Select(c => c.Split("="))
.Select(s => new { Name = s[0], Value = s[1] })
.FirstOrDefault(c => c.Name.Equals(RESP_COOKIE_NAME, StringComparison.InvariantCulture))
?.Value;
}
}
}
// Invoked when a request is made
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
HttpRequestMessageProperty httpRequestMessage;
object httpRequestMessageObject;
if (!string.IsNullOrEmpty(cookieVal))
{
var prop = new HttpRequestMessageProperty();
prop.Headers.Add(HttpRequestHeader.Cookie, $"{REQ_COOKIE_NAME}={cookieVal}");
request.Properties.Add(HttpRequestMessageProperty.Name, prop);
}
return null;
}
}
然后我们可以这样配置:
var client = new ServiceClient(...);
client.Endpoint.EndpointBehaviors.Add(new MyEndpointBehavior());
您可以使用属性来应用行为到界面,这里有一个演示:
public class ClientMessage : IClientMessageInspector
{
public void AfterReceiveReply(ref Message reply, object correlationState)
{
}
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
return null;
}
}
[AttributeUsage(AttributeTargets.Interface, AllowMultiple = false)]
public class MyContractBehaviorAttribute : Attribute, IContractBehavior
{
public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
return;
}
public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.ClientMessageInspectors.Add(new ClientMessage());
}
public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
{
return;
}
public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
{
return;
}
}
最后我们可以直接应用到界面上了: