2

I'm currently working on a client to connect to an API which is a facade for several SOAP service endpoints. I am using .Net Core 3.1

The SOAP service was written by other company and cannot be changed. We have multiple services, and each one has "login" method. After succesfull login, session cookie is returned in headers. Cookie needs to be appended to every subsequent calls to access other methods.

To achieve this, we have written middleware, which suppose to catch response from login method, and store the cookie. Then it should alter requests to WCF service, adding the cookie to headers.

Unfortunately, the middleware is triggered only, when our API path is called, not when SOAP service calls are made. Lets say im calling path "/test" in my API. The middeware is raised properly and executes. Aftehr that, my code behind is executed making SOAP service calls, and unfortunately the middleware isnt triggered.

I've looked into many topics, such as THIS or THIS

but we want to be able to globally alter messages instead of explicitly add cookie "manualy" when making every single call. Also, when session expired, we want to catch this situation and login again, without user noticing. This is why its so importat to write middleware class.

So i have my conneted services (proxies generated using Microsoft WCS Web Service Reference Provider), called like this:

MyServiceClient client = new MyServiceClient();

var logged = await client.loginAsync(new loginRequest("login", "password"));

if (logged.@return) {

    //doing stuff here (getting cookie, storing in places)

}

The loginAsync method response has cookie in its headers. How can we register some sort of middleware or interceptor to get response and extract the cookie from this method?

Than, we have service call:

 var data = await client.getSchedule(new getScheduleRequest(DateTime.Parse("2020-06-01"), DateTime.Parse("2020-06-23")));

And now i want my message inspector/middleware/interceptor to alter the request and add stored cookie as header.

Middleware is registered in Startup.cs:

app.UseMiddleware<WCFSessionMiddleware>();

I've also tried using behaviors, but the problem is the same - it needs to be called every time i create wcf service client to alter the behaviour using:

client.Endpoint.EndpointBehaviors.Add(myBehaviour);

I would appriciate any help, no matter how small.

jps
  • 20,041
  • 15
  • 75
  • 79
Karol Pawlak
  • 440
  • 4
  • 15

2 Answers2

1

It depends on the Binding you use, but by default the client should automatically send the cookies received in the previous requests it made.

Eg:

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'

This because the automatically generated Binding code is something like:

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));
}

If you need to manually read/set the cookies you can use an IEndpointBehavior, but please note that this has nothing to do with the middlewares pipeline. The middlewares pipeline is what process the incoming requests to your ASP.NET application, the behavior we are going to discuss is what process a request from your application to a WCF service.

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)
    {
    }
}

And here's the Message inspector:

// 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;
    }
}

And then we can configure it like this:

var client = new ServiceClient(...);
client.Endpoint.EndpointBehaviors.Add(new MyEndpointBehavior());
Matteo Umili
  • 3,412
  • 1
  • 19
  • 31
  • Thanks for Your response. Thats how i have this resolved right now,. Well i have a little bit simpler solution, but as i noticed, you still have to create client and register behavior every time you want to use it. – Karol Pawlak Jul 21 '20 at 11:51
1

You can use Attribute to apply behavior to the interface,here is a demo:

    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;
        }
    }

Finally, we can apply it directly to the interface:

enter image description here

Ding Peng
  • 3,702
  • 1
  • 5
  • 8