WCF REST parameters cannot contain periods? - wcf

So here's a super simple interface for doing rest in WCF.
[ServiceContract]
public interface IRestTest
{
[OperationContract]
[WebGet(UriTemplate="Operation/{value}")]
System.IO.Stream Operation(string value);
}
It works great, until i try to pass a string with periods in it, such as a DNS name... I get a 404 out of asp.net.
Changing the UriTemplate to stick parameters into the query string makes the problem go away. Anyone else see this or have a workaround?

That is true that a path part cannot contain a period or many other special characters for that matter. I experienced the same problem a while back and received an answer from TechNet team stating that querystring is your only option the could find. Sorry
http://social.msdn.microsoft.com/Forums/en-US/wcf/thread/d03c8331-1e98-4d5d-82a7-390942a93012/

I have a service with almost the exact signature. I can pass values that have a "." in the name. For example, this would work on mine:
[OperationContract]
[WebGet(UriTemplate = "category/{id}")]
string category(string id);
with the url http://localhost/MyService.svc/category/test.category I get the value `"test.category" passed in as the string value.
So there must be some other issue. how are you accessing the URL? just directly in the browser? Or via a javascript call? Just wondering if it is some error on the client side. The server passes the value just fine. I would recommending trying to access the url in your browser, and if it doesn't work then post exactly what URL you are using and what the error message was.
Also, are you using WCF 3.5 SP1 or just WCF 3.5? In the RESTFul .Net book I'm reading, I see there were some changes with regards to the UriTemplate.
And finally, I modified a simple Service from the RESTFul .Net book that works and I get the correct response.
class Program
{
static void Main(string[] args)
{
var binding = new WebHttpBinding();
var sh = new WebServiceHost(typeof(TestService));
sh.AddServiceEndpoint(typeof(TestService),
binding,
"http://localhost:8889/TestHttp");
sh.Open();
Console.WriteLine("Simple HTTP Service Listening");
Console.WriteLine("Press enter to stop service");
Console.ReadLine();
}
}
[ServiceContract]
public class TestService
{
[OperationContract]
[WebGet(UriTemplate = "category/{id}")]
public string category(string id)
{
return "got '" + id + "'";
}
}

Here's an example HttpModule that fixes the 'period' when they occur in REST parameters. Note that I've only seen this happen in the Development Server (aka Cassini), in IIS7 it seems to work without this "hack". The example I've included below also replaces the file extension '.svc' which I adapted from this answer. How to remove thie “.svc” extension in RESTful WCF service?
public class RestModule : IHttpModule
{
public void Dispose()
{
}
public void Init(HttpApplication context)
{
context.BeginRequest +=
delegate
{
HttpContext ctx = HttpContext.Current;
string path = ctx.Request.AppRelativeCurrentExecutionFilePath;
int i = path.IndexOf('/', 2);
if (i > 0)
{
int j = path.IndexOf(".svc", 2);
if (j < 0)
{
RewritePath(ctx, path, i, ".svc");
}
else
{
RewritePath(ctx, path, j + 4, "");
}
}
};
}
private void RewritePath(HttpContext ctx, string path, int index, string suffix)
{
string svc = path.Substring(0, index) + suffix;
string rest = path.Substring(index);
if (!rest.EndsWith(ctx.Request.PathInfo))
{
rest += ctx.Request.PathInfo;
}
string qs = ctx.Request.QueryString.ToString();
ctx.RewritePath(svc, rest, qs, false);
}
}

Related

Cannot de-serialise SOAP responses from Magento 2 SOAP API - mismatch between XML Namespace in response and Service Reference WSDL

The short story is - I need a way for a single code base to be able to connect to multiple SOAP APIs where each API's WSDL is essentially the same except that the XML Namespace varies site by site.
The long story is (sorry there is a lot of this):
My .NET 4.5 application acts as a client to the Magento SOAP API (downloads orders, uploads products, stock levels etc).
The application uses a Service Reference to a stock Magento WSDL, and for Magento 1.x this worked fine - the application could connect to any website's Magento API just by passing a different endpoint URL when instantiating the client.
So then Magento 2 came along, and I wanted to make a new version of the application that could interface with it. However a significant challenge arose.
I started by creating a Service Reference to a known Magento 2 website API's WSDL (this was not straightforward as under Magento 2 the WSDL is only exposed if the request is OAUTH authenticated, but that is another story). The application worked fine when connecting to that same website API. However when any other endpoint URL is used to instantiate the client, every method call seems to result in a null response object. If the Service Reference is re-created from the target website's WSDL, then it starts working. Obviously I cannot do this and compile a new version of the application for every different possible target website!
I looked at the difference between my reference WSDL and another, and traced the request and response with Fiddler, and I noticed something that I believe to be the root cause of the problem. Unlike under Magento 1.x, a Magento 2 WSDL has XML Namespaces specific to the website the WSDL came from. This translates to different Namespace values in class attributes in the Reference.cs of the Service Reference, for example:
Magento 1.x attributes (note the generic Namespace value):
[System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:Magento")]
[System.ServiceModel.ServiceContractAttribute(Namespace="urn:Magento", ConfigurationName="MagentoAPI.Mage_Api_Model_Server_Wsi_HandlerPortType")]
[System.ServiceModel.MessageBodyMemberAttribute(Namespace="urn:Magento", Order=0)]
Magento 2 attributes:
[System.Xml.Serialization.XmlTypeAttribute(Namespace="http://www.my-magento-site.net/soap/default?services=salesCreditmemoRepositoryV1")]
[System.ServiceModel.ServiceContractAttribute(Namespace="http://www.my-magento-site.net/soap/default?services=salesCreditmemoRepositoryV1", ConfigurationName="MagentoV2SoapApiV1.SalesCreditmemoRepositoryV1.salesCreditmemoRepositoryV1PortType")]
[System.ServiceModel.MessageBodyMemberAttribute(Namespace="http://www.my-magento-site.net/soap/default?services=salesCreditmemoRepositoryV1", Order=0)]
My conclusion is that the SOAP response cannot be deserialised unless the XML Namespace used within the response exactly corresponds to that in the class attributes in the Reference.cs.
Initially I tried altering the class attribute values at runtime using various techniques but this didn't work.
Now I am trying to intercept the response using an IClientMessageInspector, and replacing the given XML Namespace with the one in my Reference.cs. My code is below and it seems to correctly make the replacement, but STILL the response object is null!
public class CustomInspectorBehavior : IEndpointBehavior
{
private readonly CustomMessageInspector _clientMessageInspector = new CustomMessageInspector();
public string LastRequestXml { get { return _clientMessageInspector.LastRequestXml; } }
public string LastResponseXml { get { return _clientMessageInspector.LastRequestXml; } }
public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters) {}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) {}
public void Validate(ServiceEndpoint endpoint) {}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { clientRuntime.MessageInspectors.Add(_clientMessageInspector); }
}
public class CustomMessageInspector : IClientMessageInspector
{
public string LastRequestXml { get; private set; }
public string LastResponseXml { get; private set; }
public void AfterReceiveReply(ref Message reply, object correlationState)
{
LastResponseXml = reply.ToString();
var doc = new XmlDocument();
var ms = new MemoryStream();
var writer = XmlWriter.Create(ms);
reply.WriteMessage(writer);
writer.Flush();
ms.Position = 0;
// Do namespace substitution
doc.Load(ms);
doc.DocumentElement.SetAttribute("xmlns:ns1", "http://www.my-reference-address.net/soap/default?services=salesCreditmemoRepositoryV1");
ms.SetLength(0);
writer = XmlWriter.Create(ms);
doc.WriteTo(writer);
writer.Flush();
ms.Position = 0;
var reader = XmlReader.Create(ms);
reply = Message.CreateMessage(reader, int.MaxValue, reply.Version);
}
public object BeforeSendRequest(ref Message request, System.ServiceModel.IClientChannel channel) { LastRequestXml = request.ToString(); }
}
public static salesCreditmemoRepositoryV1PortTypeClient GetCreditMemosServiceClient(string apiAddress)
{
const string serviceName = "salesCreditmemoRepositoryV1";
var apiClient = new salesCreditmemoRepositoryV1PortTypeClient(GetSoap12Binding(), new EndpointAddress(apiAddress));
var requestInterceptor = new CustomInspectorBehavior();
apiClient.Endpoint.Behaviors.Add(requestInterceptor);
return apiClient;
}
There is only 1 XML Namespace in the entire response, and like I said, my AfterReceiveReply method seems to be making the substitution, so I am now REALLY STUCK for what to do next!
Example response:
<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope" xmlns:ns1="http://www.my-magento-site.net/soap/default?services=salesCreditmemoRepositoryV1">
<env:Body>
<ns1:salesCreditmemoRepositoryV1GetListResponse>
<result>
<items/>
<searchCriteria>
<filterGroups>
<item>
<filters>
</filters>
</item>
</filterGroups>
</searchCriteria>
<totalCount>0</totalCount>
</result>
</ns1:salesCreditmemoRepositoryV1GetListResponse>
</env:Body>
</env:Envelope>
Note: I had a similar issue where my application's service request would get a 500 error response unless the XML Namespace in the request (which is given by the Reference.cs) matched the target site. I got around this successfully by doing a substitution using the BeforeSendRequest method of the above IClientMessageInspector. I have left that code out for clarity.
I got it working by changing the AfterReceiveReply method. For some reason, using an XmlDocument to help create the modified reply works.
private const string ReplyXmlNameSpacePattern = #"xmlns:ns1=""(.+)\?services=(.+)""";
public void AfterReceiveReply(ref Message reply, object correlationState)
{
// Read reply XML
var doc = new XmlDocument();
var ms = new MemoryStream();
var writer = XmlWriter.Create(ms);
reply.WriteMessage(writer);
writer.Flush();
ms.Position = 0;
doc.Load(ms);
// Replace XML namespace in SOAP envelope
var replacementXmlNameSpace = #"xmlns:ns1=""http://www.my-reference-address.net/soap/default?services=$2""";
var newReplyXml = Regex.Replace(doc.OuterXml, ReplyXmlNameSpacePattern, replacementXmlNameSpace, RegexOptions.Compiled);
doc.LoadXml(newReplyXml);
// Write out the modified reply
ms.SetLength(0);
writer = XmlWriter.Create(ms);
doc.WriteTo(writer);
writer.Flush();
ms.Position = 0;
var reader = XmlReader.Create(ms);
reply = Message.CreateMessage(reader, int.MaxValue, reply.Version);
}

About wcf rest return list

Server side interface:
[OperationContract]
[WebGet(UriTemplate = "GetCardInfoByCardNumber/?cardNumber={cardNumber}&SerialNumber={SerialNumber}&token={token}", ResponseFormat = WebMessageFormat.Json)]
IList<Cards> GetCardInfoByCardNumber(string cardNumber, string SerialNumber, string token);
Server side implementation:
public IList<Cards> GetCardInfoByCardNumber(string cardNumber, string SerialNumber, string token)
{
if (BaseClass.HasPermission(token))
return cm.GetCardInfoByCardNumber(cardNumber, SerialNumber);
else
return null;
}
Client side:
class Program
{
static void Main(string[] args)
{
TestResWCF();
Console.ReadLine();
}
static List<Cards> TestResWCF()
{
List<Cards> a = null;
string ServiceUri = "http://192.168.15.18:8089/GetCardInfoByCardNumber/?cardNumber=HH-120109-017&SerialNumber=&token=123456";
WebClient proxy = new WebClient();
proxy.Encoding = Encoding.UTF8;
proxy.DownloadStringCompleted += new DownloadStringCompletedEventHandler
(
(s, e) =>
{
Stream stream = new MemoryStream(Encoding.Unicode.GetBytes(e.Result));
DataContractJsonSerializer obj = new DataContractJsonSerializer(typeof(List<Cards>));
a = obj.ReadObject(stream) as List<Cards>;
}
);
proxy.DownloadStringAsync(new Uri(ServiceUri));
return a;
}
List<Cards> a return empty string always! How to return data? Thank you very much!
Do you have any example? sorry for my bad english
Can you share the code for the "Cards" and "Card" classes?
I'm very sure that most likely, it is not decorated with [DataContract] and [DataMember] properly. You may have decorated the type with [DataContract], but forgotten to annotate the members you want with [DataMember]. Or alternatively, you may not have decorated them at all, and something else is happening behind the scenes. In 99% of the scenarios, misdecoration or improper decoration or mis-initialization of the serializer is the reason this error occurs.
If you did decorate it properly, there may be some other problems. It's hard to tell with 100% certainty from just the detail you've provided, so I'd enable tracing to generate tracing logs (that you can then view/share with SvcTraceViewer) and turn on debug exceptions (by turning on the includeExceptionDetailInFaults setting).

How does a WSDL parser decide to generate a void method for a Request/Reply operation?

I have spent 5 hours on this, and I've already been up 30 hours writing this contract-first spec ("aggressive" deadline, ergo stupid) and I cannot see what I'm missing.
I do not want a one-way operation as I expect faults. I have already built a simple WCF service and examined the WSDL that it generates and it does round trip to a void method but I've been staring at it so long (and WSDL 1.1 is so annoying at the best of times - roll on 2.0 please) that I can no longer see what the magic trick is.
Can anyone provide some very simple WSDL explaining the magic? I'm targetting both jax-ws 2.2 and WCF 3.5/4.0 with this WSDL. I am hand-writing the WSDL and every time I try to build proxies (in java or .net) it always builds a method with the return message. I'm losing it.
A "void" method doesn't necessarily mean that it's a one-way operation. The two operations below are different:
[ServiceContract]
public interface ITest
{
[OperationContract(IsOneWay = true)]
void Process1();
[OperationContract]
void Process2();
}
The first one is really a one-way operation - any exceptions / faults thrown by the server will not be propagated to the client, while in the second one, although it doesn't "return" anything, if the server throws an exception (e.g., a FaultException), the exception will be returned back to the caller.
Update: to answer the question posed in the title, the WSDL parser decides to generate a void operation (at least the one used by WCF) if the schema for the output message of the operation is empty.
For example, in the code below:
public class StackOverflow_8316567
{
[ServiceContract]
public interface ITest
{
[OperationContract(IsOneWay = true)]
void Process1();
[OperationContract]
void Process2();
[OperationContract]
int Add(int x, int y);
}
public class Service : ITest
{
public void Process1() { }
public void Process2() { }
public int Add(int x, int y) { return x + y; }
}
static Binding GetBinding()
{
var result = new BasicHttpBinding();
return result;
}
public static void Test()
{
string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
host.AddServiceEndpoint(typeof(ITest), GetBinding(), "");
host.Description.Behaviors.Add(new ServiceMetadataBehavior { HttpGetEnabled = true });
host.Open();
Console.WriteLine("Host opened");
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}
If you run it and browse to http://localhost:8000/service?wsdl you'll see that:
The operation Process1 (under wsdl:portType/wsdl:operation) only has an input message
both Add and Process2 (r/r operations) have both an input and the output messages
Now, the message part for the output message for those 2 operations reference their schema in the imported schema (at http://localhost:8000/service?xsd=xsd0). You can see that:
The schema for the response for the Process2 operation (complex type Process2Response) is an empty element (i.e., an empty sequence)
The schema for the response for the Add operation (AddResponse) is a sequence containing one element (an xs:int value).
So the processor will generate a void method for Process2 (since it doesn't return anything) and a non-void method for Add.

Something like an operation filter in WCF REST?

I am looking for something like the AuthorizeAttribute in MVC, something I can use like this:
[WebGet(UriTemplate = "data/{spageNumber}")]
[WebCache(CacheProfileName = "SampleProfile")]
[WcfAuthorize]
public IEnumerable<SampleItem> GetCollection(String spageNumber)
{
Int32 itemsPerPage = 10;
Int32 pageNumber = Int32.Parse(spageNumber);
return Enumerable.Range(pageNumber * itemsPerPage, itemsPerPage)
.Select(i => SampleItem.Create(i));
}
That WcfAuthorizeAttribute, will try to authenticate the user with FormsAuthentication, and set the context's IPrincipal, or return a HTTP 401 Unauthorized.
I have tried with a IOperationBehavior, but I gets executed in the first method, whichever it be, not in the method I have set the attribute.
How can this be achieved in WCF REST?
Regards.
PS: I have seen the RequestInterceptor example in the Starter Kit, but what I want is put it in some methods only, and the example looks like a filter you execute in all the operations.
You can use AOP to achieve this. I have used PostSharp as an AOP tool to achieve this functionality. You can also find a sample on their website. The OnMethodEntry gets executed before a method (that is decorated with this attribute) is executed and you can perform your validation there.
I did a quick sample to test this and it worked.
[Serializable]
[ProvideAspectRole(StandardRoles.Security)]
public class WcfAuthorizeAttribute : OnMethodBoundaryAspect
{
public override void OnEntry(MethodExecutionArgs args)
{
//extract forms authentication token here from the request and perform validation.
}
}
And you could decorate your WCF methods like below.
[ServiceContract]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class Service1
{
[WcfAuthorize]
[WebGet(UriTemplate = "")]
public List<SampleItem> GetCollection()
{
return new List<SampleItem>() { new SampleItem() { Id = 1, StringValue = "Hello" } };
}

WCF Rest Error Handling

I'm having a mind blowing problem using WCF 4.0 RESTful service. I am trying to make a rest service that will return, in case of an error, a xml document describing the problem
ex :
<ErrorHandler>
<cause>Resource not available</cause>
<errorCode>111103</errorCode>
</ErrorHandler>
In order to make this i've created a default REST service using the template provided by visual studio
Here is my service Class :
public class Service1
{
// TODO: Implement the collection resource that will contain the SampleItem instances
[WebGet(UriTemplate = "")]
public List<SampleItem> GetCollection()
{
// TODO: Replace the current implementation to return a collection of SampleItem instances\
// throw new WebException("lala");
throw new WebFaultException<ErrorHandler>(new ErrorHandler { cause = "Resource not available", errorCode = 100 }, System.Net.HttpStatusCode.NotFound);
//return new List<SampleItem>() { new SampleItem() { Id = 1, StringValue = "Hello" } };
}
[WebInvoke(UriTemplate = "", Method = "POST")]
public SampleItem Create(SampleItem instance)
{
// TODO: Add the new instance of SampleItem to the collection
return new SampleItem() { Id = 3, StringValue = "59" };
}
[WebGet(UriTemplate = "{id}")]
public SampleItem Get(string id)
{
// TODO: Return the instance of SampleItem with the given id
throw new NotImplementedException();
}
[WebInvoke(UriTemplate = "{id}", Method = "PUT")]
public SampleItem Update(string id, SampleItem instance)
{
// TODO: Update the given instance of SampleItem in the collection
throw new NotImplementedException();
}
[WebInvoke(UriTemplate = "{id}", Method = "DELETE")]
public void Delete(string id)
{
// TODO: Remove the instance of SampleItem with the given id from the collection
throw new NotImplementedException();
}
}
}
As you can see from the code above i am throwing a WebFaultException in the GetCollection method. that should put in the body of the response a "ErrorHandler" object.
Here is how my ErrorHandler class looks like :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Runtime.Serialization;
namespace WcfRestService1
{
[DataContract]
public class ErrorHandler
{
[DataMember]
public int errorCode { get; set; }
[DataMember]
public string cause { get; set; }
}
}
The crazy thing is that this thing works but it down't :)). What i'm trying to say is that visual studio is giving me an error saying that the WebFaultException is not caught by the user code :| and it suspends my app. If i press continue everything works as it should.
Here are some pictures describing my problem:
First step in fiddler :
First Step
Next Visual Studio's error: Visual Studio Error
Finally After pressing continue everything works :
It makes no sense to me and i have no idea why this thing is happening and how to fix it :P. I've searched the web for days trying to find a solution with no luck. I'm using Visual Studio 2010 Ultimate
Best Regards :)
Nothing is wrong here. You are debugging, an exception is thrown, it breaks, you continue and it works as it should.
I suspect you have set the exception handling option (Ctrl+Alt+E) to break when exceptions are thrown. ("Thrown" in the options) This will cause break whenever exception is thrown regardless it is handled.
Exceptions that are thrown in WCF operations will be handled by the WCF runtime and if they are faults, they will be sent back as such so that channel is not faulted.
Now with regard to sending back an XML, you can just send string representation of the XML using WebFaultException<string>.