Disclaimer: Yes I know WCF isn't the Web API or MVC. However I'm trying to figure out a hack to do sort of what MVC does with routing so that I may be able to keep the same method names in my .svc contract behind the scenes when I wire up different requests with different sets of querystring params.
So I have this dumb WCF service and I want to have an overload of Get methods in here. But I can't, WCF bitches about it when I try to make a call to this service.
I have 2 methods named "Get" that I'm trying to essentially overload but differentiate by querystring. Notice the two below have different sets of querystrings in the uri so my intent is not having to have all these different Get, GetbySomething, GetBySomething2. It'd be nice to just stick with the name Get() on all mymethods and just overload them / differentiate them so that the request coming in can wire up to maybe the querystring differentiation sort of like MVC does:
[WebGet(UriTemplate = "?securityToken={securityToken}&brokerId={brokerId}&orderStatus={orderStatus}&pageNumber={pageNumber}&pageSize={pageSize}&sortBy={sortBy}&purchaseOrderNumber={purchaseOrderNumber}")]
public OrderResponse Get(string securityToken, string brokerId, string orderStatus, string pageNumber, string pageSize, string sortBy, string purchaseOrderNumber)
[WebGet(UriTemplate = "?securityToken={securityToken}&brokerId={brokerId}&purchaseOrderNumber={purchaseOrderNumber}&startDate={startDate}&endDate={endDate}&pageNumber={pageNumber}&pageSize={pageSize}&sortBy={sortBy}")]
public OrderResponse Get(string securityToken, string brokerId, int purchaseOrderNumber, string startDate, string endDate, string pageNumber, string pageSize, string sortBy)
Error in response coming from WCF: "Cannot have two operations in the same contract with the same name, methods Get and Get in type OrderService violate this rule. You can change the name of one of the operations by changing the method name or by using the Name property of OperationContractAttribute. "
ok whatever fine. So can I somehow use a route table in my global.asax to essentially do what MVC can do?
RouteTable.Routes.Add(new ServiceRoute("Inventory", new WebServiceHostFactory(), typeof(InventoryService)));
MVC allows me to map but takes into account the querystring as part of the matching:
Just an example, I could take into account querystrings ?someParam={someValue}
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}?someParam={someValue}",
defaults: new { id = RouteParameter.Optional }
);
yes I also know in MVC you can overload your methods to differentiate them via parameters in the controller and as you can see here my WCF contract methods for Get() do have different set of params so one would think WCF would let you differentiate the same method name by params but it doesn't.
You can overload the functions to accept different parameters
Because in WCF the way you specify the querystring match part is via attribute:
[WebGet(UriTemplate = "?securityToken={securityToken}&brokerId={brokerId}&purchaseOrderNumber={purchaseOrderNumber}&startDate={startDate}&endDate={endDate}&pageNumber={pageNumber}&pageSize={pageSize}&sortBy={sortBy}]
so is there a way to use my RouteTable.Routes.Add(new ServiceRoute("V1/Inventory", new WebServiceHostFactory(), typeof(InventoryService))); that I have in my global.ascx to somehow all me to make Get method names unique and overload them by differentiating them by querystring?
Because right now to fix this I'd have to make one of my Get method names something else which is IMO stupid and unclean. I'd have to do something like GetByDateRange. So I'd have to do something like:
[WebGet(UriTemplate = "?securityToken={securityToken}&brokerId={brokerId}&orderStatus={orderStatus}&pageNumber={pageNumber}&pageSize={pageSize}&sortBy={sortBy}&purchaseOrderNumber={purchaseOrderNumber}")]
public OrderResponse Get(string securityToken, string brokerId, string orderStatus, string pageNumber, string pageSize, string sortBy, string purchaseOrderNumber)
[WebGet(UriTemplate = "?securityToken={securityToken}&brokerId={brokerId}&purchaseOrderNumber={purchaseOrderNumber}&startDate={startDate}&endDate={endDate}&pageNumber={pageNumber}&pageSize={pageSize}&sortBy={sortBy}")]
public OrderResponse GetByDateRange(string securityToken, string brokerId, int purchaseOrderNumber, string startDate, string endDate, string pageNumber, string pageSize, string sortBy)
This puts me back into RPC style method names behind my web service handling and I hate that. We are using WCF restfully as well so I'd like to keep this clean by just having a bunch of overloaded Put() methods, Get(), etc.
Related
I have a GET request with URL which is http://foo.com/accounts/123/users/456. For the API Controller, the routing will be like:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/accounts/{accountid}/users/{userid}",
defaults: new { accountid = RouteParameter.Optional, userid = RouteParameter.Optional }
);
The controller will like:
public string Get(string accountid, string userid)
{
....
}
So, rather than receive two parameters, is that possible to make the controller to receive one object which contains these parameters like:
public string Get(Query query)
{
string accountid = query.AccountID;
string userid= query.UserID;
}
Thanks for the help!!
Absolutely. From the ASP.NET site:
By default, Web API uses the following rules to bind parameters:
If the parameter is a “simple” type, Web API tries to get the value from the URI. Simple types include the .NET primitive types
(int, bool, double, and so forth), plus TimeSpan, DateTime, Guid,
decimal, and string, plus any type with a type converter that can
convert from a string. (More about type converters later.)
For complex types, Web API tries to read the value from the message body, using a media-type formatter.
I'm trying to create a RESTful WCF service. I get a runtime error saying you can't have 2 of the same method names in your service class:
[OperationContract, WebGet]
...
string Get();
[OperationContract, WebGet]
...
string Get(int id);
Why in the world can't you! they are both different signatures. If I'm to get this to work like REST like I want, which is to be able to overload stuff like this, then that would suck and WCF is not for me.
Has anyone been able to have 2 of the same method names in your so-called attempt to make WCF restful?
you can override service method by using OperationContract name property with define separate routes.Your service interface should look like
[OperationContract(Name = "GetemployeeName")]
string Get(string param);
[OperationContract(Name = "GetemployeeAge")]
bool Get(long sysID);
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
I would like to know the parameters for the invoke method used by Salesforce to invoke remote web services. I have a service that I'm suposed to be able to invoke, but the service WSDL does not define the security requirements, so I'm hoping I can add that information manually (The services uses WS-Security passed through Soap headers).
Here is what I (think I) know so far:
WebServiceCallout.invoke(
Class servicePort, //Usually set to "this", contains httpheader info as well as ?
request_x, //Request object, defining schema, properties, and field order
response_map_x, //Response object, defining schema, properties, and field order
new String[]{
String endpoint, //Endpoint of the service
String ?, //what is this?
String methodSchema, //Schema for the request object?
String method, //Name of the request method?
String responseSchema, //Schema for the response object?
String response, //Name of the response object?
String responseClass} //Name of the Apex class the response will be converted to
);
Can anyone help fill in the gaps?
Here's what I have discovered so far for WebServiceCallout.invoke:
Object servicePort - A class with the following variables:
String enpoint_x: containing the service endpoint (not sure if necessary)
Map<String,String> inputHttpHeaders_x: custom httpHeaders
Map<String,String> outputHttpHeaders_x: I think this is the httpHeaders that were returned
String clientCertName_x: Used in configuring an SSL cert?
String clientCert_x: Used in configuring an SSL cert?
String clientCertPassword: Used in configuring an SSL cert?
Integer timeout_x: How long (in milliseconds?) to wait for the response
String[] ns_map_type_info: The first String is the namespace of the service schema, the second is the name of the object that contains the Apex classes defining the schema objects
Object request_x - The Apex object that will form the XML schema object
Map<String, Object> response_map_x - Object is the object that the result is to be unserialized into. String is the name of Object variable.
String[] {
endpoint - The service endpoint
soapAction - If the service call requires a soapAction, put it here. Otherwise leave blank.
methodSchema - Schema for the request object
method - Name of the request method
responseSchema Schema for the response
responseClass The Apex class that the response will be unserialized into
}
In addition, Soap headers can be inserted by creating an object in the servicePort class as well as
a String with the same variable name+"_hns" that specifies the namespace for that object:
public SoapSecurity Security;
private String Security_hns = "Security=http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
The apex XML Schema objects should contain variables for each child element (or attribute). Arrays whose variable names match certain patterns define how the object variables are used in the xml.
Given the following example XML:
<foo a="b"><bar>baz</bar></foo>
The Apex classes would be something like this:
public class MyService {
public class bar {
public String bar;
private String[] bar_type_info = new String[] {'bar','http://www.w3.org/2001/XMLSchema','string','0','1','true'};
private String[] apex_schema_type_info = new String[] {'http://schema.myservice.com', 'false', 'false'};
private String[] field_order_type_info = new String[] {'bar'};
}
public class foo {
public MyService.bar bar;
public String a;
private String[] bar_type_info = new String[] {'bar','http://schema.myservice.com','bar','0','1','true'};
private String[] a_att_info = new String[] {'a'};
private String apex_schema_type_info = new String[] {'http://schema.myservice.com','false','false'};
private String[] field_order_type_info = new String[] {'bar'};
}
}
Here's a (brief) breakdown of these objects:
If the variable represents another XML element or a text node, then there needs to be a matching _type_info String[] e.g. bar_type_info. The elements of this array are:
1. XML element name
2. Schema
3. XML type
4. minOccurs
5. maxOccurs (set to '-1' for unbounded)
6. isNillable
If the variable represents an attribute, then there must be a matching _att_info String[] e.g. a_type_info. Thise simply contains the XML name of the attribute.
Note that if an class variable name is a reserved word, then _x is appended to it e.g. bar_x. This would affect the other variables names: bar_x_type_info. The Apex Developer's Guide explains their rules for names, but if you are manually creating it, I think you can give it whatever name you want--the arrays determine the XML element name...
I have not found a way to represent a simple XML type that also contains an attribute: e.g.
<foo bar="baz">bar</foo>
The apex_schema_type_info array specifies information about the XML element represented by the class:
1. Schema
2. 'true' if elementFormDefault="qualified"
3. 'true' if attributeFormDefault="qualified"
I'm still rather fuzzy on what 2 and 3 actually do, but it seems to affect how child elements (and attributes) inherit the parent namespace (whether it's implied or must be specified in the resulting XML).
field_order_type_info simply specifies the order of the child elements.
Please feel free to correct or clarify...
There is the Force.com Apex Code Developers Guide - Understanding the Generated Code, but it is currently fairly sparse on details for WebServiceCallout.invoke(...).
There is also Apex Web Services and Callouts, again not any useful details.
Up-voting Ideas: Documentation for WebServiceCallout may help in the long run.
Salesforce have just done an opensource release of wsdl2apex on Github, so you can now check to code to see exactly what is occurring. Announcing the Open-Source WSDL2Apex Generator
UPDATE 10/19/2010
I know I asked this question a while ago, but the workarounds shown in these answers are hardly satisfactory, and this is still a common problem for many. WCF just isn't flexible. I started my own open source C# library for creating REST services without WCF. Check restcake.net or rest.codeplex.com for info on said library.
END UPDATE
UPDATE 8/2/2012
ASP.NET Web API (previously WCF Web API, the replacement for REST WCF) uses Json.NET by default
END UPDATE
The DataContractJsonSerializer is unable to handle many scenarios that Json.Net handles just fine when properly configured (specifically, cycles).
A service method can either return a specific object type (in this case a DTO), in which case the DataContractJsonSerializer will be used, or I can have the method return a string, and do the serialization myself with Json.Net. The problem is that when I return a json string as opposed to an object, the json that is sent to the client is wrapped in quotes.
Using DataContractJsonSerializer, returning a specific object type, the response is:
{"Message":"Hello World"}
Using Json.Net to return a json string, the response is:
"{\"Message\":\"Hello World\"}"
I do not want to have to eval() or JSON.parse() the result on the client, which is what I would have to do if the json comes back as a string, wrapped in quotes. I realize that the behavior is correct; it's just not what I want/need. I need the raw json; the behavior when the service method's return type is an object, not a string.
So, how can I have my method return an object type, but not use the DataContractJsonSerializer? How can I tell it to use the Json.Net serializer instead?
Or, is there someway to directly write to the response stream? So I can just return the raw json myself? Without the wrapping quotes?
Here is my contrived example, for reference:
[DataContract]
public class SimpleMessage
{
[DataMember]
public string Message { get; set; }
}
[ServiceContract]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class PersonService
{
// uses DataContractJsonSerializer
// returns {"Message":"Hello World"}
[WebGet(UriTemplate = "helloObject")]
public SimpleMessage SayHelloObject()
{
return new SimpleMessage("Hello World");
}
// uses Json.Net serialization, to return a json string
// returns "{\"Message\":\"Hello World\"}"
[WebGet(UriTemplate = "helloString")]
public string SayHelloString()
{
SimpleMessage message = new SimpleMessage() { Message = "Hello World" };
string json = JsonConvert.Serialize(message);
return json;
}
// I need a mix of the two. Return an object type, but use the Json.Net serializer.
}
I finally figured out a solution to this. It's not what I would have preferred (which would be to return the specific object type, and somehow instruct WCF to use a Json.Net serializer, instead of the DataContractJsonSerializer), but it is working great, and it's simple and clear.
Extending my contrived example using this new solution:
[WebGet(UriTemplate = "hello")]
public void SayHello()
{
SimpleMessage message = new SimpleMessage() {Message = "Hello World"};
string json = JsonConvert.Serialize(message);
HttpContext.Current.Response.ContentType = "application/json; charset=utf-8";
HttpContext.Current.Response.Write(json);
}
Note the return type of void. We do not return anything, since it would be serialized with DataContractJsonSerializer. Instead, I write directly to the response output stream. Since the return type is void, the processing pipeline doesn't set the content-type to the default type of "application/json", so I set it explicitly.
Because this uses HttpContext, I'm guessing it will only work if you have [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)] on your service class, since that will force requests to the service to go through the ASP.NET pipeline. Without the asp.net compatibility, the HttpContext will not be available, since wcf hosting is supposed to be host agnostic.
Using this method, the results look perfect in firebug for GET requests. Correct content-type, correct content length, and raw json, not wrapped in quotes. And, I'm getting the serialization I want using Json.Net. Best of both worlds.
I'm not 100% positive of what obstacles I might run into regarding deserialization, when my service methods have [DataContract] object types as input parameters. I'm assuming the DataContractJsonSerializer will be used for that too. Will cross that bridge when I come to it...if it creates a problem. It hasn't so far, with my simple DTOs.
UPDATE
See Oleg's answer (the UPDATE2 part). He changes the return type of the service method from void to System.ServiceModel.Channels.Message, and rather than using HttpContext.Current.Response.Write(), he uses:
return WebOperationContext.Current.CreateTextResponse (json,
"application/json; charset=utf-8", Encoding.UTF8);
Which is indeed a better solution. Thank you Oleg.
UPDATE 2
There is yet another way of accomplishing this. Change your service's return type from Message to Stream, and return this:
WebOperationContext.Current.OutgoingResponse.ContentType = "application/json; charset=utf-8";
return new MemoryStream(System.Text.Encoding.UTF8.GetBytes(json));
I haven't done any specific tests, but it's possible that this would be a better choice for methods that could potentially return large amounts of data. I don't know if that matters for non-binary data though. Anyway, a thought.
It seems to me that you use not correct DataContractJsonSerializer. What is strange is: you don't define ResponseFormat = ResponseFormat.Json attribute for the public SimpleMessage SayHelloObject() method.
Moreover if you have {"Message":"Hello World"} in a string and display it in debugger it will be display as "{\"Message\":\"Hello World\"}", so exactly like you see string json = JsonConvert.Serialize(message); (Json.Net). So it seems to me that you have in both cases the same results.
To verify this use a client software which read the results. See some examples
JQuery ajax call to httpget webmethod (c#) not working
Can I return JSON from an .asmx Web Service if the ContentType is not JSON?
How do I build a JSON object to send to an AJAX WebService?
UPDATED: In your code you define method SayHelloString(). It's result are a string. If you call the method this string will be one more time JSON serialized. JSON serialization of the string {"Message":"Hello World"} is a quoted string (see http://www.json.org/ definition for not a object, but a string) or exactly string "{\"Message\":\"Hello World\"}". So everything is correct with both methods of your Web Service.
UPDATED 2: I am glad that my tip from "Update" part of my answer helped you to swich of the double JSON serialization.
Nevertheless I would recommend you to change a little the solution to stay more at the WCF concept.
If you want implement a custom encoding of the web responce in WCF (see http://msdn.microsoft.com/en-us/library/ms734675.aspx) your WCF method should better return Message instead of void:
[WebGet(UriTemplate = "hello")]
public Message SayHello()
{
SimpleMessage message = new SimpleMessage() {Message = "Hello World"};
string myResponseBody = JsonConvert.Serialize(message);
return WebOperationContext.Current.CreateTextResponse (myResponseBody,
"application/json; charset=utf-8",
Encoding.UTF8);
}
You can of cause use another Message formater: for example CreateStreamResponse (or some other see http://msdn.microsoft.com/en-us/library/system.servicemodel.web.weboperationcontext_methods(v=VS.100).aspx) instead of CreateTextResponse.
If you want to set some additional HTTP headers or Http status code (for example in case of some error) you can do this with this way:
OutgoingWebResponseContext ctx = WebOperationContext.Current.OutgoingResponse;
ctx.StatusCode = HttpStatusCode.BadRequest;
At the end I want repeat my question from a comment: could you explain why you want use Json.Net instead of DataContractJsonSerializer? Is it performance improvement? Do you need implement serialization of some data types like DateTime in other way as DataContractJsonSerializer do? Or the main reason of your choose of Json.Net is some other?