1

我会拦截对自定义 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 文件(从技术上讲这是可能的,但部署和维护起来很痛苦)。

我实际上继承了这个类Microsoft.SharePoint.Client.Services.MultipleBaseAddressWebServiceHostFactory

4

3 回答 3

1

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

您当前的解决方案不适用于每次调用,因为它仅在打开新服务主机时才被调用。一旦实例化(并打开),该服务主机实例将为后续调用提供服务。但是,由于它已经打开,因此您的代码不会在这些后续调用中被调用。

于 2013-11-04T15:52:47.797 回答
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。然后,我们需要设置服务端点以使用此自定义端点行为。

在此示例中,我将 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>

从现在开始,所有的请求都会经过这个点。希望能帮助到你。

于 2013-11-04T17:00:48.610 回答
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 服务工厂!

于 2013-11-04T15:44:20.400 回答