【问题标题】:Custom WCF service factory and hooking all calls自定义 WCF 服务工厂和挂钩所有调用
【发布时间】:2013-11-15 05:27:54
【问题描述】:

我会拦截所有对自定义 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

【问题讨论】:

    标签: wcf sharepoint servicehost


    【解决方案1】:

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

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

    【讨论】:

    • 谢谢。正如您在我的回答中看到的,我发现了这个界面,它帮助解决了我的问题。
    • 酷。在我回复之前没有看到你的答案。很高兴你能成功。
    • 您的回答仍然很有价值。它解释了为什么我以前的方法失败了。谢谢你
    【解决方案2】:

    您需要通过添加消息检查器来扩展 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 在客户端处理消息。如果您需要在消息中添加自定义标头,请点击此处。如果不需要添加自定义header,可以跳过这第一段代码。

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

    然后,设置您的 Endpoint 以使用此行为。

    以编程方式

    ...
    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>
    

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

    【讨论】:

    • 正如我在问题中所说,我在 SharePoint 下工作。我无权访问 web.config,也没有自己生成端点。关于标头,它不是 WCF 标头,而是由一些 jQuery 代码提供的普通旧 Http 标头(服务是休息服务)。无论如何,感谢您提供背景信息。
    • 我知道您无法更改配置,但我不知道您不能以编程方式使用解决它。对不起 =/
    【解决方案3】:

    好的,在代码项目文章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 服务工厂!

    【讨论】:

    • 那么请将问题标记为已回答,这样我就可以在 5 分钟前停止阅读...
    猜你喜欢
    • 1970-01-01
    • 2013-02-06
    • 1970-01-01
    • 2021-12-07
    • 2013-03-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-01
    相关资源
    最近更新 更多