I only need to sign one specific field inside a WCF message.
The class has the next aspect:
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")]
[System.ServiceModel.MessageContractAttribute(WrapperName="simpleInputData",
ProtectionLevel = ProtectionLevel.None
, IsWrapped=true)]
public partial class SimpleOperationRequest
{
[System.ServiceModel.MessageHeaderAttribute(
ProtectionLevel = ProtectionLevel.None)]
public BusinessHeader businessHeader;
[System.ServiceModel.MessageHeaderAttribute(
ProtectionLevel = ProtectionLevel.None)]
public TechnicalHeader technicalHeader;
[System.ServiceModel.MessageBodyMemberAttribute(
ProtectionLevel = ProtectionLevel.Sign, Order = 0)]
public SimpleInput simpleInput;
[System.ServiceModel.MessageBodyMemberAttribute(
ProtectionLevel = ProtectionLevel.None, Order = 1)]
public Attachment attachment;
[...]
}
As you can see, I only need sign simpleInput field, but, when I run the code, the package sent is (only show the body node):
[...]
<s:Body u:Id="_3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<simpleInputData xmlns="http://xxxx/simple">
<simpleInput>
<in>llega?</in>
</simpleInput>
<attachment>
<ImageData>iVBORw0K...5CYII=</ImageData>
</attachment>
</simpleInputData>
</s:Body>
[...]
In the code, you can see that the whole body node is signed.
How could I obtain only the node "simpleInput" signed??
Thanks a lot in advance!
Not possible in WCF. You must sign the whole body or nothing of it. You could choose which headers to sign though.
Related
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?
I am trying to consume a REST API to Get data by httpclient, encountered a parsing problem ,{"Error in line 1 position 95. Expecting element 'workflow' from namespace 'http://schemas.datacontract.org/2004/07/'.. Encountered 'Element' with name 'workflow', namespace ''. "}
the client code is
string baseUri = "/rest/workflows/";
client = CreateClient(baseUri);
HttpRequestMessage request = CreateRequest(baseUri);
var task = client.SendAsync(request);
HttpResponseMessage response = task.Result;
response.EnsureSuccessStatusCode();
response.Content.ReadAsAsync<collection>().ContinueWith(wf =>
{
Console.WriteLine(wf.Result.workflow.Length);
});
the data classes
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "http://www.w3.org/2005/Atom", IsNullable = false)]
public partial class collection
{
private workflow[] workflowField;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute("workflow", Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
public workflow[] workflow
{
get
{
return this.workflowField;
}
set
{
this.workflowField = value;
}
}
}
and the response xml file is in this format
<collection xmlns:ns2="http://www.w3.org/2005/Atom">
<workflow uuid="5ffbde8c-c430-4851-9c83-164c102a4d68">
<name>Remove a Volume</name>
<categories>
<category>Decommissioning</category>
</categories>
</workflow>
</collection>
I can get the string by using response.Content.ReadAsStringAsync() and save it to xml file, then ,i deserilize it to collection,can succeed ,but need and a default namespace to the serizliazer
XmlSerializer serializer = new XmlSerializer(typeof(collection), "xmlns:ns2=\"http://www.w3.org/2005/Atom\"");
c = serializer.Deserialize(stream) as collection;
anyone can help on this
You should not touch the generated file from xsd.exe tool.
Just explicitly set that you want to use the XmlSerializer instead of the DataContractSerializer used by default with XmlMediaTypeFormatter by setting UseXmlSerializer = true.
So you must create a specific type formatter like this :
var formatters = new List<MediaTypeFormatter>() {
new XmlMediaTypeFormatter(){ UseXmlSerializer = true } };
And use it as a parameter of the ReadAsAsync method :
private async Task<T> ReadAsync<T>(HttpResponseMessage response)
=> await response.Content.ReadAsAsync<T>(formatters);
Your namespaces do not match; your xml declares a namespace alias (ns2) for the atom address, but the namespace of the collection element is still empty, since it doesn't use that alias (it is not ns2:collection). Either the xml is wrong or the code is. If the xml cannot be changed, then simply set the namespace on the [XmlRoot(...)] to be the empty string. If the C# is correct and the xml is wrong, then make it the namespace instead of an alias:
<collection xmlns="http://www.w3.org/2005/Atom">
<workflow uuid="5ffbde8c-c430-4851-9c83-164c102a4d68">
<name>Remove a Volume</name>
<categories>
<category>Decommissioning</category>
</categories>
</workflow>
</collection>
or identically:
<ns2:collection xmlns:ns2="http://www.w3.org/2005/Atom">
<workflow uuid="5ffbde8c-c430-4851-9c83-164c102a4d68">
<name>Remove a Volume</name>
<categories>
<category>Decommissioning</category>
</categories>
</workflow>
</ns2:collection>
I had the exact same issue when reading data from my web api. What solved the issue for me is to decorate the class in the client with the [DataContract(Namespace="namespacefromyourwebapi")] attribute and for each property in your class decorate it with the [DataMember] attribute.
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've detected a strange behavior calling a WCF Service from a Powershell script. Using the command 'New-WebServiceProxy' from Powershell 2.0 get's you the abillity to send requests to a Webservice from a PS script. But i got some problems with System.DateTime objects on the service side, the value on the server side is always DateTime.Min.
So i created a small test service an script and i can reproduce this error. I used a 'standard' WCF-Project from VS2010 and extedended the 'DataContract' Class with a DateTime Property:
[DataContract]
public class CompositeType
{
bool boolValue = true;
string stringValue = "Hello ";
[DataMember]
public bool BoolValue
{
get { return boolValue; }
set { boolValue = value; }
}
[DataMember]
public string StringValue
{
get { return stringValue; }
set { stringValue = value; }
}
[DataMember]
public DateTime Datum { get; set; }
}
Powershell script to call the service:
cls
$serv = New-WebServiceProxy -uri 'http://localhost:50176/TestService.svc?wsdl' - Namespace wt
$data = [wt.CompositeType](New-Object wt.CompositeType)
$data.StringValue = "abcd"
$data.BoolValue = $true
$data.Datum = Get-Date
$serv.GetDataUsingDataContract($data)
If needed, i can send you a dropbox link for the zipped project.
Regards Uwe
I've never used powershell before but thought I'd take a long overdue look at it for this question!
The proxy object $data can have a date property set but, despite what your code looks like its doing, $data isn't the real object, just an XML proxy of it.
If you enter command "$data" you'll see what looks like an XmlSerialized version of the object (has xxSpecified properties for the bool and DateTime). It does reflect changes made by e.g. "$data.Datum = Get-Date".
The proxy is deserialised back to an instance of MyCompositeType when you call GetUsingDataContract (as its passed as a parameter and sent using XML) which you can see by putting breakpoints on the property get/setters prior to calling it.
As part of this deserialization, only the StringValue makes it which is because the Xml serialization for the other properties will only include values where "xxxSpecified" is true.
If you set the "xxxSpecified" properties in the proxy they will serialize back correctly.
But the best fix is to change their DataMember attribute to:
[DataMember(IsRequired=true)]
Which should just work with the code you've got.
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.