Introduction
This is a demo of using multiple kinds of MS based clients, all communicating with the same WCF service. A REST based WCF service is used so that proxies don't have to be generated or any framework specific WCF limitations dealt with.
Background
I recently worked on a project that had a .NET Compact Framework and WPF client accessing the same WCF service. It was really slick, but one of the things I wanted to change about the project was that the domain and service access was duplicated between the two clients - so I wanted to come up with a proof of concept to demonstrate how to use WCF with multiple clients using the same codebase and no copied domain objects or proxies.
Using the code
The service itself is simple, just to demonstrate the basic concept. It returns a list of InfoItem
objects which have just two properties: an ID and description. Here is the method signature on the interface and implementation:
[OperationContract]
List<InfoItem> GetComplexData();
public List<InfoItem> GetComplexData()
{
Console.WriteLine("GetComplexData executing");
return new List<InfoItem>
{
new InfoItem { Id = 1, Description = "Number 1" },
new InfoItem { Id = 2, Description = "Number 2" },
new InfoItem { Id = 3, Description = "Number 3" },
new InfoItem { Id = 4, Description = "Number 4" },
new InfoItem { Id = 5, Description = "Number 5" },
};
}
Each of the clients uses a REST based WCF client to get the data in XML format and deserialize it into the list of domain objects. The domain objects used are the same file for all three clients though each of the three clients are compiled separately to different frameworks. Each of the clients submits a request similar to the below. I would eventually like to use the REST Starter Kit for all three, but I didn't want to embark on a mission to compile that on the CF and Silverlight frameworks. So for now, the RESTUtil class is a custom one with a few methods that does standard HTTP operations to get the XML response from each service:
var infoList = RESTUtil.GetList<InfoItem>(
"http://localhost:2890",
IService1Uri.GetComplexData
);
The URI used for the REST based contract is in a static class used just for the URIs so that changes can be done in one place (on the WCF interface) and don't need to be changed on any of the clients. Really though, this sample isn't using HTTP status codes and other things that would make this a "real" REST based service, I am just interested in simplifying the request/response process for simplicity of demonstration. For an excellent article on all the details of setting up a REST service, see this MSDN article: http://msdn.microsoft.com/en-us/library/dd203052.aspx.
Before I get into each of the different clients, here is the basic trick used to compile the same domain objects on all the different project types: each client is just added in the already existing file that works with the console client, but is added using the "Add as Link" functionality in the Visual Studio "Add Existing File" dialog. The only problem with using the same file on all the platforms is you have to make it compile on all three. To do that, I just added bogus attributes on all the clients where something wasn't supported, for example, DataContract
, OperationContract
, etc. Here are the ones used for the Compact Framework (the Silverlight client has similar workarounds in place):
namespace System.Runtime.Serialization
{
public class DataContractAttribute : Attribute { }
public class ServiceContractAttribute : Attribute { }
public class OperationContractAttribute : Attribute { }
public class DataMemberAttribute : Attribute
{ public int Order { get; set; } }
}
namespace System.ServiceModel.Web
{
public class WebGetAttribute : Attribute
{ public string UriTemplate { get; set; } }
}
These attributes don't actually do anything, they just allow us to use the same interface file used for the service and rich clients in the Compact Framework and Silverlight frameworks which don't have all the functionality of the standard .NET framework. The same trick is used for the domain objects and the RESTUtil class, so the only framework specific code used in any of the clients is in the BogusAttributes
class, similar to the above.
Console client
The console client was the simplest to get working, because all of the .NET framework could be used. The same would go for WPF, Web and WinForm clients.
var infoList = RESTUtil.GetList<InfoItem>(
"http://localhost:2890",
IService1Uri.GetComplexData
);
foreach (var item in infoList)
{
Console.WriteLine("Id: {0}, Description: {1}", item.Id, item.Description);
}
If all you have are these kinds of clients, you can get away with proxy-less WCF much easier, simply by doing the below. Perhaps, eventually, this will work on CF and Silverlight as well.
var channelFactory = new ChannelFactory<IService1>(new NetTcpBinding(), endPoint);
client = channelFactory.CreateChannel();
If you aren't using the standard .NET framework however, you'll need to resort to XML deserialization to get the data. I used this same method on all the clients just for consistency.
Test project
Included in the project is an integration test project that spins up a WCF host and tests the client's connection, getting just the XML, and finally using the business objects. You can use these to understand the basic concepts involved and see the raw XML returned by the service:
[TestMethod]
public void CanCallService()
{
string response = RESTUtil.GetRawResponse(endPoint + "/test/?value=47");
Assert.AreEqual(
"<string xmlns=\"http://schemas.microsoft.com/" +
"2003/10/Serialization/\">" +
"You entered: 47</string>",
response
);
}
[TestMethod]
public void CanGetComplexDataTyped()
{
var infoList = RESTUtil.GetList<InfoItem>(endPoint, IService1Uri.GetComplexData);
Assert.AreEqual(5, infoList.Count);
}
.NET Compact Framework client
The code used for the Compact Framework is shown below. The only real caveat for the Compact Framework is getting the networking functioning, which isn't easy. To get the emulator to work correctly, I had to click around in the network settings for a while, pointing them all to the same network card, which has to be the one that is currently functioning on a network (not unplugged, even to access the same machine). In addition I had to use the TcpTrace utility to forward from port 8080 to the real port. I didn't spend too much time trying to figure out why, since that isn't the focus of the article, but did get it working eventually.
infoItemBindingSource.DataSource = RESTUtil.GetList<InfoItem>(
"http://192.168.1.64:8080", IService1Uri.GetComplexData
);
Silverlight client
The only difficulty in getting the Sliverlight client working was getting the cross domain policy setup when using a self hosted WCF service. I found an article on how to achieve that, and included a reference to that in the code, basically setting up an endpoint for the two files needed on the service itself and manually returning the file contents as a string.
Since the Silverlight framework goes out of its way to disable all synchronous operations and only provides Begin/End style asynchronous operations, I implemented that one in a similar style:
RESTUtil.GetListAsync<InfoItem>(
"http://localhost:2890",IService1Uri.GetComplexDat,
list => Dispatcher.BeginInvoke(() =>
dataGrid.ItemsSource = list
)
);
To run any of these clients, first launch the ConsoleServer project. As each client accesses the server, you will see the method name accessed printed out on the console.
Points of interest
The nice thing about using this method is that if you want to expose this same service to an Android, iPhone, or a different OS, you could just publish the REST based URIs, and those platforms could implement their own use of the service, though they wouldn't be able to reuse the domain objects or interfaces using the tricks shown in this article.
The RESTUtil
class isn't intended to be a complete, production-ready implementation. POST, method arguments, and authentication aren't supported, but I don't think it would be too difficult to add. If there was another framework that could be compiled and used on all the different client types, I would certainly use that one before I spend too much time trying to upgrade the RESTUtil
class. Please post a comment if you know of a proxy-less method that will work on all three client types shown in the article. I could even see using different HTTP/REST implementations on each framework if each implementation was able to reuse the same domain/interface classes and not require keeping them in synch across the different frameworks.
History
- 7-Feb-2010: Initial version.