我会拦截对自定义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
答案 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服务工厂了!