Pass multiple parameters into WCF Web API-service - wcf

I'm wondering about the behind the scenes magic that's happening when you create a WCF-Web service.
In one old project I got methods that I can call from JavaScript that look like this
[OperationContract]
[WebInvoke(Method = "POST", ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]
IEnumerable<Result> SearchObjects(string x, int y, double z);
And this works when I send { "x": "something", "y": 1, "z": 1.5 } from JavaScript.
A couple of months after the creation of that webservice, I found the WCF Web API and tried to make something similar.
Difference was that I created the route in my Global.asax with the HttpServiceHostFactory()
Now when I try to call the method, I get an exception like this
Exception Details: System.InvalidOperationException:
The HttpOperationHandlerFactory is unable to determine the input parameter that should be associated with the request message content for service operation 'Invoke_LoginRequest'. If the operation does not expect content in the request message use the HTTP GET method with the operation. Otherwise, ensure that one input parameter either has it's IsContentParameter property set to 'True' or is a type that is assignable to one of the following: HttpContent, ObjectContent1, HttpRequestMessage or HttpRequestMessage1.
And to get it to work, I need to declare the method like this (VB.Net)
Public Function Invoke_LoginRequest(ByVal request As HttpRequestMessage(Of JsonValue)) As HttpResponseMessage(Of String)
But then I need to parse the JsonValue manually. So how does the old version really work? And is there any way I could get that behaviour back?
Best regards
Jesper

1) Define a class containing the data that you want to receive, i.e,
public class Model
{
public string x { get; set; }
public int y { get; set; }
public double z { get; set; }
}
2) Define the operation parameter as an ObjectContent<Model>
public HttpResponseMessage Post(ObjectContent<Model> c){
Model m = c.ReadAs();
...
}
HTH
Pedro

Related

Support aliased arguments in get requests for web api

Background
I have a web api project which uses complex types for GET requests, here is an example of a controller method, and its associated complex type
[RoutePrefix("api")]
public class MyController : ApiController
{
[Route("Something")]
public IHttpActionResult GetSomething([FromUri]RequestObject request)
{
// do something to get "data"
return Ok(data);
}
}
// elsewhere
public class RequestObject
{
[Required]
public string SomeValue{get;set;}
}
This works with a url such as http://domain.com/api/Something?SomeValue=foo.
I would like to use alias' for these parameters, for which I will do some complex stuff (once I have this working) but effectively I have defined an attribute AliasAttribute.
[AttributeUsage(AttributeTargets.Property,AllowMultiple=true)]
public class AliasAttribute : Attribute
{
public string ParameterName { get; private set; }
public AliasAttribute(string parameterName)
{
this.ParameterName = parameterName;
}
}
Which I would like to adorn onto my request model like so:
// elsewhere
public class RequestObject
{
[Required,Alias("val")]
public string SomeValue{get;set;}
}
Allowing my url to shorten to http://domain.com/api/Something?val=foo. This is a contrived and simplified example, but hopefully demonstrates the background.
Problem
ModelBinding in web api has become very complex compared to Mvc model binding. I am getting twisted up between IModelBinder, IValueProvider, HttpParameterBinding et al.
I would like an example of where I should hook in to the model binding to allow me to write the value to my model from the querystring - note that I only use this aliasing behaviour when the route uses the FromUri attribute (see MyController.GetSomething above).
Question title: Support aliased arguments in get requests for web api. I think you are re-inventing a wheel here AliasAttribute , and have not given a really good reason why you don't want to use community ways of doing this.
I have done something similar with Newtonsoft.Json serializer. But if you want something ootb I'd have to google around.
public class RequestObject
{
[Required]
[JsonProperty("vla")]
public string SomeValue{get;set;}
}
Example SO that uses it: .Net NewtonSoft Json Deserialize map to a different property name
Here is a more agnostic way to do it.
[DataContract]
public class RequestObject
{
[DataMember(Name="val", IsRequired=true)]
public string SomeValue{get;set;}
}

What does my WCF service expect the request data to look like?

I've got a WCF-hosted service right now which is self-hosted and defined like this:
[OperationContract]
[WebInvoke(RequestFormat = WebMessageFormat.Json, Method = "PUT", UriTemplate = "/device")]
void updateDeviceLevel(ZDevice device);
The ZDevice class looks like this:
public class ZDevice {
public bool? newPowerState { get; set; }
public int nodeId {get; set;}
}
I have a simple Mac client which consumes the service by using an http post. It posts {"newLevel":27,"nodeId":6} to the \devices url and .NET magically stuffs the values into a ZDevice object for me. All is well here.
Now however, I need to add some basic security to the mix. I've done this by adding a new parameter and some "RequestWrapping" to the method call:
[OperationContract]
[WebInvoke(RequestFormat = WebMessageFormat.Json, BodyStyle=WebMessageBodyStyle.WrappedRequest, Method = "PUT", UriTemplate = "/device")]
void updateDeviceLevel(string password, ZDevice device);
What I'm trying to do now is figure out what syntax the server is expecting from the consuming clients. I'd hoped that posting in {"password":"somepwd", "newLevel":27,"nodeId":6} would work, but .NET is no longer able to "deserialize" that into the ZDevice object like it did before.
Anyone got some suggestions for me?
Thanks
It should look like this:
{"password":"somepwd", "device": {"newLevel":27,"nodeId":6}}
Each property on the JSON object has a value; and in the case of device it's just a new object.
Note that in your ZDevice class you called it newPowerState, but in JSON you are calling it newLevel. In your class it's also a bool, but in JSON you are assigning it an int. Something isn't matching up.
Based on your C#, I'd expect it to look like this:
{"password":"somepwd", "device": {"newPowerState":true,"nodeId":6}}
The property names in your JSON object should match the parameter / property names in C#.

Send custom data as object using WCF

In my latest project, I wish to send custom data as an object using WCF. Reason for this is that I won't have to update each client when a new data class is introduced.
However, when I try to send this data, it never arrives at the client side.
To give a short example:
A custom class:
[DataContract]
public class MyData
{
[DataMember]
public string Name { get ;set; }
[DataMember]
public id Value { get; set; }
public MyData(string name, id value)
{
this.Name = name;
this.Value = value;
}
}
When I want to send this to the client, I use:
object obj = new MyData("test",1);
service.SendDataToClient(obj);
The client never receives this event from the service when I send it as object. However, when I send it as MyData instead of object, it works as it should. How can I send this as object?
If you want to send custom data the easy way is using XElement instead of object. Another approach is defining all possible transfered types by ServicKnownTypeAttribute or creating generic resolver (in such case you must share contract assembly between client and service). Check this great article.

WCF 4: Passing Empty parameters on a GET request

I'm creating an API which will just use a get request to return some search results from the database, I'm trying to make it so that optional parameters can be passed (easy with WCF) but also so that if parameters are specfied in the query string as long as they are empty they will be ignored by the service.
However if you have the a query string with empty parameters it will return a bad request (400) by the server e.g.
Using a end-user point of your choice pass the following querystring
http://www.exampleservice.com/basic/?apiKey=1234&noOfResults=3&maxSalary=&minSalary=&ouId=0&keywords=Web+Developer
Note that maxSalary and minSalary are not passing values
You then have the following WCF service:
[OperationContract]
[WebGet(UriTemplate = "basic/?apiKey={apiKey}&noOfResults={noOfResults}&maxSalary={maxSalary}&minSalary={minSalary}&ouId={ouId}&keywords={keywords}", BodyStyle = WebMessageBodyStyle.Bare)]
public List<SearchResultsDto> BasicSearch(string keywords, string apiKey, int noOfResults, int maxSalary, int minSalary, int ouId)
{
//Do some service stuff
}
This will cause a 400 error, please can someone explain how you pass empty parameters across to a WCF service or is this just not possible?
Currently passing null or an empty parameter is not supported in WCF, the main solution to this problem is to override the querystringconverter which handles the url as it comes through the pipe but before it reaches the operation contract.
An excellent example of implmenting an extension of the querystringconverter is found here:
In the WCF web programming model, how can one write an operation contract with an array of query string parameters (i.e. with the same name)?
HOWEVER
sadly there is a bug in WCF 4 where you cannot override the querystringconverter, this has been addressed by Microsoft and will be fixed in the SP1 release coming this year.
Until then there is no clean way to deal with this situation other than to handle the exception and return a status code of 400 (bad request) - good documentation of the api should handle this in the interim.
Is it just the integers giving you trouble? Maybe you can try making them nullable?
int? MaxSalary
hope this helps
You could send in "-1", and treat that in your business logic as not sent.
It can be handled in multiple ways. Since you are talking about a REST service that can have optional parameters, my suggestion will be do the something like this.
Create a DataObject that will be accepeted as parameter to this method.
[ServiceContract]
public interface IService1
{
[OperationContract]
[WebGet(RequestFormat=WebMessageFormat.Json)]
RequestObject BasicSearch(RequestObject apiKey);
}
public class Service1 : IService1
{
public RequestObject BasicSearch(RequestObject obj)
{
//Do some service stuff
return obj;
}
}
[DataContract]
public class RequestObject
{
[DataMember]
public string Keywords {get; set;}
[DataMember]
public string ApiKey {get; set;}
[DataMember]
public int NoOfResults { get; set; }
}
Advantages (am going to be short, ping me back for details)
No change in service signature
contract does not change
you will get the flexibility of have
null parameters
you can always extend the number of
parameters without any impact to
existing services
below is the sample input and output from fiddler
note: in the request part i havent passed anything to NumberOfResults intentionally to prove

How to wrap all results of an EndPoint in OperationResult?

I want to wrap each result from one Wcf service in my application in something like
public class OperationResult{
public string Status;
public string Data;
}
even if my contract looks like
[ServiceContract]
internal interface ITest
{
[OperationContract,
WebGet(
RequestFormat = WebMessageFormat.Json,
ResponseFormat = WebMessageFormat.Json)]
MyDc EchoDc(MyDc input);
}
From what I've read the potential extensibility points are IServiceBehavior, IEndpointBehavior, IContractBehavior, IOperationBehavior.
Any thoughts where I can hook my wrapping magic ?
Look # my answer here:
How to customize the process employed by WCF when serializing contract method arguments?
There it is mentioned how you can replace an object of one type to another type while it is being returned.
I think thats not possible via extensionpoints on the WCF framework because what you watn to do is to change your contract.
The contract is a c# interface which is used by your client.
You have to write an own proxy class for use by your client where you can map the operation results to whatever you like:
class ServiceProxy : ClientBase<YourServiceInterface>
{
public OperationResult EchoDc(MyDs input)
{
MyDc result = Channel.EchoDc(input);
return new OperationResult( ... // map your operation result here))
}
}