Lets say I have a WCF service Service1.svc that contains GetData(value).
[ServiceContract]
public interface IService1
{
[OperationContract]
string GetData(int value);
}
public class Service1 : IService1
{
public string GetData(int value)
{
return string.Format("You entered: {0}", value);
}
}
I also have a client that already auto-generate the proxy to consume this service, something like this:
using (var client = new ServiceReference1.Service1Client())
{
var result = client.GetData(1);
//Assert.AreEqual("You entered: 1", result);
}
Now, I removed that WCF service and replaced it with a new Web API service, something like this:
[RoutePrefix("Service1.svc")]
public class DataController : ApiController
{
[HttpGet]
[Route("GetData")]
public string GetDataOld(int value)
{
return string.Format("You entered: {0}", value);
}
}
So when I try to use the client Service1Client() it doesn't work anymore. I am pretty sure this is possible but what I have to do in order to accomplish this goal?
Update 05/23/2016
Since this is not possible, I decided to create a proxy so the client(s) can easily implement the new Restful Web API.
WebApi is designed to be REST endpoints, so the wcf way of var client = new ServiceReference1.Service1Client() is counter to the concept.
Rather you should try to access it through the url, for your example it'll be something like ~\Service1.svc\GetData\1. Also check your route configuration to be sure.
Your ServiceReference1.Service1Client() is directly related to WCF. You cannot reuse it for interacting with WebApi endpoints.
If you want a client that interacts with your WebApi endpoints, you will need to write a wrapper client yourself. RestSharp is my library of choice for such a thing. Example:
var client = new RestClient("http://localhost:8000");
int value = 12345;
var request = new RestRequest($"Service1.svc/GetData/{value}", Method.GET);
IRestResponse response = client.Execute(request);
var content = response.Content; // returns "You entered: 12345"
(untested)
This is possible, and we are currently doing something similar - we replaced our WCF/SOAP service endpoints with WebAPI/JSON endpoints, without changing the client's proxies.
To do this, you have to use the WebHttpBinding and add some more attributes to your service interface so that the proxy generates the right calls:
[ServiceContract]
public interface IService1
{
[OperationContract]
[WebGet]
string GetData(int value);
[OperationContract]
[WebInvoke(Method="POST", RequestFormat = WebMessageFormat.Json)]
void PostData(MyDataType data);
}
This will make the proxy use HTTP calls, rather than SOAP calls, to access your service. As you can see, this works both for GET and POST requests, and automatically serializes your data as JSON (or XML, if you prefer).
There are more ways to customize it - like using the UriTemplate property to specify more complex HTTP routes (or have two methods, GetData and PostData, both map to the same route and differ only by verb, the RESTful way). And there are caveats as well - from what I could see, you can only pass strings to GET requests, not ints. But all in all, it works quite well.
Would we have chosen to go with WCF proxies mapped to WebAPI endpoints if we had started the project with WebAPI/REST? Probably not. But this allowed us to switch back-ends with relatively little front-end code changes.
Related
I have a curious problem with a WCF service I've not been able to solve.
When I call a specific method on my service, it simply does not return to the client until the binding times out, although it already has returned on the server and a return message has been sent to the client.
I'm using ChannelFactory<T> on the client side, and shared interfaces that are defined like so:
[ServiceContract]
interface IService {
[OperationContract] bool Upload(Guid id, string name, byte[] data);
[OperationContract] bool Unlock(Guid id);
}
[ServiceContract(Name = "IService")]
interface IServiceAsync : IService {
[OperationContract] Task<bool> UploadAsync(Guid id, string name, byte[] data);
[OperationContract] Task<bool> UnlockAsync(Guid id);
}
The IService interface is implemented by the server class, the IServiceAsync interface is only used on the client (and works automagically).
The server is implemented in a standalone console application using ServiceHost, the server class looks like this:
[ServiceBehavior(InstanceContextMode = Single, ConcurrencyMode = Multiple)]
static class Server : IService {
public bool Upload(Guid id, string name, byte[] data) { ... }
public bool Unlock(Guid id) { ... }
}
On the client side, I create a channel using a ChannelFactory<T> like this:
static IServiceAsync GetService() {
EndpointAddress endpoint = new EndpointAddress(/* some Uri */);
NetTcpBinding binding = /* NetTcpBinding without security */;
ChannelFactory<IServiceAsync> factory =
new ChannelFactory<IServiceAsync>(binding, endpoint);
factory.Open();
IServiceAsync proxy = factory.CreateChannel();
((IServiceChannel)proxy).Open();
return proxy;
}
In my scenario, I then upload some files to the server, an operation that can be canceled by the user at any time. The code for the upload looks something like this:
static async void UseService(IServiceAsync proxy) {
try {
byte[] buffer;
do {
cancellationToken.ThrowIfCancellationRequested();
buffer = GetBytes();
bool uploaded = await proxy.UploadAsync(id, name, buffer);
} while(buffer.Length > 0);
((IClientChannel)proxy).Close();
}
catch (Exception ex) {
try {
proxy.Unlock(id);
}
catch {
// Connection may have failed
}
throw;
}
}
Now when the upload finishes everything works fine, but when the upload is canceled, and cancellationToken.ThrowIfCancellationRequested() throws an exception, I want to unlock the server.
So I call proxy.Unlock(id), which gets called on the server, returns there (confirmed by writing to the console immediately before return true; on the server), but does not return on the client side until the binding times out.
I looked at the network traffic once and confirmed that there is actually a return message being sent to the client. (I don't actually know whether proxy.Unlock(id) just returns or throws a CommunicationException, i suspect the second one.)
TL;DR: A WCF method does not return on the client side although it already returned on the server side, other methods work fine.
So, in case you're still here after that wall of text: what is wrong here?
How can I debug or solve this?
If you need any more information please let me know.
Update:
Thanks to Daniel's comment I did some tracing and managed to reproduce the problem on a small scale.
The problem only seems to happen when ConfigureAwait(false) is used when awaiting async WCF methods, an exception is thrown, and there is no context switch before the next call to a WCF method. I uploaded a small test project on GitHub.
Could this be a bug in the automagically implemented async methods in the WCF proxy?
I am trying to test a WCF web service and am having problems when I introduce complicated return types. I have tried to keep this simple to start and am verifying I can pass data between the web service and a client. I have created the following files: MyService.svc, MyService.cs, and IMyService.cs. This is done in ASP.NET C#.
MyService.cs looks like this:
public class MyService : IMyService
{
public void DoWork()
{
}
public bool BoolTest()
{
return false;
}
public string StrTest(string input)
{
return input;
}
}
IMyService.cs Looks like this:
[ServiceContract]
public interface IMyService{
[OperationContract]
void DoWork();
[OperationContract]
bool BoolTest();
[OperationContract]
string StrTest(string input);
}
I have a console application which I connect to the Service above with a "Service Reference". When I use the code shown above, everything links up okay and I am able to make calls to the functions provided by the web service.
My problem occurs when I try to return anything more complicated such as an array, a list, or a custom object. After I add a function to the Web Service with one of these return types, then attempt to update the Service Reference in the Console Application, the reference fails to update and is lost.
Here is MyService.cs with the additional method that causes the failure:
public class MyService : IMyService
{
public void DoWork()
{
}
public bool BoolTest()
{
return false;
}
public string StrTest(string input)
{
return input;
}
public string [] StrArrTest()
{
string[] s = new[] { "test", "test2" };
return s;
}
}
Here is a copy of IMyService.cs with the additional method added that causes the error:
[ServiceContract]
public interface IMyService{
[OperationContract]
void DoWork();
[OperationContract]
bool BoolTest();
[OperationContract]
string StrTest(string input);
[OperationContract]
string [] StrArrTest();
}
If I use a List<string> as a return type or MyObject as a return type it also fails.
I am guessing there is some sort of serialization needed but am having a hard time tracking down exactly what I need to do.
Additional bits of information that might be helpful... The client and host are running on the same machine. When I access http://localhost/MyService.svc in a web browser there aren't any errors shown. When I access http://localhost/MyService.svc?wsdl a descent sized xml document appears.
Can anyone help explain what I need to do so I can return arrays and lists of objects in the web service?
I found this similar question, why does my silverlight reference to my wcf service blow up when I add a method to the wcf service that returns a generic list, but it applies to SilverLight which I am not using. I took a look at the Reference.cs file mentioned in the post, when everything is working it is populated with a bunch of stuff. When the reference is not updated correctly (after the method that returns an array is added), the Reference.cs file is empty with the exception of a comment about the file being auto-generated.
I want to create an IIS-hosted webservice which I will consume using a universal windows store aoo (windows phone/windows 8.1/windows RT).
As I understand universal applications do not support proxy class generation and SOAP calls using "Add service reference" so I need to create a RESTful webservice and manually consume it in the universal application.
I've tried dozens of tutorials and approaches throughout the net but I never managed to actually POST data to the webservice.
I need to send objects of a custom class which is defined in a shared library to the webservice. I understand that I will need to serialize the Object and include it in the POST request, however no matter what I try I end up with different issues - e.g HTTP 400 Bad Request: The incoming message has an unexpected message format 'Raw'. The expected message formats for the operation are 'Xml'; 'Json'.
I've seen several approaches to manually set the content type header, however the methods I found are not available in a universal application.
Can someone provide information or an example which is fitting my scenario (POST-ing via universal app)?
update 1: For further clarification: I am aware how WCF works and I was already able to complete a basic GET request like described in this post. However I was unable to extend that to also work with POST requests.
Some code I've tried:
public async static void SendStartup(CustomClass customObject)
{
var httpClient = new HttpClient();
var serialized = JsonConvert.SerializeObject(customObject);
var response = await httpClient.PostAsync("http://localhost:49452/Metrics.svc/LogStartup", new StringContent(serialized));
string content = await response.Content.ReadAsStringAsync();
}
Web Service Interface:
[OperationContract]
[WebInvoke(UriTemplate = "LogStartup", Method="POST", BodyStyle=WebMessageBodyStyle.Wrapped)]
string LogStartup(CustomClass obj);
Implementation:
public void LogStartup(CustomClass obj)
{
// nothing
}
This for example failes at runtime with the error mentioned above
There are two problem with your code.
1) You have to send the Content-Type header while your are making a request
var content = new StringContent(serialized,Encoding.UTF8,"application/json");
2) You have to use BodyStyle = WebMessageBodyStyle.Bare
WebMessageBodyStyle.Bare can work with one parameter as in your example, but if you want to post more parameters then you have to use WebMessageBodyStyle.Wrapped but then, your object you post should be modified as
var serialized = JsonConvert.SerializeObject(new { obj = customObject });
Here is a working code you can test with self-hosted WCF service
async void TestRestService()
{
var ready = new TaskCompletionSource<object>();
Task.Factory.StartNew(() =>
{
var uri = new Uri("http://0.0.0.0:49452/Metrics.svc/");
var type = typeof(Metrics);
WebServiceHost host = new WebServiceHost(type, uri);
host.Open();
ready.SetResult(null);
},TaskCreationOptions.LongRunning);
await ready.Task;
var customObject = new CustomClass() { Name = "John", Id = 333 };
var serialized = JsonConvert.SerializeObject(new { obj = customObject });
var httpClient = new HttpClient();
var request = new StringContent(serialized,Encoding.UTF8,"application/json");
var response = await httpClient.PostAsync("http://localhost:49452/Metrics.svc/LogStartup", request);
string content = await response.Content.ReadAsStringAsync();
}
[ServiceContract]
public class Metrics
{
[OperationContract]
[WebInvoke(Method = "POST", BodyStyle = WebMessageBodyStyle.Wrapped)]
public string LogStartup(CustomClass obj)
{
return obj.Name + "=>" + obj.Id;
}
}
public class CustomClass
{
public string Name { set; get; }
public int Id { set; get; }
}
PS: If you want to return a json response then you can use ResponseFormat=WebMessageFormat.Json. You should then change the WebInvoke attribute as
[WebInvoke(Method = "POST", BodyStyle = WebMessageBodyStyle.Wrapped,ResponseFormat=WebMessageFormat.Json)]
BTW: You can still dynamically choose the returned content type(xml or json) by setting AutomaticFormatSelectionEnabled.
Have you seen this article?
How to use HttpClient to post JSON data
Basically it seems like you need to add more parameters to your StringContent() constructor like this:
new StringContent(serialized, System.Text.Encoding.UTF8, "application/json");
One thing you need to know about Windows Communication Foundation is the ABC's.
A : Address
B : Binding
C : Contract
So the theory is incredibly simple, though while your coding it, it is quite odd. A simple tutorial can be found here or here. Several other tutorials can be found at Code Project for this exact approach.
Understanding Polymorphism may be helpful for understanding Windows Communication Foundation as it relies heavily on it.
[ServiceContract]
public interface IContent
{
[OperationContract]
void DoSomething(SomeModel model);
}
So what you're doing here is defining your service, defining your method. As I mentioned above we've explicitly declared our contract but we haven't implemented our method. Also we intend to pass SomeModel which would be our Data Contract.
We will build our model:
[DataContract]
public class SomeModel
{
[DataMember]
public string Name { get; set; }
}
The model can be incredibly simple like above, or incredibly complex. It will depend on usage.
Now we would like to implement our method:
public class Content : IContent
{
public void DoSomething(SomeModel model)
{
// Implementation
}
}
Now on the client, you simply consume your service. Once you understand the basics and how it serializes and deserializes you can use it for REST. Which tutorials also exist for that.
How do I create a dynamic response with out using the query string?
I want to dynamically output the response format based on what the user specifics inside of the message body.
For example, if the user inputs "json","xml","soap", it will return the respective format. Thanks, in advance.
public interface IReg
{
[OperationContract]
[WebInvoke]
MemberBasic Login(string uniqueID, string password, string returnFormat);
}
[DataContract(Namespace = "", IsReference=false)]
[Serializable()]
public class MemberBasic
{
#region Properties
[DataMember]
public DateTime LastModified
{
get;
set;
}
}
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public sealed class RWS : IReg
{
public MemberBasic Login(string uniqueID, string password, string returnFormat)
{
MemberBasic result = new MemberBasic();
setReturnFormat(returnFormat);
return result;
}
}
private static void Init(string returnFormat)
{
var response = WebOperationContext.Current.OutgoingResponse;
response.Headers.Add("cache-Control", "no-cache");
response.Headers.Add("Last-Modified", string.Format("{0:r}", DateTime.Today));
switch (returnFormat)
{
case "xml":
{
WebOperationContext.Current.OutgoingResponse.Format = WebMessageFormat.Xml;
WebOperationContext.Current.OutgoingRequest.Headers.Add(System.Net.HttpRequestHeader.ContentType, "application/json");
} break;
case "json":
{
WebOperationContext.Current.OutgoingResponse.Format = WebMessageFormat.Json;
} break;
default:
{
throw new ArgumentException("Return Format unrecognized; cannot complete request.",
"returnFormat");
}
}
}
The simplest way to do what you are after is to create different endpoints with different bindings. You can have one for POX, SOAP and JSON. They can share contracts and implementations but WCF/configuration is responsible for managing the request/response formats.
It doesn't make a lot of sense to have SOAP specified as the response format since, in WCF it would mean the request would also have to be a SOAP request.
You cannot have in a same endpoint a SOAP and a JSON (or POX - "plain old XML") response. SOAP is a protocol which dictates how the request and the response need to be formatted - according to the SOAP envelope version, SOAP addressing headers (or absence of them), etc. If an endpoint "talks" SOAP, it cannot talk "non-SOAP".
For changing between JSON and XML (i.e., POX), you can specify as part of the operation the format which you want to use in the return in a single endpoint. The endpoint needs to be SOAP-less (i.e., its binding must have MessageVersion.None, such as the WebHttpBinding), and have a Web behavior applied to it (usually the WebHttpBehavior, or <webHttp/> if defined in config). Such endpoints are often known as WCF WebHttp endpoints or (rather misnamed) REST endpoints.
Your example is one way to do that for Web endpoints, although you're setting the content-type to application/json if you set the response format to XML, which will likely break your clients.
I am using VSTS 2008 + .Net + C# 3.5 to develop WCF service (self-hosted as a Windows Service). From client side, I am using ChannelFactory to connect to WCF service. My confusion is, when I change client side code from "public string response" to "public string responseAliasName", the value of responseAliasName is null. But when change back to variable name response, the response value is correct (value is "hi WCF").
My confusion is, I think variable name should not matter as long as the layout is the same from client and server side. Any ideas what is wrong?
Server side code:
namespace Foo
{
// NOTE: If you change the interface name "IService1" here, you must also update the reference to "IService1" in Web.config.
[ServiceContract]
public interface IFoo
{
[OperationContract]
FooResponse Submit(string request);
}
[DataContract]
public class FooResponse
{
[DataMember]
public string response;
}
}
namespace Foo
{
// NOTE: If you change the class name "Service1" here, you must also update the reference to "Service1" in Web.config and in the associated .svc file.
public class FooImpl : IFoo
{
public FooResponse Submit(string request)
{
FooResponse foo = new FooResponse();
foo.response = "hi WCF";
return foo;
}
}
}
Client side code:
namespace Foo
{
// NOTE: If you change the interface name "IService1" here, you must also update the reference to "IService1" in Web.config.
[ServiceContract]
public interface IFoo
{
[OperationContract]
FooResponse Submit(string request);
}
[DataContract]
public class FooResponse
{
[DataMember]
// public string responseAliasName;
public string response;
}
}
namespace FooTestClient1
{
class Program
{
static void Main(string[] args)
{
ChannelFactory<IFoo> factory = new ChannelFactory<IFoo>(
"IFoo");
IFoo f = factory.CreateChannel();
FooResponse response = f.Submit("Hi!");
return;
}
}
}
you can use
[DataContract(Name="ResponseAliasName")]
public string response;
on Server side and it will work as you expect, DataContract by default uses field or property name to serialize data, and the server can't find correct data
No, the member name is included in the serialization. If you change it on the client side, it can't deserialize back into that member.
George, why not just use "Add Service Reference" to create your client? As you can see, it's dangerous to create the client-side service contracts by hand.
My confusion is, when
I change client side code from "public
string response" to "public string
responseAliasName", the value of
responseAliasName is null. But when
change back to variable name response,
the response value is correct (value
is "hi WCF").
My confusion is, I think variable name
should not matter as long as the
layout is the same from client and
server side. Any ideas what is wrong?
The variable name that you use locally in your client is totally irrelevant - the server knows nothing about that. Given your code snippet from the client:
ChannelFactory<IFoo> factory = new ChannelFactory<IFoo>("IFoo");
IFoo f = factory.CreateChannel();
FooResponse response = f.Submit("Hi!");
This will work - no problem:
FooResponse myResponseVar1 = f.Submit("Hi!");
and so will this:
FooResponse someReallyCleverVariableNameWhateverItMightBe = f.Submit("Hi!");
But the DataContract of course is a shared element that the service and client have to agree on! You cannot locally change the names of the elements in the data contract - after all, that's what really describes how your calls are going to be turned into an XML message, and these bits and pieces have to stay in sync between the server and the client in order for the client to be able to turn the message received from the server back into an object for you to use.
The ServiceContract and the DataContract must be the same on both ends - that's the basic requirement, otherwise, pretty much nothing goes at all.
Marc