自定义WCF服务工厂并挂钩所有呼叫

时间:2013-11-04 14:53:01

标签: wcf sharepoint servicehost

我会拦截对自定义WCF服务(.net 3.5 SP1)的所有发布请求,以验证是否存在特定标头。

到目前为止我尝试了什么:

public class ServiceFactory : WebServiceHostFactory
{
    protected override System.ServiceModel.ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
    {
        var result = base.CreateServiceHost(serviceType, baseAddresses);

        result.Opened += result_Opened;

        return result;
    }

    private void result_Opened(object sender, EventArgs e)
    {
        var ctx = HttpContext.Current;
        var request = ctx.Request;
        if (request.HttpMethod == "POST")
        {              
            // Validate if the request contains my header
            if(request.Headers["MyHeader"] != "42")
                throw new VeryBadThingsException("boom");
        }
    }
}

我还设置了我的svc文件来使用这个工厂。

有时有效。实际上,并非所有的Web服务调用都被open事件处理程序挂钩。达到了Web服务的实际实现,所以我认为问题不在于Web服务本身。

如何正确挂接所有传入的请求到我的服务?

PS:为了更多地描述我的上下文,该服务由SharePoint 2010托管。这意味着我无法更改web.config文件(从技术上讲,它是可能的,但它是' sa部署和维护的痛苦)。

我实际上继承了班级Microsoft.SharePoint.Client.Services.MultipleBaseAddressWebServiceHostFactory

3 个答案:

答案 0 :(得分:1)

您应该在服务端实现IDispatchMessageInspector。在AfterReceiveRequest方法中传递给您的消息实例有一个Headers属性,您可以在其中检查所需的标题。

您当前的解决方案不适用于每次调用,因为只有在打开新服务主机时才会调用它。实例化(并打开)后,该服务主机实例正在为后续调用提供服务。但是,因为它已经打开,所以后续调用不会调用您的代码。

答案 1 :(得分:1)

您需要通过添加消息检查器来扩展WCF管道。客户端的消息检查器将负责添加标头,服务器的消息检查器将负责验证标头是否存在。

  

良好实践:如果要创建自定义标头,请指定自定义命名空间以便于查找。

public static class WCFSOAPNamespaces
    {
        private const string root = "http://www.schemas.productname.com/";
        public const string Headers = root + "headers/";
    }

服务器

IDispatchMessageInspector处理所有传入服务器的消息。在这里您将检查服务器上是否存在标头。

public class DispatchMessageInspector : IDispatchMessageInspector
    {
        public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
        {
            const string headerName = "nameOfTheHeader";

            var someHeaderData = request.Headers.GetHeader<string>(headerName, WCFSOAPNamespaces.Headers);
            //someHeaderData is the content that you want to check for every request. Attention: it throws System.ServiceModel.MessageHeaderException if the header doesn't exist   

            return null;
        }

        public void BeforeSendReply(ref Message reply, object correlationState) { }
    }

客户端

IClientMessageInspector处理客户端上的消息。如果您需要在邮件中添加自定义标题,请输入以下位置。如果您不需要添加自定义标题,则可以跳转第一段代码。

public class ClientMessageInspector : IClientMessageInspector
    {
        public void AfterReceiveReply(ref Message reply, object correlationState) { }

        public object BeforeSendRequest(ref Message request, IClientChannel channel)
        {
            const string headerName = "nameOfTheHeader";
            string headerContent = ""; //fill this variable with the content

            var header = new MessageHeader<string>(headerContent ?? string.Empty);
            var untyped = header.GetUntypedHeader(headerName, WCFSOAPNamespaces.Headers);
            request.Headers.Add(untyped);

            return null;
        }
    }

两者(客户端和服务器)

即使客户端上不需要消息检查程序,仍需要此配置将消息检查添加到服务器端应用程序。更具体地说,我们需要一个EndpointBehavior来处理MessageInspector。然后,我们需要设置服务endpoits以使用此自定义端点行为。

在这个例子中,我将2个检查员放在相同的行为中,但如果需要,你可以创建不同的行为。

   public class EndpointBehavior : BehaviorExtensionElement, IEndpointBehavior
    {
        public EndpointBehavior() { }

        public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { }

        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
            clientRuntime.MessageInspectors.Add(new ClientMessageInspector());
        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {
            endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new DispatchMessageInspector());
        }

        public void Validate(ServiceEndpoint endpoint) { }

        public override Type BehaviorType
        {
            get { return this.GetType(); }
        }

        protected override object CreateBehavior()
        {
            return new EndpointBehavior();
        }
    }

然后,将您的端点设置为使用此行为。

编程

...
ServiceEndpoint endpoint;
...
endpoint.Behaviors.Add(new EndpointBehavior());

配置

...
<services>
  <service name="...">
    <endpoint address="..." binding="..." contract="..." behaviorConfiguration="endpointBehaviorName" />
  </service>
...
<behaviors>
  ...
  <endpointBehaviors>
    <behavior name="endpointBehaviorName">
      <customEndpointBehavior />
    </behavior>
  </endpointBehaviors>
</behaviors>
...
<extensions>
  <behaviorExtensions>
    <add name="customEndpointBehavior" type="FullNamespace.EndpointBehavior , AssemblyName" />
  </behaviorExtensions>
</extensions>

从现在开始,所有请求都将通过这一点。 希望它有所帮助。

答案 2 :(得分:0)

好的,在代码项目文章Add Custom Message Header in WCF 4 Calls的帮助下,我设法在所有对象之间游泳。

特别是,它帮助我弄清楚如何使用属性通过代码正确附加ServiceBehavior。

我终于有了这个:

internal class ValidateSPFormDigestAttribute : Attribute, IServiceBehavior
{
    public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase host)
    {
        foreach (ChannelDispatcher cDispatcher in host.ChannelDispatchers)
        {
            foreach (EndpointDispatcher eDispatcher in cDispatcher.Endpoints)
            {
                eDispatcher.DispatchRuntime.MessageInspectors.Add(new ValidateSPFormDigestInspector());
            }
        }
    }

    public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
    }
}

internal class ValidateSPFormDigestInspector : IDispatchMessageInspector
{
    public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
    {

        if (!SPUtility.ValidateFormDigest())
        {
            throw new FaultException(new FaultReason("Invalid form digest token"));
        }

        return null;
    }

    public void BeforeSendReply(ref Message reply, object correlationState)
    {
    }
}

我直接在服务上附加自定义行为:

[BasicHttpBindingServiceMetadataExchangeEndpoint]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
[ValidateSPFormDigest]
public class MyCustomService: IWidgetAdminService

直接的好处是,我不再需要创建自定义Web服务工厂了!