How Dynamically change URL in a WCF Custom Behavior - wcf

Class is defined as follows:
public class BizTalkRESTTransmitHandler : IClientMessageInspector
I'm a method with this signature:
public object BeforeSendRequest(ref Message request, IClientChannel channel)
So I think I need to manipulate the channel object.
The reason is this is being using in BizTalk 2010 SendPort to support JSON.
I tried this so far:
if (channel.RemoteAddress.Uri.AbsoluteUri == "http://api-stage2.mypartner.com/rest/events/2/"
|| channel.RemoteAddress.Uri.AbsoluteUri == "http://api.mypartner.com/rest/events/2/")
{
//TODO - "boxout" will become a variable obtained by parsing the message
Uri newUri = new Uri(channel.RemoteAddress.Uri.AbsoluteUri + "boxout");
channel.RemoteAddress.Uri = newUri;
}
Above gives compile error: "System.ServiceModel.EndpointAddress.Uri" cannot be assigned to - it is ready only" RemoteAddress seems to be read only as well.
I have referenced these questions but they don't use channel object.
Assign a URL to Url.AbsoluteUri in ASP.NET, and
WCF change endpoint address at runtime
But they don't seem to be dealing with channel object.
Update 1: I tried the following:
//try create new channel to change URL
WebHttpBinding myBinding = new WebHttpBinding();
EndpointAddress myEndpoint = new EndpointAddress(newURL);
ChannelFactory<IClientChannel> myChannelFactory = new ChannelFactory<IClientChannel>(myBinding, myEndpoint); //Change to you WCF interface
IClientChannel myNewChannel = myChannelFactory.CreateChannel();
channel = myNewChannel; //replace the channel parm passed to us
but it gave this error:
System.InvalidOperationException: Attempted to get contract type for IClientChannel, but that type is not a ServiceContract, nor does it inherit a ServiceContract.

IClientMessageInspector is not the right place the manipulate the Channel, you should use IEndpointBehavior instead:
From MSDN
Implements methods that can be used to extend run-time behavior for an
endpoint in either a service or client application.
Here is a simple example:
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
Uri endpointAddress = endpoint.Address.Uri;
string address = endpointAddress.ToString();
if (address == "http://api-stage2.mypartner.com/rest/events/2/"
|| address == "http://api.mypartner.com/rest/events/2/")
{
//TODO - "boxout" will become a variable obtained by parsing the message
Uri newUri = new Uri(address + "boxout");
ServiceHostBase host = endpointDispatcher.ChannelDispatcher.Host;
ChannelDispatcher newDispatcher = this.CreateChannelDispatcher(host, endpoint, newUri);
host.ChannelDispatchers.Add(newDispatcher);
}
}
Here you can read the excelent post of Carlos Figueira about IEndpointBehavior:
https://blogs.msdn.microsoft.com/carlosfigueira/2011/04/04/wcf-extensibility-iendpointbehavior/
Another alternative is to implement a simple Routing with WCF, here is link with an example:
WCF REST service url routing based on query parameters
Hope it helps.

Using the interface IEndpointBehavior, you'll have access to the ApplyClientBehavior method, which exposes the ServiceEndPoint instance.
Now you can change the value for the Address by defining a new EndpointAddress instance.
public class MyCustomEndpointBehavior : IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint serviceEndpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint serviceEndpoint, System.ServiceModel.Dispatcher.ClientRuntime behavior)
{
serviceEndpoint.Address = new System.ServiceModel.EndpointAddress("http://mynewaddress.com");
}
public void ApplyDispatchBehavior(ServiceEndpoint serviceEndpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
{
}
public void Validate(ServiceEndpoint serviceEndpoint)
{
}
}

I might be a bit too late but hoe it helps a bit.
I recently had a similar objective (also related to biztalk) where I needed to change the url based on some value sent on the message.
I tried using the ApplyDispatchBehavior method but it was never called and also, I couldn't see how to access the message from here so I started looking at method BeforeSendRequest (in the Inspector class).
Here is what i came up with:
object IClientMessageInspector.BeforeSendRequest(ref Message request, IClientChannel channel)
{
var queryDictionary = HttpUtility.ParseQueryString(request.Headers.To.Query);
string parameterValue = queryDictionary[this.BehaviourConfiguration.QueryParameter];
//Only change parameter value if it exists
if (parameterValue != null)
{
MessageBuffer buffer = request.CreateBufferedCopy(Int32.MaxValue);
request = buffer.CreateMessage();
//Necessary in order to read the message without having WCF throwing and error saying
//the messas was already read
var reqAux = buffer.CreateMessage();
//For some reason the message comes in binary inside tags <Binary>MESSAGE</Binary>
using (MemoryStream ms = new MemoryStream(Convert.FromBase64String(reqAux.ToString().Replace("<Binary>", "").Replace("</Binary>", ""))))
{
ms.Position = 0;
string val = ExtractNodeValueByXPath(ms, this.BehaviourConfiguration.FieldXpath);
queryDictionary.Set(this.BehaviourConfiguration.QueryParameter, DateTime.Now.ToString("yyyyMMddHHmmssfff") + "_" +
this.BehaviourConfiguration.Message + (string.IsNullOrWhiteSpace(val) ? string.Empty : "_" + val) + ".xml");
UriBuilder ub = new UriBuilder(request.Headers.To);
ub.Query = queryDictionary.ToString();
request.Headers.To = ub.Uri;
}
}
return null;
}
So, I discovered that, messing with the request.Headers.To I could change the endpoint.
I had several problems getting the message content and most examples on the internet (showing to use the MessageBuffer.CreateNavigator or Message.GetBody< string > which was always throwing an expcetion i couldn't get around) would not give me the biztalk message but rather the soap message?... not sure but it had a node header, body and inside the body there was some base64 string which was not my biztalk message.
Also, as you can see in Convert.FromBase64String(reqAux.ToString().Replace("<Binary>", "").Replace("</Binary>", "")), I had to do this ugly replaces. I don't don't why this comes in base64, probably some WCF configuration?, but by doing it, I could then look for my value.
NOTE: I haven't fully tested this, but so far it as worked for my examples.
By the way, any idea on what can i switch my MemoryStream with so it becomes a more streaming solution?

Related

spring amqp RPC copy headers from request to response

I'm looking for a way to copy some headers from the request message to the response message when I use RabbitMq in RPC mode.
so far I have tried with setBeforeSendReplyPostProcessors but I can only access the response and add headers to it. but I don't have access to the request to get the values I need.
I have also tried with the advice chain, but the returnObject is null after proceeding so I can't modify it (I admit I don't understand why it is null... I thought I could get the object to modify it):
#Bean
public SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory(SimpleRabbitListenerContainerFactoryConfigurer simpleRabbitListenerContainerFactoryConfigurer, ConnectionFactory connectionFactory) {
SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory = new SimpleRabbitListenerContainerFactory();
simpleRabbitListenerContainerFactory.setAdviceChain(new MethodInterceptor() {
#Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Object returnObject = invocation.proceed();
//returnObject is null here
return returnObject;
}
});
simpleRabbitListenerContainerFactoryConfigurer.configure(simpleRabbitListenerContainerFactory, connectionFactory);
return simpleRabbitListenerContainerFactory;
}
a working way is to change my method annotated with #RabbitListener so it returns a Message and there I can access both the requesting message (via arguments of the annotated method) and the response.
But I would like to do it automatically, since I need this feature at different places.
Basicaly I want to copy one header from the request message to the response.
this code do the job, but I want to do it through an aspect, or an interceptor.
#RabbitListener(queues = "myQueue"
, containerFactory = "simpleRabbitListenerContainerFactory")
public Message<MyResponseObject> execute(MyRequestObject myRequestObject, #Header("HEADER_TO_COPY") String headerToCopy) {
MyResponseObject myResponseObject = compute(myRequestObject);
return MessageBuilder.withPayload(myResponseObject)
.setHeader("HEADER_RESPONSE", headerToCopy)
.build();
}
The Message<?> return type support was added for this reason, but we could add an extension point to allow this, please open a GitHub issue.
Contributions are welcome.

How to get the WCF service address from DynamicEndpoint

I Crreated a DynamicEndpoint to find the WCF service automatically.
namespace Client
{
class Program
{
static void Main(string[] args)
{
DynamicEndpoint dynamicEndpoint = new DynamicEndpoint(ContractDescription.GetContract(typeof(ICalculator)), new NetTcpBinding());
using (ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>(dynamicEndpoint))
{
ICalculator caculate = channelFactory.CreateChannel();
Console.WriteLine("x + y = {2} when x = {0} and y = {1}", 4, 9, caculate.Add(4, 9));
Console.WriteLine("Find service, the service address is: " + dynamicEndpoint.Address.Uri);
}
Console.Read();
}
}
}
The problem is when I try to print the service address, the return value is
http://schemas.microsoft.com/discovery/dynamic
That's not the real service address I published.
1. How to get the real service address?
2. If there are multiple services available, which one will DynamicEndpoint choose? Can I get the address array or list?
As far as I know, we could not get the actual use endpoint in client. except that we use the OperationContext object,which provides access to the execution context of a service method.
https://learn.microsoft.com/en-us/dotnet/api/system.servicemodel.operationcontext?redirectedfrom=MSDN&view=netframework-4.7.2
For example, we could refer to the following code to get the actual endpoint.
Server.
public string GetAddress()
{
OperationContext oc = OperationContext.Current;
string result=oc.Channel.LocalAddress.Uri.ToString();
return result;
}
Client.
ChannelFactory<IService> factory = new ChannelFactory<IService>(dynamicEndpoint);
IService sv = factory.CreateChannel();
Console.WriteLine(sv.GetAddress());
Besides,I don't think dynamic endpoint could list the endpoints that have been found. Dynamic Endpoint merge service discovery with service invokation. when a service is invoked using a dynamic endpoint, it will depend on the FindCriteria property to find the service endpoint and then invokes it.
https://learn.microsoft.com/en-us/dotnet/api/system.servicemodel.discovery.dynamicendpoint?view=netframework-4.7.2
https://learn.microsoft.com/en-us/dotnet/api/system.servicemodel.discovery.findcriteria?view=netframework-4.7.2

Call OutgoingHeaders using NServiceBus.Host

Using NServiceBus 4.0.11
I would like to call
Bus.OutgoingHeaders["user"] = "john";
The Header Manipulation sample shows how to call it with a custom host.
I would like to call it while using the NServiceBus.Host.
So actually I would like to have a reference to the instance of the Bus, to call OutgoingHeaders on.
Tried IWantCustomInitialization but that gives me an exception when calling CreateBus in it. INeedInitialization isn't the way to go neither.
How should I call Bus.OutgoingHeaders["user"] = "john"; while using the NServiceBus.Host?
Reading your question makes me think that you want to add this header to a certain message that you want to send during initialization/startup or when handling a message. Usually, headers have a more generic behavior as they need to be applied to more than one message.
Instead of setting the header before sending the message you can also add the header via a message mutator or behavior.
Behavior
public class OutgoingBehavior : IBehavior<SendPhysicalMessageContext>
{
public void Invoke(SendPhysicalMessageContext context, Action next)
{
Dictionary<string, string> headers = context.MessageToSend.Headers;
headers["MyCustomHeader"] = "My custom value";
next();
}
}
Mutator
public class MutateOutgoingTransportMessages : IMutateOutgoingTransportMessages
{
public void MutateOutgoing(object[] messages, TransportMessage transportMessage)
{
Dictionary<string, string> headers = transportMessage.Headers;
headers["MyCustomHeader"] = "My custom value";
}
}
Documentation
See: http://docs.particular.net/nservicebus/messaging/message-headers#replying-to-a-saga-writing-outgoing-headers for samples.

Protocol Violation - Verb does not allow content type

Update: I was at Build 2013, and Mark Simms looked at this and confirmed that HTTP request leaving the router does have a "Body" in the request. He felt that this was caused by the Get arriving at the SB, then packaged to be routed, then the package is managed again as a Messsage type, before sending it back out again. In between the packaging and routing, properties are left in the body of the request - which violates protocol for the "GET". All this however lives within MS framework either in the .NET, or in the ServiceBus. As the body is immutable (at least I can't find a way to alter it), the only means is to duplicate the request, and then update the original request on the way out.
This is a small part of a routing application that takes in a HTTP GET/POST request from a Azure ServiceBus endpoint, brings it down via the relay channel to my local workstation, where I rewrite the URL, and send it to my local web service.
Here is the interface - generic so that it can receive any type of call to a controller/action URL
// The Router, and general concept of how to recieve from the SB and redirect was taken from
// Tony Sneed Blog - which he documented here: http://blog.tonysneed.com/2012/04/24/roll-your-own-rest-ful-wcf-router/
//
[ServiceContract(Namespace = "urn:Twiddler")]
public interface IRoutingService
{
[WebInvoke(UriTemplate = "")]
[OperationContract(AsyncPattern = true, Action = "*", ReplyAction = "*")]
IAsyncResult BeginProcessRequest(Message requestMessage, AsyncCallback asyncCallback, object asyncState);
Message EndProcessRequest(IAsyncResult asyncResult);
}
}
Here is the code:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall,
AddressFilterMode = AddressFilterMode.Any, ValidateMustUnderstand = false)]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class RoutingService : IRoutingService, IDisposable
{
private IRoutingService _client;
/// <summary>
/// when a message is received from the SB, it arrives here as simply a message -
/// </summary>
/// <param name="requestMessage"></param>
/// <param name="asyncCallback"></param>
/// <param name="asyncState"></param>
/// <returns></returns>
public IAsyncResult BeginProcessRequest(Message requestMessage, AsyncCallback asyncCallback, object asyncState)
{
string RequestMessageAction = requestMessage.Headers.Action;
IAsyncResult asyncResult = null;
//if the full URI for the namespace does not match the one contructed in Twiddler, then pass it through - we have nothing to do with it!
if (requestMessage.Headers.To.AbsoluteUri.Contains(Utilities.ServiceFormBridge.NameSpaceName) && requestMessage.Headers.To.AbsoluteUri.Contains(Utilities.ServiceFormBridge.EndPointName) == false)
return asyncResult;
//as the service bus will accept anything in terms of controllers and actions, we only need alter the DestinationAddress.Authority (host and port)
var RewriteTheURL = requestMessage.Headers.To.AbsoluteUri.Replace(string.Format("http://{0}.servicebus.windows.net/{1}/", ServiceFormBridge.NameSpaceName, ServiceFormBridge.EndPointName), ServiceFormBridge.DestinationWebSite);
Uri DestinationAddress = new Uri(RewriteTheURL);
System.ServiceModel.ChannelFactory<IRoutingService> factory = null;
factory = new ChannelFactory<IRoutingService>(new WebHttpBinding(), DestinationAddress.AbsoluteUri);
WebHeaderCollection httpHeaders = WebOperationContext.Current.IncomingRequest.Headers;
httpHeaders.Remove("Host");
httpHeaders.Add("Host", DestinationAddress.Authority); //give it the new host that we are re-directing to
httpHeaders.Remove("Connection"); //todo: not sure I need this, but without it there is an exception between Keep-Alive and Closed
// Set factory and message address
factory.Endpoint.Address = new EndpointAddress(DestinationAddress);
requestMessage.Headers.To = DestinationAddress;
_client = factory.CreateChannel();
asyncResult = _client.BeginProcessRequest(requestMessage, asyncCallback, asyncState);
return asyncResult;
}
}
On the BeginProcessRequest, I get an exception:
Protocol Violation: Cannot send a content-body with this verb-type
Which I have researched and I understand that under a GET request, there can't be anything in the body of the request.
As my code works for a POST, I can only assume that for some reason, there is something in the body.
However, as the originating request was a GET from a browser using the URL of the ServiceBus, I'm not sure why there would be anything in the body.
So:
I'm thinking I'm doing something in the code that is causing me a problem - if so I would like to know what!
If there is something in the incoming request, how can I remove it so I don't get the violation?
Any other suggestions, improvements to the code?
Placing this attribute on the operation, [WebInvoke(UriTemplate = "")], supports POST by default, but you can pass a parameter specifying other verbs, such as PUT, DELETE, etc. However, GET is supported by a different attribute, [WebGet]. I would suggest adding additional operations to the router, one with WebGet, and others with WebInvoke and different HTTP verbs.

System.ServiceModel.Channels.MessageProperties object has been disposed

When I raise an exception in my WCF service. The built in WebErrorHandler exception handler itself fails trying to access OperationContext.Current.IncomingMessageProperties as it seems to have been disposed. The consequence is that the real error is masked, and I always get an HTML error output.
Why would my IncomingMessageProperties be disposed? The service returns a stream - not sure if that is relevant.
I found the problem. I was creating a WebFaultException. Now I create a normal Exception and use IErrorHandler to serialize it correctly to the client.
This happen to me when i try to acces OperationContext.Current.IncomingMessageProperties from another method, not the OperationContract method.
To access OperationContext.Current.IncomingMessageProperties
create an extencion method to OperationContext to acces the property
something like this:
public static string GetClientIP(this OperationContext context)
{
string ip = "";
MessageProperties messageProperties = context.IncomingMessageProperties;
RemoteEndpointMessageProperty endpointProperty =
messageProperties[RemoteEndpointMessageProperty.Name] as RemoteEndpointMessageProperty;
if (endpointProperty != null)
{
ip = endpointProperty.Address;
}
return ip;
}