When I woke up this morning I had not planned on the day unfolding like this, but an email from a colleague started me on a path and here is where it led.
If you create a service that uses UriTemplates with the WCF REST Starter Kit and you include Query String parameters in the UriTemplate you might end up with something like this
[OperationContract] [WebGet(UriTemplate = "subscriptions?page={page}&pagesize={pagesize}", ResponseFormat = WebMessageFormat.Xml)] ItemInfoList GetPageInXml(int page, int pagesize); [WebGet(UriTemplate = "subscriptions")] [OperationContract] ItemInfoList GetItemsInXml();
This contract will result in a runtime exception when WCF validates the contract
System.InvalidOperationException was unhandled by user code Message="UriTemplateTable does not support multiple templates that have equivalent path as template 'subscriptions?format=json' but have different query strings, where the query strings cannot all be disambiguated via literal values. See the documentation for UriTemplateTable for more detail." Source="System.ServiceModel.Web"
What WCF is saying is that it does not have enough information to map a URI to a method on the contract and the Query Strings don’t help in this case.
My advice is to avoid putting Query String parameters in the UriTemplate. You can still access them from the incoming request. To make it easier, I created a class called QueryString to help you.
QueryString will retreive arguments from the IncommingRequest object. It also provides overloads that allow you to specify default values and a bool flag to indicate if the parameter is required or not.
Suppose you want to add paging support for a RESTful URI. I would need to allow the caller to specify the starting index and the page size. If they don’t specify either, they get some defaults like start at page 0 and use a page size of 5.
Because I don’t want to put the QueryString parameters in my contract it would look like this
[WebGet(UriTemplate = "subscriptions")] [OperationContract] List GetItemsInXml() { // Get the optional start index parameter int startIndex = GetInt("start", // Name of Query Parameter 0); // Default value // Get the optional page size parameter int pageSize = GetInt("count", // Name of Query Parameter 5); // Default value // Get the items - our db layer doesn't support paging in this case List Items = GetItems(); // Support the paging here return Items.Skip(startIndex).Take(pageSize).ToList(); } // Call this service using a URI like this (query parameters are optional) http://localhost/service.svc/subscriptions?start=5&count=10
Sometimes you want to require a query string parameter. If the caller doesn't supply it, you want to return a status code of 400 - Bad Request. To do this you can do this
// Get a required parameter - no default // If no supplied, the response code is set to 400 and an argument exception is thrown with the response body suppressed. string tag = GetRequiredString("tag")
In my negative test cases (ones that should fail) I wanted to insure that the HTTP status code was 400 (Bad Request) and not something else like 500 (Internal Server Error). Though MSTest allows you to specify an [ExpectedException(...)], you cannot validate the status code from the attribute. To do this I had to use the following code.
// Should error on invalid count [TestMethod()] [ExpectedException(typeof(WebException))] public void ShouldBadRequestOnInvalidCount() { bool required = false; string tag = null; string start = "3"; string count = "bar"; HttpWebRequest request = (HttpWebRequest)WebRequest.Create(CreateRequestUri(required, tag, start, count)); try { HttpWebResponse response = (HttpWebResponse)request.GetResponse(); } catch (WebException webex) { // Shoud return a 400 Assert.AreEqual(HttpStatusCode.BadRequest, ((HttpWebResponse)webex.Response).StatusCode); throw; } }
At first I left off the ExceptedException attribute because I was catching the exception, but then you won't get a test failure if no exception is thrown so it is important to verify the status code and the ExpectedException.
You are welcome to check this class out and give it a try - let me know what you think
PingBack from http://www.anith.com/?p=8558