Remembering some similar issues in HttpModules/HttpApplications I started looking for a point to do the injection of a method while the service is in the middle of loading and I found ServiceBehavior.AddBindingParameters. The other two methods don't seem to be at the right point in the sequence for the injection to work correctly.
WARNING: lots of trial & error based code ahead, I doubt this is correct in all situations, it does work on a REST POX service fairly well. I am mostly writing this so I remember it, and so others can see it probably can be done.
First you have to make an attribute that you will use later to decorate your service implementation. In this example the AddUI sub looks at the first operation for the current contract, copies a lot of it's general settings then adds the messages to an operation, then the operation to the contract.
Imports System.ServiceModel.Description Public Class ExternalInvalidatorAttribute Inherits Attribute Implements IServiceBehavior Public Sub AddBindingParameters(ByVal serviceDescription As System.ServiceModel.Description.ServiceDescription, ByVal serviceHostBase As System.ServiceModel.ServiceHostBase, ByVal endpoints As System.Collections.ObjectModel.Collection(Of System.ServiceModel.Description.ServiceEndpoint), ByVal bindingParameters As System.ServiceModel.Channels.BindingParameterCollection) Implements System.ServiceModel.Description.IServiceBehavior.AddBindingParameters For Each endpt In endpoints AddUI(endpt) Next End Sub Private Sub AddUI(ByRef endpt As ServiceEndpoint) Dim cd = endpt.Contract.Operations(0).DeclaringContract Dim od = New OperationDescription("invalidatorUI", cd) Dim inputMsg = New MessageDescription(cd.Namespace + cd.Name + "/invalidatorUI", MessageDirection.Input) Dim mpd = New MessagePartDescription("a", endpt.Contract.Namespace) mpd.Index = 0 mpd.MemberInfo = Nothing mpd.Multiple = False mpd.ProtectionLevel = Net.Security.ProtectionLevel.None mpd.Type = GetType(System.String) inputMsg.Body.Parts.Add(mpd) od.Messages.Add(inputMsg) Dim outputMsg = New MessageDescription(cd.Namespace + cd.Name + "/invalidatorUIResponse", MessageDirection.Output) outputMsg.Body.ReturnValue = New MessagePartDescription("invalidatorUIResult", cd.Namespace) With {.Type = GetType(System.String)} od.Messages.Add(outputMsg) od.Behaviors.Add(New DataContractSerializerOperationBehavior(od)) od.Behaviors.Add(New System.ServiceModel.Web.WebGetAttribute() With {.UriTemplate = "/invalidator/{a}"}) od.Behaviors.Add(New System.ServiceModel.OperationBehaviorAttribute()) Dim dc = New DasOP() od.Behaviors.Add(dc) cd.Operations.Add(od) End Sub #Region "not needed" Public Sub ApplyDispatchBehavior(ByVal serviceDescription As System.ServiceModel.Description.ServiceDescription, ByVal serviceHostBase As System.ServiceModel.ServiceHostBase) Implements System.ServiceModel.Description.IServiceBehavior.ApplyDispatchBehavior End Sub Public Sub Validate(ByVal serviceDescription As System.ServiceModel.Description.ServiceDescription, ByVal serviceHostBase As System.ServiceModel.ServiceHostBase) Implements System.ServiceModel.Description.IServiceBehavior.Validate End Sub #End Region End Class
Since there is no function in the service implementation the default invoker call would error with nothing to act upon, so the DasOP custom operation behavior is substituted for the default. Looking at it's apply dispatch sub below you can see it is just a crutch to a custom invoker. You don't have to do this, but if you don't you have to deal with the message objects by hand, and they are not really that friendly.
Imports System.ServiceModel.Description Public Class DasOp Inherits Attribute 'this makes it a decorator Implements IOperationBehavior 'this makes it get called for applybehaviour Public Sub ApplyDispatchBehavior(ByVal operationDescription As System.ServiceModel.Description.OperationDescription, ByVal dispatchOperation As System.ServiceModel.Dispatcher.DispatchOperation) Implements System.ServiceModel.Description.IOperationBehavior.ApplyDispatchBehavior dispatchOperation.Invoker = New InvalidatorInvoker() 'this invoker actually does the work, it needs a reference to the other cache objects so it can meddle in their cache arrays End Sub #Region "not used" Public Sub AddBindingParameters(ByVal operationDescription As System.ServiceModel.Description.OperationDescription, ByVal bindingParameters As System.ServiceModel.Channels.BindingParameterCollection) Implements System.ServiceModel.Description.IOperationBehavior.AddBindingParameters 'not needed End Sub Public Sub ApplyClientBehavior(ByVal operationDescription As System.ServiceModel.Description.OperationDescription, ByVal clientOperation As System.ServiceModel.Dispatcher.ClientOperation) Implements System.ServiceModel.Description.IOperationBehavior.ApplyClientBehavior 'not needed End Sub Public Sub Validate(ByVal operationDescription As System.ServiceModel.Description.OperationDescription) Implements System.ServiceModel.Description.IOperationBehavior.Validate 'not needed End Sub #End Region End Class
Finally you make the invoker, it doesn't really invoke anything, since nothing actually exists to invoke, but it allocates and input and returns a result like their was, so the rest of the WCF seems not to notice the difference.
Imports System.ServiceModel.Dispatcher Imports System.Xml.Linq Imports System.Text.RegularExpressions Public Class InvalidatorInvoker Implements IOperationInvoker Public Function AllocateInputs() As Object() Implements System.ServiceModel.Dispatcher.IOperationInvoker.AllocateInputs Return {Nothing} 'reserve a spot for some input End Function Public Function Invoke(ByVal instance As Object, ByVal inputs() As Object, ByRef outputs() As Object) As Object Implements System.ServiceModel.Dispatcher.IOperationInvoker.Invoke outputs = New Object(-1) {} 'return an empty array here, MSDN does not elaborate as to why
Return "Result" End Function #Region "not needed" Public Function InvokeBegin(ByVal instance As Object, ByVal inputs() As Object, ByVal callback As System.AsyncCallback, ByVal state As Object) As System.IAsyncResult Implements System.ServiceModel.Dispatcher.IOperationInvoker.InvokeBegin Return Nothing End Function Public Function InvokeEnd(ByVal instance As Object, ByRef outputs() As Object, ByVal result As System.IAsyncResult) As Object Implements System.ServiceModel.Dispatcher.IOperationInvoker.InvokeEnd Return Nothing End Function #End Region Public ReadOnly Property IsSynchronous As Boolean Implements System.ServiceModel.Dispatcher.IOperationInvoker.IsSynchronous Get Return True 'disable async End Get End Property End Class
It isn't pretty, but it works.
No comments:
Post a Comment