I'm using Framework 3.5 and would like to have a ServiceContract that can take in different types of Request / Response objects, is it possible?
Yes, if you really want to, you can deal with a generic Message type as parameter and/or return value.
[ServiceContract]
public interface IMyService
{
[OperationContract]
Message GetData();
[OperationContract]
void PutData(Message m);
}
See the details here in the MSDN documentation.
However, this means you'll have to manually do a lot of XML manipulation voodoo and handle lots of stuff yourself, that would be handled for free, if you used strongly typed [DataContract] types.
Can you do it? Yes. Should you do it? Well, depends on how much you really want it! :-)
Marc
As Marc says you have a service contract which takes Message parameters. But you don't have to build the XML by hand, instead you could share the interface and the message contracts in a shared DLL which is common to both the server and the client.
For example I have a piece of software which takes Messages because the spec says it had to. I have a common assembly which contains the interface, all the potential request and response messages and public static strings for the namespaces. So, for one of the operations it looks like the following
[ServiceContract(
Namespace = Constants.Service.Namespace.Location,
Name = "ServiceMonitorContract")]
public interface IMonitor
{
[OperationContract(
Action = Constants.Service.Actions.GetTasksRequest,
ReplyAction = Constants.Service.Actions.GetTasksResponse)]
Message GetTasks(Message request);
}
and I have message contracts that look like
[MessageContract(IsWrapped = true,
WrapperNamespace = Constants.Messages.Namespace.Location)]
public sealed class GetTasksRequest
{
....
}
To get a connection to the service I do the following
private static IMonitor GetChannelToWebService()
{
EndpointAddress endpoint = new EndpointAddress("http://example/service.svc");
ChannelFactory<IMonitor> channelFactory =
new ChannelFactory<IMonitor>(new BasicHttpBinding(), endpoint);
return channelFactory.CreateChannel();
}
And then I can do the following to use it, with the shared message contracts
IMonitor channel = GetChannelToWebService();
// Create the GetTasksRequest message
GetTasksRequest getTasksRequest = new GetTasksRequest();
// Set the various properties on the message
// Convert it to a strongly type message
TypedMessageConverter requestMessageConverter = TypedMessageConverter.Create(
typeof(GetTasksRequest),
Constants.Service.Actions.GetTasksRequest,
Constants.Service.Namespace.Location);
Message request = requestMessageConverter.ToMessage(
getTasksRequest,
MessageVersion.Soap11);
// Send it and get the response.
Message response = channel.GetTasks(request);
// Check for SOAP faults
if (response.IsFault)
{
MessageFault fault = MessageFault.CreateFault(response, int.MaxValue);
// React accordingly
}
TypedMessageConverter responseMessageConverter = TypedMessageConverter.Create(
typeof(GetTasksResponse),
Constants.Service.Actions.GetTasksResponse,
Constants.Service.Namespace.Location);
GetTasksResponse getTasksResponse =
responseMessageConverter.FromMessage(response) as GetTasksResponse;
((IClientChannel)channel).Close();
The one thing to be aware of is that faults will not get thrown client side, you must check the Message object when it arrives back as a response manually and act accordingly as you can see from the sample.
Server side I do much the same thing with a TypedMessageConvertor
// Convert the inbound message to a GetTasksRequest.
TypedMessageConverter getTasksMessageConverter = TypedMessageConverter.Create(
typeof(GetTasksRequest),
Constants.Service.Actions.GetTasksRequest,
Constants.Service.Namespace.Location);
GetTasksRequest getTasksMessage =
getTasksMessageConverter.FromMessage(request) as GetTasksRequest;
// Validate the message is the correct type.
if (getTasksMessage == null)
{
throw FaultHelper.UnknownMessageTypeFault();
}
// Do my thing
GetTasksResponse responseMessage = new GetTasksResponse();
// Set appropriate response bits in the responseMessage
TypedMessageConverter responseConverter = TypedMessageConverter.Create(
typeof(GetTasksResponse),
Constants.Service.Actions.GetTasksResponse,
Constants.Service.Namespace.Location);
Message response = responseConverter.ToMessage(responseMessage, request.Version);
response.Headers.RelatesTo = request.Headers.MessageId;
return response;
Just don't forget to set the RelatesTo header to be the MessageId from the request headers
You could also pass an XElement. This gives the request object the flexibility to contain whatever content you choose.
You would ideally specify an XSD which contains a number of 'choice' elements, each of which specify one of the different request types.
<xs:element name="request">
<xs:complexType>
<xs:choice>
<xs:element name="requestType1"/>
....
</xs:element>
<xs:element name="requestType2"/>
....
</xs:element>
</xs:choice>
</xs:complexType>
</xs:element>
Your service side code simple needs to determine which of the 'choice' objects are present in order to determine what to do with the parameter.
Related
I have a BizTalk WCF-Custom receive location to which I have added a custom behavior:
public class SasTokenProviderEndpointBehavior : BehaviorExtensionElement, IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
var tokenProvider = TokenProvider.CreateSharedAccessSignatureTokenProvider(sharedAccessSecretName, sharedAccessKey);
bindingParameters.Add(new TransportClientEndpointBehavior { TokenProvider = tokenProvider });
}
}
}
parameter setup code omitted for brevity
This is adapted from a sample found at https://code.msdn.microsoft.com/How-to-integrate-BizTalk-07fada58#content - this author is widely respected in the BizTalk community and code of this kind has been in use for some years. All I am doing is adapting the method he uses, that is proven to work, to substitute a different TokenProvider.
I can see through debugging that this code runs and the TransportClientEndpointBehavior with correct parameters is added to the channel. However when the BizTalk receive location polls Service Bus, I see the following in the event log:
The adapter "WCF-Custom" raised an error message. Details "System.UnauthorizedAccessException: 40102: Missing authorization token, Resource:sb://[namespace].servicebus.windows.net/[queue]. TrackingId:452c2534-d3e6-400f-874f-09be324e9e11_G27, SystemTracker:[namespace].servicebus.windows.net:[queue], Timestamp:12/1/2016 11:38:56 AM ---> System.ServiceModel.FaultException: 40102: Missing authorization token, Resource:sb://[namespace].servicebus.windows.net/[queue]. TrackingId:452c2534-d3e6-400f-874f-09be324e9e11_G27, SystemTracker:[namespace].servicebus.windows.net:[queue], Timestamp:12/1/2016 11:38:56 AM
I cannot see any reason that the Azure Service Bus endpoint would return this error message except that because the token provider is not being used. Why would the channel ignore the TokenProvider and what do I have to do to pass the token correctly?
edit:
I have inspected the raw WCF message traffic for the port in question as well as one using the SB-Messaging adapter, which works as expected. The difference is that the SB-Messaging adapter's messages contain a SOAP header like:
<Authorization xmlns="http://schemas.microsoft.com/servicebus/2010/08/protocol/">SharedAccessSignature sr=[really long encoded string]</Authorization> and my custom binding port's messages do not. So it is true that the problem is a missing Authorization SOAP header; but the question persists - why isn't the channel adding this header?
edit #2:
I have decompiled Microsoft.ServiceBus.dll and I believe I've found the class that actually creates the WCF messsage, Microsoft.ServiceBus.Messaging.Sbmp.SbmpMessageCreator. It has this method:
private Message CreateWcfMessageInternal(string action, object body, bool includeToken, string parentLinkId, RetryPolicy policy, TrackingContext trackingContext, RequestInfo requestInfo)
{
Message message = Message.CreateMessage(this.messageVersion, action, body);
MessageHeaders headers = message.Headers;
headers.To = this.logicalAddress;
string sufficientClaims = this.GetSufficientClaims();
if (this.linkInfo != null)
{
if (!string.IsNullOrEmpty(this.linkInfo.TransferDestinationEntityAddress))
{
SecurityToken authorizationToken = this.GetAuthorizationToken(this.linkInfo.TransferDestinationEntityAddress, sufficientClaims);
if (authorizationToken != null)
{
SimpleWebSecurityToken webSecurityToken = (SimpleWebSecurityToken) authorizationToken;
if (webSecurityToken != null)
this.linkInfo.TransferDestinationAuthorizationToken = webSecurityToken.Token;
}
}
this.linkInfo.AddTo(headers);
}
if (includeToken)
{
ServiceBusAuthorizationHeader authorizationHeader = this.GetAuthorizationHeader(sufficientClaims);
if (authorizationHeader != null)
headers.Add((MessageHeader) authorizationHeader);
}
if (this.messagingFactory.FaultInjectionInfo != null)
this.messagingFactory.FaultInjectionInfo.AddToHeader(message);
if (!string.IsNullOrWhiteSpace(parentLinkId))
message.Properties["ParentLinkId"] = (object) parentLinkId;
if (trackingContext != null)
TrackingIdHeader.TryAddOrUpdate(headers, trackingContext.TrackingId);
MessageExtensionMethods.AddHeaderIfNotNull<RequestInfo>(message, "RequestInfo", "http://schemas.microsoft.com/netservices/2011/06/servicebus", requestInfo);
return message;
}
So thinking about it logically, there are two reasons the Authorization header would be missing:
includeToken is false (Why would this be so?)
GetAuthorizationHeader() returns null (Why?)
edit #3:
I have compiled and run the example code and this works. The only significant difference between my code and his is that mine includes a line which calls out to Azure Key Vault:
var kv = new KeyVaultClient(this.GetAccessToken);
var key = kv.GetSecretAsync(this.KeyVaultUri.AbsoluteUri, this.SharedAccessSecretName).Result;
var sharedAccessKey = key.Value;
var tokenProvider = TokenProvider.CreateSharedAccessSignatureTokenProvider(
this.SharedAccessSecretName,
sharedAccessKey);
bindingParameters.Add(new TransportClientEndpointBehavior { TokenProvider = tokenProvider });
This is an asynchronous method that returns a Task. Can it be that blocking on the result of this Task somehow doesn't do what would be expected in certain situations, and this is messing up the configuration of the WCF channel somehow? As I said, I am certain this code runs and assigns the TokenProvider. I am now merely not certain when it runs.
D'OH!
I had neglected to realise that the very old version of Microsoft.ServiceBus.dll we still have in the solution for interop with the (equally old) on premises version of Service Bus (Service Bus for Windows Server) was the one referenced by my project. For whatever reason this version just doesn't do what it's supposed to, and doesn't give any indication that it's bypassing the intended behaviour. Updating to have the current NuGet package for Service Bus fixes the problem.
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.
Sorry to bother you guys again.
I am going to consume a WCF service on a server. The service was created by outside. If I look at it in the browser, it is fine. Please see the image below.
To consume it, I add service reference. with the url http://wsvc01/BOERPI/BOERPI.svc
Then I instantiate the proxy by the code.
BOERPI.PostPhoneCallResponse client = null;
client = new BOERPI.PostPhoneCallResponse();
double x = client.ActualCallCharge; // suppose to get a proper value but not
Some of the code of the service is:
[ServiceContract]
public interface iBOERPI
{
[OperationContract]
PostPhoneCallResponse PostPhoneCall(PostPhoneCallRequest objCDRRequest);
[DataContract]
public class PostPhoneCallResponse
{
[DataMember]
public double ActualCallCharge = -1.0;
I assume the service code is 100% right, is any thing wrong when I consume the service?
When I righted click the definition of PostPhoneCallResponse, it is:
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "4.0.0.0")]
[System.Runtime.Serialization.DataContractAttribute(Name="PostPhoneCallResponse", Namespace="http://schemas.datacontract.org/2004/07/nsBOERPI")]
[System.SerializableAttribute()]
public partial class PostPhoneCallResponse : object, System.Runtime.Serialization.IExtensibleDataObject, System.ComponentModel.INotifyPropertyChanged {
[System.NonSerializedAttribute()]
private System.Runtime.Serialization.ExtensionDataObject extensionDataField;
[System.Runtime.Serialization.OptionalFieldAttribute()]
private double ActualCallChargeField;
Thanks.
client = new BOERPI.PostPhoneCallResponse(); You are trying to use your DataContract here instead of Service client.
Check under Service References for your service name you used in your client application and
use it:
eg.
using(var client = new BingMapsGeocodeService()) // This should be your service client name
{
}
Update:
Sending and Received messages using request and response objects:
You need to create a request object as per your operation shows:
var request = new PostPhoneCallRequest(){ // populate all your properties you need to send to the service};
var client = new BOERPI.MyClient(); // Instantiate your client with the name you have given for your service client.
PostPhoneCallResponse response = client.PostPhoneCall(request); // You are sending your request and getting a response as PostPhoneCallResponse object
I have an ASP application which is client of WCF SERVICE1 , which is client of WCF SERVICE2.
I have added IDispatchMessageInspector and IClientMessageInspector to WCF SERVICE1.
Now I need to pass a custom value from ASP to WCF1 , then to WCF2.
from ASP to WCF1 it is trivial , via Message Headers.
The question is , how to pass a custom value from IDispatchMessageInspector.AfterReceiveRequest(request from ASP received by WCF1) to IClientMessageInspector.BeforeSendRequest(prepare to send request to WCF2) operation of WCF SERVICE 1 ?
Is there is some context which could be used ?
What does your code look like? Assuming that first Dispatch Message Inspector is the one making the request to WCF2, then simply using message properties would suffice.
However, if your dispatch message inspector does something; then the request continues processing and it is the service implementation that actually calls WCF2, then you'll need to jump through a few more hoops. In general, I'd say you'd need the inspector to put some data in the service request message properties that the service implementation would need to pick up and copy to the message to send to WCF2 so that the client inspector can pick them up.
That's ugly, and would kinda make the whole process more brittle.
Can you elaborate a bit more what you're trying to do? What kind of data are you hoping to pass around this way?
In my case, I had to identify and log nested service calls requested by client.
To do that, I stamp each service call by ThreadStatic property and add this property to the header of client call(service1 count as client for service2) than in AfterReceiveRequest method I have checked its existance. If exists,current method was requested by parent service.
public class GenericMessageInspector : IDispatchMessageInspector, IClientMessageInspector
{
[ThreadStatic]
private static string _masterServiceGUID;
public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
{
if (request.Headers.Action == null)
return null;
//Control request header for nested call
string masterRequestId = string.Empty;
var IsMasterExist = request.Headers.FindHeader("MasterServiceGUID", "namespace");
if (IsMasterExist > -1)
{
//requested by internal service
masterRequestId = request.Headers.GetReaderAtHeader(IsMasterExist).ReadInnerXml();
}
}
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
if (!String.IsNullOrEmpty(_masterServiceGUID))
{
request.Headers.Add(MessageHeader.CreateHeader("MasterServiceGUID", "namespace", _masterServiceGUID));
}
return null;
}
}
}
We've got the following WCF Service Contracts:
[ServiceContract(Namespace = "http://example.com", Name = "Service1")]
public interface IService1
{
[OperationContract]
[FaultContract(typeof(Fault1))]
ValidateUserResult ValidateUser(
string username,
string password);
}
[ServiceContract(Namespace = "http://example.com", Name = "Service1")]
public interface IService1Async
{
[OperationContract(AsyncPattern = true)]
[FaultContract(typeof(Fault1))]
IAsyncResult BeginValidateUser(
string username,
string password,
AsyncCallback callback,
object userState);
ValidateUserResult EndValidateUser(IAsyncResult asyncResult);
}
[DataContract(Namespace = "http://example.com")]
public class Fault1
{
}
We are calling the async version of ValidateUser in the client side and we are throwing a FaultException<Fault1> on the server, but all the client receives is the base FaultException.
What can be the reason the contractually-specified fault is not being received?
We found now why. We are generating the fault from a Service Behaviour using the ProvideFault method. There we use code similar to the example at IErrorHandler.ProvideFault in msdn
The only difference was that we weren't passing the right action on the Message.CreateMessage overload. We copied exactly what gets generated in the case that we manually throw the fault and voila.
My excuses for not giving that last detail :-)
Can you show us the catch statements for your call? The contract and everything looks fine to me...
In which order do you check for faults?? You would have to check for FaultException<Fault1> before checking for FaultException or CommunicationException - any chance you might have that order mixed up somehow?
Does it work when you call the sync version of the method?