How to include WCF Custom Headers in console Service Host

北战南征 提交于 2020-01-23 07:00:47

问题


In my WCF service I was getting 405 method not allowederror and then came across a post which suggest to have the following in Application_BeginRequest of my WCF host:

protected void Application_BeginRequest(object sender, EventArgs e)
{
    if (HttpContext.Current.Request.HttpMethod == "OPTIONS")
    {
        HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers",
                    "Accept, Content-Type,customHeader");

        HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods",
                    "POST,GET,OPTIONS");

        HttpContext.Current.Response.AddHeader("Access-Control-Max-Age",
                    "172800");

        HttpContext.Current.Response.AddHeader("Access-Control-Allow-Credentials",
                    "true");

        HttpContext.Current.Response.AddHeader("Access-Control-Expose-Headers",
                    "customHeader");

        HttpContext.Current.Response.AddHeader("Content-type",
                    "application/json");

        HttpContext.Current.Response.End();
    }
    else
    {
        HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers",
                    "Accept, Content-Type,customHeader");

        HttpContext.Current.Response.AddHeader("Access-Control-Expose-Headers",
                    "customHeader");

        HttpContext.Current.Response.AddHeader("Content-type",
                    "application/json");
    }
} 

But I am hosting my service using a console application.

using (ServiceHost sc = new ServiceHost(typeof(DataRetriever)))
{
    sc.Open();

    foreach (var endPoints in sc.Description.Endpoints)
    {
        Console.WriteLine(endPoints.Address);
    }

    Console.ReadKey();
    sc.Close();
}

So how do I include the headers in the console app.


回答1:


In WCF, headers can be accessed via an instance of the class OperationContext, which is accessible via the OperationContext.Current (when available).

The naive way to approach this problem is to simply use this property within the method of your service:

[ServiceContract]
public interface IMyService
{
    [OperationContract]
    void MyMethod();
}

internal class MyService: IMyService
{
    public void MyMethod()
    {
        Console.WriteLine("My Method");
        OperationContext.Current.OutgoingMessageHeaders.Add(MessageHeader.CreateHeader("headerFromMethod", "namespace", "headerFromMethodValue"));
    }
}

For completeness, the code used to host this service within the Console Application (no config required) is:

using (var serviceHost = new ServiceHost(typeof(MyService)))
{
    var endpoint = serviceHost.AddServiceEndpoint(typeof(IMyService), new BasicHttpBinding(), "http://localhost:9000");

    serviceHost.Open();

    Console.WriteLine("Open for business");
    Console.ReadLine();

    serviceHost.Close();
}

A .NET client would access the headers like this:

var channel = ChannelFactory<IMyService>.CreateChannel(new BasicHttpBinding(), new EndpointAddress("http://localhost:9000"));

var contextChannel = channel as IContextChannel;
using (new OperationContextScope(contextChannel))
{
    channel.MyMethod();

    var incommingHeaders = OperationContext.Current.IncomingMessageHeaders;
    var header = incommingHeaders.GetHeader<string>("headerFromMethod", "namespace");
    Console.WriteLine("Header from server: " + header);
}

If you have Fiddler, you can also see the headers using this tool.

Whilst this method will do what you want, it is questionable whether you want to mix your business logic (contained within the implementation of IMyService), and the logic controlling the "out-of-band" information attached to the message.

A cleaner separation is gained by implementing IDispatchMessageInspector, which allows you to intercept calls on the server and modify the message as it comes in and out:

public class ServerInterceptor: IDispatchMessageInspector, IEndpointBehavior
{
    object IDispatchMessageInspector.AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
    {
        return null;
    }

    void IDispatchMessageInspector.BeforeSendReply(ref Message reply, object correlationState)
    {
        reply.Headers.Add(MessageHeader.CreateHeader("header", "namespace", "headervalue"));
    }

    void IEndpointBehavior.ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
    {
        endpointDispatcher.DispatchRuntime.MessageInspectors.Add(this);
    }

    void IEndpointBehavior.Validate(ServiceEndpoint endpoint){}

    void IEndpointBehavior.AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters){}

    void IEndpointBehavior.ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime){}
}

The headers are accessed from a .NET client in the same way as before. It's worth noting that you can pass information from the AfterReceiveRequest method to the BeforeSendReply, as the object returned in the former method is passed as the correlationState parameter in the latter. This would be useful if the headers you return are dependent on the headers of the incoming message - as your example suggests.

Finally, to install this functionality on the service, you need to modify the hosting code as follows:

...
var endpoint = serviceHost.AddServiceEndpoint(typeof(IMyService), new BasicHttpBinding(), "http://localhost:9000");
endpoint.Behaviors.Add(new ServerInterceptor());
serviceHost.Open();
...

which we can do by virtue of the fact that ServerInterceptor implements IEndpointBehavior




回答2:


This can be done. You'd need a shared variable operating between the host (console) exe and the web service classes. You'd have to run a continuous loop after calling WebService.Open(), checking this shared variable for input. The code would look something like this:

//this would be your console host class
public class HostInterface
{
    string sHeaderString;
    public static string HeaderString {
        get { return sHeaderString; }
        set { sHeaderString += value; }


   public void main()
   {
      //code to start the web service
      System.ServiceModel.ServiceHost myWebService = default(System.ServiceModel.ServiceHost);

      //configure myWebService stuff
      myWebService.open();

      // here loop every second until the communication is stopped
      //check for new text in the shared sHeaderString 
      //written to by your web service class
      while (true) {
        if (myWebService.state != Communicationsstate.Opened){
            break; 
        }
        //write message out through console
        console.writeline("Headers:" + sHeaderString);
        Threading.Thread.Sleep(1000);
        //sleep 1 second before going trying next
      }
    }
  }
}

this would be in the main class of your web service, referencing and updating the "HeaderString" shared variable from your console.

public void WriteHeadersToSharedVariable()
{
        //here, setting the headers into the shared variable instanced back on the console program
        HostInterface.Headerstring = GetHeadersFromRequest();
}

public string GetHeadersFromRequest()
{
    //some code to get the headers from inbound request
    return "blah blah blah";
}

Hopefully you find this usefull. Good luck.



来源:https://stackoverflow.com/questions/23493495/how-to-include-wcf-custom-headers-in-console-service-host

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!