Working example of implementing an ExcelMediaTypeFormatter for WebApi.
Source: https://github.com/stevenh77/ExcelFormatterForWebApi/
Full blog write up to follow….
Working example of implementing an ExcelMediaTypeFormatter for WebApi.
Source: https://github.com/stevenh77/ExcelFormatterForWebApi/
Full blog write up to follow….
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="binding_Default" maxReceivedMessageSize="2147483647">
<security mode="Transport">
<transport clientCredentialType="Certificate"/>
</security>
</binding>
</basicHttpBinding>
</bindings>
<behaviors>
<endpointBehaviors>
<behavior name="endpointBehavior">
<clientCredentials>
<clientCertificate storeLocation="CurrentUser"
storeName="My"
findValue="28dfc90a0d22763ca41bb937e91925e10f9de7a4"
x509FindType="FindByThumbprint"/>
<serviceCertificate>
<authentication certificateValidationMode="None" revocationMode="NoCheck"/>
</serviceCertificate>
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
<client>
<endpoint address="https://SERVERNAME/SERVICENAME"
binding="basicHttpBinding"
bindingConfiguration="binding_Default"
contract="NAMESPACE.INTERFACE"
name="MyServiceEndpoint"
behaviorConfiguration="endpointBehavior">
</endpoint>
</client>
</system.serviceModel>
</configuration>
T4 template that takes a DLL, pulls out all the WCF interfaces and gives your client development team a proxy class to code against.
namespace ServerInterfaces
{
[ServiceContract]
public interface ILoginFacade
{
[OperationContract]
[WebInvoke(RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json,
Method = "GET", UriTemplate = "/Login")]
GetLoginResponse GetLogin()
[OperationContract]
[WebInvoke(RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json,
Method = "PUT", UriTemplate = "/Login")]
UpdateLoginResponse UpdateLogin(UpdateLoginRequest request);
[OperationContract]
[WebInvoke(RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json,
Method = "GET", UriTemplate = "/Login/WriteToLog")]
void WriteToLog();
}
[ServiceContract]
public interface ITradeFacade
{
[OperationContract]
[WebInvoke(RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json,
Method = "PUT", UriTemplate = "/Trades")]
int UpdateTradesDatabase(string userId);
}
}
/***************************************************************************************
**** GENERATED CODE: Use t4toolbox & tangiblet4editor to update AsyncInterfaces.tt ****
****************************************************************************************/
using System;
using Newtonsoft.Json;
namespace WcfServiceDirectory
{
public partial class Services
{
public Services()
{
this.LoginFacade = new LoginFacade();
this.TradeFacade = new TradeFacade();
}
public LoginFacade LoginFacade { get; set; }
public TradeFacade TradeFacade { get; set; }
}
public class LoginFacade
{
private IServiceExecutor serviceExecutor;
public LoginFacade() { this.serviceExecutor = new ServiceExecutor(); }
public void GetLogin(Action<CallCompleteEventArgs<DTOs.GetLoginResponse>> callback)
{
serviceExecutor.Get<DTOs.GetLoginResponse>("/Login", callback);
}
public void UpdateLogin(DTOs.UpdateLoginRequest request, Action<CallCompleteEventArgs<DTOs.UpdateLoginResponse>> callback)
{
serviceExecutor.Put<DTOs.UpdateLoginResponse>("/Login", JsonConvert.SerializeObject(request), callback);
}
public void WriteToLog(Action<CallCompleteEventArgs<Object>> callback)
{
serviceExecutor.Get<Object>("/Login/WriteToLog", callback);
}
}
public class TradeFacade
{
private IServiceExecutor serviceExecutor;
public TradeFacade() { this.serviceExecutor = new ServiceExecutor(); }
public void UpdateTradesDatabase(System.String userId, Action<CallCompleteEventArgs<System.Int32>> callback)
{
serviceExecutor.Put<System.Int32>("/Trades", JsonConvert.SerializeObject(userId), callback);
}
}
}
namespace ClientProxy
{
public interface IServiceExecutor
{
void Get<TResponse>(string uriTemplate, Action<CallCompleteEventArgs<TResponse>> callback);
void Put<TResponse>(string uriTemplate, string request, Action<CallCompleteEventArgs<TResponse>> callback);
}
public class ServiceExecutor : IServiceExecutor
{
ServiceEnvironment serviceEnvironment = new ServiceEnvironment() { UseHttpS = false, BaseAddress = "localhost", Port = 52802 };
public void Get<TResponse>(string uriTemplate, Action<CallCompleteEventArgs<TResponse>> callback)
{
var client = new WebClient();
var address = GetUri(uriTemplate);
client.DownloadStringCompleted += (sender, eventArgs) =>
{
if (callback == null) return;
var response = JsonConvert.DeserializeObject<TResponse>(eventArgs.Result);
callback(new CallCompleteEventArgs<TResponse>(response, eventArgs));
};
client.DownloadStringAsync(address);
}
public void Put<TResponse>(string uriTemplate, string request, Action<CallCompleteEventArgs<TResponse>> callback)
{
var client = new WebClient();
var address = GetUri(uriTemplate);
client.UploadStringCompleted += (sender, eventArgs) =>
{
if (callback == null) return;
var response = JsonConvert.DeserializeObject<TResponse>(eventArgs.Result);
callback(new CallCompleteEventArgs<TResponse>(response, eventArgs));
};
client.Headers[HttpRequestHeader.ContentType] = "application/json";
client.UploadStringAsync(address, "PUT", request);
}
private Uri GetUri(string uriTemplate)
{
var uriString = string.Format("http{0}://{1}:{2}/Facades{3}",
serviceEnvironment.UseHttpS ? "s" : "",
serviceEnvironment.BaseAddress,
serviceEnvironment.Port,
uriTemplate);
return new Uri(uriString);
}
}
}
The solution features:
Here is a simple example application that shows off:
private async void LoadDataButton_Click(object sender, RoutedEventArgs e)
{
var request = new WebClient();
var uri = new Uri("http://localhost:5349/services/cars");
var jsonString = await request.DownloadStringTaskAsync(uri);
using (var stream = new MemoryStream(Encoding.Unicode.GetBytes(jsonString.ToCharArray())))
{
var serializer = new DataContractJsonSerializer(typeof (Car[]));
var cars = (Car[]) serializer.ReadObject(stream);
CarsListBox.ItemsSource = cars;
}
}
[ServiceContract]
interface IService<T>
{
[OperationContract]
IList<T> GetAll();
[OperationContract]
T Get(string id);
[OperationContract]
void Insert(T entity);
[OperationContract]
void Update(string id, T entity);
[OperationContract]
void Delete(string id);
}
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class CarService : IService<Car>
{
private readonly IRepository<Car> _repo;
public CarService()
{
_repo = new FakeCarRepository();
}
public CarService(IRepository<Car> repo)
{
_repo = repo;
}
[WebGet(UriTemplate = "Cars", ResponseFormat = WebMessageFormat.Json)]
public IList<Car> GetAll()
{
var cars = _repo.GetAll();
return cars;
}
[WebGet(UriTemplate = "Car/{id}", ResponseFormat = WebMessageFormat.Json)]
public Car Get(string id)
{
var car = _repo.GetById(id);
return car;
}
[WebInvoke(UriTemplate = "Car", Method = "POST")]
public void Insert(Car car)
{
_repo.Insert(car);
}
[WebInvoke(UriTemplate = "Car/{id}", Method = "PUT")]
public void Update(string id, Car car)
{
_repo.Update(car);
}
[WebInvoke(UriTemplate = "Car/{id}", Method = "DELETE")]
public void Delete(string id)
{
_repo.Delete(id);
}
}
[DataContract]
public class Car
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string Make { get; set; }
[DataMember]
public string Model { get; set; }
[DataMember]
public int Year { get; set; }
}
public class Global : HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
RouteTable.Routes.Add(new ServiceRoute("services", new WebServiceHostFactory(), typeof(CarService)));
}
}
<?xml version="1.0"?>
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.0" />
</system.web>
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
<standardEndpoints>
<webHttpEndpoint>
<standardEndpoint name="" helpEnabled="true" automaticFormatSelectionEnabled="true"/>
</webHttpEndpoint>
</standardEndpoints>
</system.serviceModel>
</configuration>
http://stevenhollidge.com/blog-source-code/SilverlightAsyncRestWcf.zip
Carrying on from an excellent MSDN blog post by Rick Rainey comparing the performance of various WCF bindings here is an example including NetNamePipes and ProtoBuf-Net:
Original blog post: http://blogs.msdn.com/b/rickrain/archive/2012/05/20/which-wcf-binding-is-best.aspx
Is available via Nuget or its Google project hosting: http://code.google.com/p/protobuf-net/
If you come across the following exception:
HTTP could not register URL http://+:9001/. Your process does not have access rights to this namespace (see http://go.microsoft.com/fwlink/?LinkId=70353 for details).
Either run Visual Studio using “Run as Administrator” or visit the following link for a more complete resolution:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Diagnostics;
using ProtoBuf;
using ProtoBuf.ServiceModel;
namespace BindingTest
{
[ServiceContract]
public interface ITestService
{
[OperationContract]
void Execute(TestData data);
}
[DataContract]
[ProtoContract]
public class TestData
{
[DataMember]
[ProtoMember(1)]
public List<string> Data { get; set; }
}
public class TestService : ITestService
{
public void Execute(TestData data)
{
// empty service implementation
}
}
class Program
{
static void Main(string[] args)
{
var bindingElements = new BindingElement[] {
new BinaryMessageEncodingBindingElement(),
new ReliableSessionBindingElement(true),
new HttpTransportBindingElement()
};
var bindings = new Binding[]
{
new WSHttpBinding() { MaxReceivedMessageSize = Int32.MaxValue },
new NetTcpBinding
{
Name = "NetTcpBinding (SecurityMode.Message)",
Security = { Mode = SecurityMode.Message },
MaxReceivedMessageSize = Int32.MaxValue
},
new CustomBinding(bindingElements)
{
Name = "CustomBinding (HTTP/Binary/ReliableSession(ordered)/NoSecurity"
},
new NetTcpBinding(),
new NetNamedPipeBinding(NetNamedPipeSecurityMode.Transport),
new NetNamedPipeBinding(NetNamedPipeSecurityMode.None)
};
var payloadSize = 15000;
var iterations = 5;
OutputTitle(payloadSize, iterations);
// Compare all the bindings in the bindings array.
CompareBindings(bindings, payloadSize, iterations, false);
// Compare all the bindings using protobuf-net
CompareBindings(bindings, payloadSize, iterations, true);
OutputNote();
}
public static void OutputTitle(int payloadSize, int iterations)
{
Console.WriteLine("Test parameters: {0} byte payload, {1} iterations",
payloadSize.ToString("N0"),
iterations);
Console.WriteLine();
}
private static void OutputNote()
{
Console.WriteLine();
Console.WriteLine("NOTE: NetNamedPipe is only used for same machine inter process communication");
Console.WriteLine();
}
public static string GenerateRandomStringData(int stringSize)
{
var r = new Random();
var sb = new StringBuilder(stringSize);
while (sb.Length < stringSize)
sb.Append((char)(r.Next(0, 256)));
return sb.ToString();
}
public static TestData GenerateTestData(int payloadSize)
{
const int itemDataSize = 4096;
var data = new TestData {Data = new List<string>()};
int numDataItems = payloadSize / itemDataSize;
for (int item = 0; item < numDataItems; item++)
data.Data.Add(GenerateRandomStringData(itemDataSize));
data.Data.Add(GenerateRandomStringData(payloadSize - (numDataItems * itemDataSize)));
return data;
}
public static void CompareBindings(Binding[] bindings, int payloadSize, int iterations, bool useProtoBuf)
{
Console.WriteLine("Using {0} serialisation", useProtoBuf ? "protobuf" : "normal");
var data = GenerateTestData(payloadSize);
foreach (var b in bindings)
{
try
{
// Run the test on this binding.
Stopwatch result = TestBinding(b, data, iterations, useProtoBuf);
// Output the results.
Console.Write(b.Name);
//Console.WriteLine(string.Format("Payload Size: {0}, Iterations: {1}", payloadSize, iterations));
//Console.WriteLine(" Binding Elements:");
//foreach (BindingElement be in b.CreateBindingElements())
// Console.WriteLine(" {0}", be.GetType());
Console.WriteLine(", time: {0} ms", result.ElapsedMilliseconds);
}
catch (Exception e)
{
Console.WriteLine("EXCEPTION: {0}", e.Message);
}
}
Console.WriteLine();
}
public static Stopwatch TestBinding(Binding binding, TestData data, int iterations, bool useProtoBuf)
{
var address = GetAddress(binding);
// Start the host using the binding provided
var host = new ServiceHost(typeof(TestService), new Uri(address));
var endpoint = host.AddServiceEndpoint(typeof(ITestService), binding, "");
if (useProtoBuf)
endpoint.Behaviors.Add(new ProtoEndpointBehavior());
host.Open();
var sw = new Stopwatch();
sw.Restart();
// Create a client proxy using the binding provided
var cf = new ChannelFactory<ITestService>(binding, address);
ITestService proxy = cf.CreateChannel();
// Call the service
for (int count = 0; count < iterations; count++)
proxy.Execute(data);
// Close the client proxy and host.
cf.Close();
sw.Stop();
host.Close();
return sw;
}
private static string GetAddress(Binding binding)
{
var machine = "localhost";
var port = (binding.Scheme == "net.pipe")
? ""
: ":9001";
return string.Concat(binding.Scheme, "://", machine, port, "/", Guid.NewGuid().ToString());
}
}
}
Download: http://stevenhollidge.com/blog-source-code/BindingTestSolution.zip
Here is an example of using WcfWebApi to produce a RESTful JSON data service. We have three areas to consider:
Data Operation | HTTP Verb | Has Side Effects? | Idempotent? | Example Uri |
Read All | GET | No | Yes | http://localhost:8000/contacts |
Read Single | GET | No | Yes | http://localhost:8000/contacts/1 |
Insert | POST | Yes | No | http://localhost:8000/contacts |
Update | PUT | Yes | Yes | http://localhost:8000/contacts |
Delete | DELETE | Yes | Yes | http://localhost:8000/contacts/3 |
Http Code | Description | Examples |
200 | OK | Successful (read, update or delete) |
201 | CREATED | Successful (insert) |
400 | BAD REQUEST | Passing id as “X” when an integer is expected |
404 | NOT FOUND | Item not found (based on id) |
500 | SERVER ERROR | Exception thrown within service on server with the message contained within response body. |
[ServiceContract]
public interface IContactService
{
[OperationContract]
[WebGet(UriTemplate = "")]
HttpResponseMessage GetAll();
[OperationContract]
[WebGet(UriTemplate = "{id}")]
HttpResponseMessage Get(string id);
[OperationContract]
[WebInvoke(UriTemplate = "", Method = "POST")]
HttpResponseMessage Insert(Contact contact);
[OperationContract]
[WebInvoke(UriTemplate = "", Method = "PUT")]
HttpResponseMessage Update(Contact contact);
[OperationContract]
[WebInvoke(UriTemplate = "{id}", Method = "DELETE")]
HttpResponseMessage Delete(string id);
}
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Text;
using Microsoft.ApplicationServer.Http.Dispatcher;
using RestDemo.Data;
using RestDemo.Service.Helpers;
using RestDemo.Service.Repositories;
namespace RestDemo.Service
{
public class ContactService : IContactService
{
private const string ID_MUST_BE_INTEGER = "Id must be an integer";
private const string ITEM_NOT_FOUND = "Item not found";
private readonly IContactRepository _repo;
public ContactService() : this(new ContactRepository())
{
}
public ContactService(IContactRepository repository)
{
_repo = repository;
}
#region IContactService Members
public HttpResponseMessage GetAll()
{
IEnumerable<Contact> contacts;
DataOperationResponse dataResponse = _repo.GetAll(out contacts);
HttpContent httpContent = GetHttpContent(contacts);
HttpResponseMessage httpResponse = SetupHttpResponse(dataResponse, httpContent);
return httpResponse;
}
public HttpResponseMessage Get(string id)
{
int intId;
if (!Int32.TryParse(id, out intId))
return new HttpResponseMessage(HttpStatusCode.BadRequest) { Content = new StringContent(ID_MUST_BE_INTEGER) };
Contact contact;
DataOperationResponse dataResponse = _repo.Get(intId, out contact);
HttpContent httpContent = GetHttpContent(contact);
HttpResponseMessage httpResponse = SetupHttpResponse(dataResponse, httpContent);
return httpResponse;
}
public HttpResponseMessage Insert(Contact contact)
{
DataOperationResponse dataResponse = _repo.Insert(contact);
HttpResponseMessage httpResponse = SetupHttpResponse(dataResponse);
return httpResponse;
}
public HttpResponseMessage Update(Contact contact)
{
DataOperationResponse dataResponse = _repo.Update(contact);
HttpResponseMessage httpResponse = SetupHttpResponse(dataResponse);
return httpResponse;
}
public HttpResponseMessage Delete(string id)
{
int intId;
if (!Int32.TryParse(id, out intId))
return new HttpResponseMessage(HttpStatusCode.BadRequest) { Content = new StringContent(ID_MUST_BE_INTEGER) };
DataOperationResponse dataResponse = _repo.Delete(intId);
HttpResponseMessage httpResponse = SetupHttpResponse(dataResponse);
return httpResponse;
}
#endregion
private static HttpContent GetHttpContent(int id)
{
HttpContent httpContent = new StringContent(Convertors.ToJson(id).ToString(), Encoding.UTF8, MediaTypes.JSON);
return httpContent;
}
private static HttpContent GetHttpContent(Contact contact)
{
HttpContent httpContent = contact
!= null
? new StringContent(Convertors.ToJson(contact).ToString(), Encoding.UTF8,
MediaTypes.JSON)
: null;
return httpContent;
}
private static HttpContent GetHttpContent(IEnumerable<Contact> contact)
{
HttpContent httpContent = contact
!= null
? new StringContent(Convertors.ToJson(contact).ToString(), Encoding.UTF8,
MediaTypes.JSON)
: null;
return httpContent;
}
private static HttpResponseMessage SetupHttpResponse(DataOperationResponse dataResponse, HttpContent content = null)
{
var response = new HttpResponseMessage();
switch (dataResponse.Status)
{
case DataOperationStatus.Success:
if (dataResponse.Type == OperationType.Insert)
{
response.StatusCode = HttpStatusCode.Created;
response.Content = GetHttpContent(dataResponse.InsertedId);
response.Content.Headers.Expires = new DateTimeOffset(DateTime.Now.AddSeconds(30));
}
else
{
response.StatusCode = HttpStatusCode.OK;
if (content != null)
response.Content = content;
}
break;
case DataOperationStatus.NotFound:
// REST best practise on delete is to return OK if resource
// cannot be found but here we are explicitly returning Not Found
response.StatusCode = HttpStatusCode.NotFound;
response.Content = new StringContent(ITEM_NOT_FOUND);
break;
case DataOperationStatus.Exception:
response.StatusCode = HttpStatusCode.InternalServerError;
response.Content = new StringContent(dataResponse.Exception.Message);
break;
}
return response;
}
/* You might also want set the ocation header:
* response.Headers.Location = new Uri("Contacts/" + item.Id, UriKind.Relative);
*/
}
}
public class Global : HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
var webApiConfig = new WebApiConfiguration {EnableTestClient = true};
RouteTable.Routes.SetDefaultHttpConfiguration(webApiConfig);
RouteTable.Routes.MapServiceRoute<ContactService>("Contacts");
}
}
The source code also contains an example of hosting within a client application:
private const string CONTACTS_URI = "http://localhost:8010/contacts";
private static void Main(string[] args)
{
using (var host = new HttpServiceHost(typeof (ContactService), CONTACTS_URI))
{
host.Open();
ExitOnReadKey();
}
private static void ExitOnReadKey()
{
Console.WriteLine("Press any key to exit...");
Console.ReadLine();
}
}
private static void HttpGetAll()
{
var client = new HttpClient();
HttpResponseMessage response = client.Get(CONTACTS_URI);
Console.WriteLine("GET: {0} RESPONSE: {1} \n{2}\n", CONTACTS_URI, response.StatusCode, response.Content.ReadAsString());
}
private static void HttpPost()
{
var client = new HttpClient();
HttpContent jsonContactNew = new StringContent("{\"Id\":0,\"Name\":\"Edward\"}", Encoding.UTF8, MediaTypes.JSON);
HttpResponseMessage response = client.Post(CONTACTS_URI, jsonContactNew);
Console.WriteLine("POST (INSERT CONTACT): {0} RESPONSE: {1} \n{2}\n", CONTACTS_URI, response.StatusCode, response.Content.ReadAsString());
}
private static void HttpPut()
{
var client = new HttpClient();
HttpContent jsonContactUpdated = new StringContent("{\"Id\":2,\"Name\":\"Ziggy\"}", Encoding.UTF8, MediaTypes.JSON);
HttpResponseMessage response = client.Put(CONTACTS_URI, jsonContactUpdated);
Console.WriteLine("PUT (UPDATE CONTACT ID 2): {0} RESPONSE: {1}\n", CONTACTS_URI, response.StatusCode);
}
private static void HttpDelete()
{
var client = new HttpClient();
const string uri = CONTACTS_URI + "/3";
HttpResponseMessage response = client.Delete(uri);
Console.WriteLine("DELETE (CONTACT ID 3): {0} RESPONSE: {1} \n", uri, response.StatusCode);
}
private static void HttpGet()
{
var client = new HttpClient();
const string uri = CONTACTS_URI + "/1";
HttpResponseMessage response = client.Get(uri);
Console.WriteLine("GET: {0} RESPONSE: {1} \n{2}\n", uri, response.StatusCode, response.Content.ReadAsString());
}
// Some methods worth knowing:
// 1. When expecting JSON in response body you can set the following header in the request:
// client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypes.JSON));
//
// 2. When deserializing JSON single item within response body use the following:
// var contact = response.Content.ReadAs<Contact>();
//
// 3. When deserializing JSON collection within response body use the following:
// var contacts = response.Content.ReadAs<Contact[]>();
You can use Fiddler to monitor all HTTP requests and responses. The screenshot below shows Fiddler on the left hand side picking up all requests and responses from the WCF Web API Test Client (explained within Debugging below) on the right hand side.
Make sure you punch a hole in your firewall before testing (or change the port from 8010 to 80 in the source code).
On Windows 7 > Control Panel > Windows Firewall > Allow a Program or Feature through the Firewall
We are now ready to open Fiddler and use the Request Builder tab to enter your URI, set the HTTP verb (defaults to GET) and execute your request. On the left hand side you’ll see the HTTP response and headers, in this case the result was 200 (OK):
Then switch to the Inspectors tab to view the response body containing our data.
For PUT and POST requests you must add the following to the request headers:
Content-Type: application/json; charset=utf-8
We switched this new Microsoft Tool on in the Global.asax page with “EnableTestClient = true” which automatically routes:
In our case this would be http://localhost:8010/Contacts/Test:
Full source code: http://stevenhollidge.com/blog-source-code/RestDemo.zip
Here’s a simple example of how to use RabbitMQ to provide a simple pub/sub messaging architecture (aka observer/listener pattern).
Once the RabbitMQ server is installed we are going to create a C# client to:
The code also shows how to inspect for the last message retrieved from a subscription.
For the server you’ll need to first install Erlang and then run the RabbitMQ installer:
http://www.erlang.org/download.html (Windows Binary File)
http://www.rabbitmq.com/server.html (Installer for Windows systems)
For the C# client libraries:
http://www.rabbitmq.com/dotnet.html
Queues allow messages containing work items or events to be stored in a durable manner. This enables disconnected middleware processes to be able to read from the queue, allowing for scalable processing via multiple processes reading from the queue. By contrast, sockets and other network protocols assume that direct connections always exist.
Different message types can be used on the same queue to provide a workflow scenario. Different processes can monitor the same queue for specific message types and post back to the same queue with a message type for the next stage in the workflow, which in turn gets picked up by another process and so it goes on.
For example, we might have a Web Server dealing with HTTP traffic and 2 worker processes all reading from and writing to the same queue.
In reality, “Worker X” might be a bank of multiple computers and processes to allow for a high throughput of messages to be actioned.
A nice webcast can be found here by Clemens Vasters:
http://blogs.msdn.com/b/clemensv/archive/2011/03/18/why-would-anyone-ever-use-a-message-queue.aspx
You can download the source code for this example:
As a general rule it’s always best to stay away from the the Visual Studio Add Reference service proxy/client generator.
The preferred route is to use the utility tools provided, here’s an example of using the SlSvcUtil.exe tool:
cd "C:\Program Files (x86)\Microsoft SDKs\Silverlight\v5.0\Tools"
SlSvcUtil http://localhost:1558/Services/StocksService.svc /edb /ct:System.Collections.ObjectModel.ObservableCollection`1 /r:"C:\Program Files (x86)\Microsoft Silverlight\5.0.61118.0\System.Windows.dll"
Some points of interest:
For more info you can hit the MSDN docs:
http://msdn.microsoft.com/en-us/library/cc197958(v=vs.95).aspx
Here's a few examples of a basic data item being serialized using different techniques:
Xml, Mtom, Binary, BinarySession & Json.
Source code: http://stevenhollidge.com/blog-source-code/SerializationExamples.zip
This custom framework contains the following functionality:
Client
Server
Silverlight Application Framework demo from Steven Hollidge on Vimeo.
The Ribbon button has a tag property that has the desired Uri set as its content.
When the button is clicked, a message is sent that contains the desired Uri.
The messenger listener receives the message and calls the ContentFrame.NavigateTo method.
Click here to download the source code:
http://stevenhollidge.com/blog-source-code/SilverlightExampleApp.zip
If you prefer the style of a Tree View menu, which is free compared to the Ribbon which is licensed, you can check out my blog posting here.
http://stevenhollidge.blogspot.com/2011/06/treeview-menu-for-silverlight.html
I’ve written a quick Silverlight application that listens to a WCF Service streaming a fire hose of the latest Fx prices at a rate of 1,200 per minute.
The application showcases the MVVM pattern, makes use of MVVMLight Toolkit and features the cosmopolitan/metro theme.
The WCF service exposes a pub/sub model with a call back for each new FX Rate price:
using System;
using System.ServiceModel;
using System.Threading;
using System.Threading.Tasks;
namespace PricingServiceHost
{
[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
public class PricingService : IPricingService
{
static PricingService()
{
Task.Factory.StartNew(() =>
{
var factory = new PriceFactory();
while (true)
{
Thread.Sleep(50);
if (PriceUpdate == null) continue;
FxRate latestPrice = factory.GetNextPrice();
PriceUpdate(
null,
new PriceUpdateEventArgs
{
LatestPrice = latestPrice
});
}
});
}
static event EventHandler<PriceUpdateEventArgs> PriceUpdate;
IPricingServiceCallback _callback;
public void Subscribe()
{
_callback = OperationContext.Current.GetCallbackChannel<IPricingServiceCallback>();
PriceUpdate += PricingService_PriceUpdate;
}
public void UnSubscribe()
{
PriceUpdate -= PricingService_PriceUpdate;
}
void PricingService_PriceUpdate(object sender, PriceUpdateEventArgs e)
{
if (((ICommunicationObject)_callback).State == CommunicationState.Opened)
{
try
{
_callback.PriceUpdate(e.LatestPrice);
}
catch
{
UnSubscribe();
}
}
else
{
UnSubscribe();
}
}
}
}
using System;
using System.Collections.ObjectModel;
using GalaSoft.MvvmLight;
namespace MetroPricingSample.Models
{
public class DisplayFxRate : ObservableObject
{
public static ObservableCollection<DisplayFxRate> InitialRates = new ObservableCollection<DisplayFxRate>
{
new DisplayFxRate("AUD", (decimal) 0.93272),
new DisplayFxRate("BRL", (decimal) 1.58100),
new DisplayFxRate("CAD", (decimal) 0.97495),
new DisplayFxRate("CHF", (decimal) 0.83603),
new DisplayFxRate("CNY", (decimal) 0.15425),
new DisplayFxRate("EUR", (decimal) 0.68103),
new DisplayFxRate("GBP", (decimal) 0.60819),
new DisplayFxRate("INR", (decimal) 44.6300),
new DisplayFxRate("JPY", (decimal) 80.0032),
new DisplayFxRate("NZD", (decimal) 1.21847),
new DisplayFxRate("RUB", (decimal) 27.7411),
new DisplayFxRate("THB", (decimal) 0.03303),
new DisplayFxRate("ZAR", (decimal) 6.71610)
};
public DisplayFxRate() { }
public DisplayFxRate(string isoCode, decimal rate)
{
IsoCode = isoCode;
PreviousRate = rate;
CurrentRate = rate;
Updated = DateTime.Now;
}
public const string IsoCodePropertyName = "IsoCode";
private string _isoCode = string.Empty;
public string IsoCode
{
get { return _isoCode; }
set
{
if (_isoCode == value) return;
_isoCode = value;
RaisePropertyChanged(IsoCodePropertyName);
}
}
public const string PreviousRatePropertyName = "PreviousRate";
private decimal _previousRate = 0;
public decimal PreviousRate
{
get { return _previousRate; }
set
{
if (_previousRate == value) return;
_previousRate = value;
RaisePropertyChanged(PreviousRatePropertyName);
}
}
public const string CurrentRatePropertyName = "CurrentRate";
private decimal _currentRate = 0;
public decimal CurrentRate
{
get { return _currentRate; }
set
{
if (_currentRate == value) return;
_previousRate = _currentRate;
_currentRate = value;
RaisePropertyChanged(PreviousRatePropertyName);
RaisePropertyChanged(CurrentRatePropertyName);
RaisePropertyChanged(DeltaPropertyName);
RaisePropertyChanged(StatusPropertyName);
}
}
public const string DeltaPropertyName = "Delta";
public decimal Delta
{
get
{
decimal result;
if (PreviousRate == 0 || CurrentRate == 0)
result = 0;
else
result = Math.Round(((CurrentRate / PreviousRate) - 1), 2);
return result;
}
}
public const string StatusPropertyName = "Status";
public Status Status
{
get
{
Status status;
var delta = Delta;
if (delta > 0)
status = Status.Increase;
else if (delta < 0)
status = Status.Decrease;
else
status = Status.NoChange;
return status;
}
}
public const string UpdatedPropertyName = "Updated";
private DateTime _updated = DateTime.MinValue;
public DateTime Updated
{
get { return _updated; }
set
{
if (_updated == value) return;
_updated = value;
RaisePropertyChanged(UpdatedPropertyName);
}
}
}
}
<navigation:Page x:Class="MetroPricingSample.Views.Pricing"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
xmlns:Converters="clr-namespace:MetroPricingSample.Converters"
d:DesignHeight="300"
d:DesignWidth="640"
DataContext="{Binding PricingViewModel, Source={StaticResource Locator}}"
Style="{StaticResource PageStyle}"
mc:Ignorable="d">
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="60" />
<RowDefinition Height="30" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.Resources>
<Converters:BoolToSubscribedTextConverter x:Key="BoolToSubscribedTextConverter" />
<Converters:StatusToIconConverter x:Key="StatusToIconConverter" />
<Converters:IsoCodeToFlagConverter x:Key="IsoCodeToFlagConverter" />
<Converters:DateTimeToTimeConverter x:Key="DateTimeToTimeConverter" />
</Grid.Resources>
<StackPanel Grid.Row="0">
<Button x:Name="btnSubscribe"
Width="200"
Height="30"
HorizontalAlignment="Left"
Content="{Binding Subscribed, Converter={StaticResource BoolToSubscribedTextConverter}}"
Command="{Binding SubscriptionCommand, Mode=TwoWay}" />
<TextBlock x:Name="tbInfo" Height="30" Text="{Binding ErrorText}"/>
</StackPanel>
<StackPanel Grid.Row="1" Orientation="Horizontal">
<TextBlock Text="Currency" Width="85" Margin="10,0,0,0"/>
<TextBlock Text="Previous" Width="100"/>
<TextBlock Text="Current" Width="100" />
<TextBlock Text="Delta" Width="160" />
<TextBlock Text="Updated" />
</StackPanel>
<ListBox x:Name="lbFxRates"
ItemsSource="{Binding Path=Rates}"
Grid.Row="2">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding IsoCode, Converter={StaticResource IsoCodeToFlagConverter}}" Width="20" Margin="10,0,0,0"/>
<TextBlock Text="{Binding IsoCode}" Width="50" Margin="10,0,0,0"/>
<TextBlock Text="{Binding PreviousRate}" Width="100" />
<TextBlock Text="{Binding CurrentRate}" Width="100" />
<TextBlock Text="{Binding Delta}" Width="100" HorizontalAlignment="Right" Margin="0,0,10,0" />
<Image Source="{Binding Status, Converter={StaticResource StatusToIconConverter}}" Width="20" />
<TextBlock Text="{Binding Updated, Converter={StaticResource DateTimeToTimeConverter}}" Width="200" Margin="30,0,0,0" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</navigation:Page>
The view model in the client currently uses a generated proxy for the WCF service, we I really don’t like, this will be removed in the next refactoring iteration.
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows.Input;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using MetroPricingSample.Models;
using MetroPricingSample.ServiceReference1;
namespace MetroPricingSample.ViewModels
{
public class PricingViewModel : ViewModelBase
{
public ObservableCollection<DisplayFxRate> Rates { get; set; }
private const string SubscribedPropertyName = "Subscribed";
private bool _subscribed = false;
public bool Subscribed
{
get { return _subscribed; }
set
{
if (_subscribed == value)
{
return;
}
_subscribed = value;
RaisePropertyChanged(SubscribedPropertyName);
}
}
private const string ErrorTextPropertyName = "ErrorText";
private string _errorText = string.Empty;
public string ErrorText
{
get { return _errorText; }
set
{
if (_errorText == value)
{
return;
}
_errorText = value;
RaisePropertyChanged(ErrorTextPropertyName);
}
}
public ICommand SubscriptionCommand { get; set; }
private bool _subscriptionCommand_CanExecute = true;
private PricingServiceClient _client;
public PricingViewModel()
{
Rates = DisplayFxRate.InitialRates;
if (IsInDesignMode) return;
_client = new PricingServiceClient();
_client.SubscribeCompleted += _client_SubscribeCompleted;
_client.UnSubscribeCompleted += _client_UnSubscribeCompleted;
_client.PriceUpdateReceived += PriceUpdate;
SubscriptionCommand = new RelayCommand(SubscriptionCommand_Execute, () => _subscriptionCommand_CanExecute);
}
void SubscriptionCommand_Execute()
{
if (!Subscribed)
{
_client.SubscribeAsync();
}
else
{
_client.UnSubscribeAsync();
}
_subscriptionCommand_CanExecute = false;
}
void _client_UnSubscribeCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
if (e.Error == null)
{
Subscribed = false;
ErrorText = "";
}
else
{
ErrorText = "Unable to connect to service.";
}
_subscriptionCommand_CanExecute = true;
}
void _client_SubscribeCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
if (e.Error == null)
{
Subscribed = true;
ErrorText = "";
}
else
{
ErrorText = "Unable to connect to service.";
}
_subscriptionCommand_CanExecute = true;
}
public void PriceUpdate(object sender, PriceUpdateReceivedEventArgs e)
{
if (Subscribed)
{
PriceUpdate(e.fxRate);
}
}
public void PriceUpdate(FxRate fxRate)
{
try
{
foreach (var rate in Rates.Where(rate => rate.IsoCode == fxRate.IsoCode))
{
rate.CurrentRate = fxRate.Rate;
rate.Updated = fxRate.Updated;
}
}
catch (Exception e)
{
//log here
}
}
}
}
The application currently uses MVVM Light on the client side and WCF for the server side service. The GUI is struggling to keep up with the service so I plan to introduce the Telerik Grid Control to see how it deals with the updates. The functionality currently provided by value converters will be moved out of the view and into the view model and I’ll also refactor the application to use a REST interface with Service Stack (written by Demis Bellot).
You can download the source code here:
https://github.com/stevenh77/MetroPricingSample
When you run the application, be sure to set the Web host and Service to both start up at runtime (right click on solution > Properties > Multiple Startup Projects > START both MetroPricingSample.Web and PricingServiceHost).
Note: As I’m looking purely at performance I’ve deliberately omitted tests and some of the error handling and graceful dereferencing, I’ll add these as I refactor later on.
If you want to view the messages and parameters being sent to and from your Wcf Services you can follow these simple steps.
To demonstrate we will be creating a new “WCF Service Library” project:
Right click on your App.Config file and select “Edit Wcf Configuration”.
Select the Diagnostics node (on the left hand side of the form), and then click “Enable Message Logging” (on the right hand side of the form).
Next click the setting for Log Level (set to “Malformed, Transport” by default and select “Service Messages” as shown below:
Finally, expand the Diagnostics node and select the Message Logging node and change the LogEntireMessage setting to True.
Close the Microsoft Service Configuration Editor, saving your changes.
By default the Editor sets the log file to an absolute file path. You can change this in the App.Config to a relative path:
From:
To:
Press F5 to run your WCF Service Library, double click on the GetData() method within the IService1 contract. Enter a request value for the operation on the right hand side, we’ll be using 1234 for this example, and click “Invoke”.
Now close your WCF Test Client window.
To view your Service Log file, open Windows Explorer and navigate to your Project output folder. For this example that directory is C:\Projects\WcfTracingDemo\WcfTracingDemo\Bin\Debug.
You will now be able to see your new log file: app_messages.svclog
Double click on the file to open it within the Microsoft Service Trace Viewer application and select the activity line on the left hand side.
This will populate the Message Log Trace lines on the right hand side, to see your request scroll down to the second to last entry and select it. To view the request scroll down on the “Formatted” tab to view the formatted request:
To view the response select the last entry in the Message Log Trace lines and again scroll down within the “Formatted” section to reveal your response.
I hope you find this brief tutorial on how to Trace messages in WCF comes in useful – happy coding!
Source code: WcfTracingDemo.zip
CodeProjectWCF makes it very easy to expose JSON data over a RESTful interface, as long as you are aware of a couple of “gotchas” in advance.
This article will explain those to you, so you can focus on your business logic rather than configuration of your WCF services.
We start this example by creating a WCF Service Library project:
Next we need to add a reference to the System.ServiceModel.Web framework. Right click on your project file and select Add Reference…
As this framework is not part of the .Net Framework 4 Client Profile, Visual Studio kindly informs us that it will update our target Framework to the full version of .Net Framework 4. Click Yes to accept this change:
We are now ready to update our code.
Copy and paste the following code into the App.Config file:
<?xml version="1.0"?>
<configuration>
<system.serviceModel>
<services>
<service name="WcfJsonRestService.Service1">
<endpoint address="http://localhost:8732/service1"
binding="webHttpBinding"
contract="WcfJsonRestService.IService1"/>
</service>
</services>
<behaviors>
<endpointBehaviors>
<behavior>
<webHttp />
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
</startup>
</configuration>
Notice the binding is set to webHttpBinding as opposed to the normal project template default of wsHttpBinding.
The other important change is the addition of an endpointBehavior for WebHttp.
These two changes are required to enable JSON over REST for WCF.
Copy and paste the following code into the IService1 file:
using System.ServiceModel;
namespace WcfJsonRestService
{
[ServiceContract]
public interface IService1
{
[OperationContract]
Person GetData(string id);
}
}
Notice we are accepting an “In” parameter for id of datatype string. For this example we are returning a custom type of Person.
Copy and paste the following code into the Service1.cs file:
using System;
using System.ServiceModel.Web;
namespace WcfJsonRestService
{
public class Service1 : IService1
{
[WebInvoke(Method = "GET",
ResponseFormat = WebMessageFormat.Json,
UriTemplate = "data/{id}")]
public Person GetData(string id)
{
// lookup person with the requested id
return new Person()
{
Id = Convert.ToInt32(id),
Name = "Leo Messi"
};
}
}
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
}
}
The key elements here are the attributes applied to the method. We are enabling the method to be called over HTTP GET, returning the data in Json format and setting the Uri template to ensure we are using a RESTful interface.
To test your brand new service we will pass in the id value of 10 simply by opening your favourite browser and pasting in the following URL:
http://localhost:8732/Service1/data/10
Source code: WcfJsonRestService.zip
using System;
using System.ServiceModel;
using System.ServiceModel.Web;
namespace WcfJsonRestService
{
[ServiceContract]
public interface IService1
{
[OperationContract]
Person GetData(string id);
[OperationContract]
Person GetDataWithTwoParams(string id, string name);
}
public class Service1 : IService1
{
[WebInvoke(Method = "GET",
ResponseFormat = WebMessageFormat.Json,
UriTemplate = "data/{id}")]
public Person GetData(string id)
{
// lookup person with the requested id
return new Person()
{
Id = Convert.ToInt32(id),
Name = "Leo Messi"
};
}
[WebInvoke(Method = "GET",
ResponseFormat = WebMessageFormat.Json,
UriTemplate = "data/{id}/{name}")]
public Person GetDataWithTwoParams(string id, string name)
{
// create person with the requested id and name
return new Person()
{
Id = Convert.ToInt32(id),
Name = name
};
}
}
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
}
}
When dealing with your WCF services have you ever gotten so confused with all the settings in your App.Config that you just wanted to throw the file away? Well now you can, and I’ll show you how.
With .Net 4.0 WCF allows developers to take the convention over configuration approach to their services. This means that by omitting configuration settings, the .Net framework will create defaults in their place and make your services fully available and operational.
So lets check out how it’s done!
First, create a new WCF Service Library project and call it WcfZeroConfig:
Now, and this is good bit, delete the App.Config file. Just right click on the file, select Delete – feels good right?!
The WCF Service Library project template makes two methods available for us by default and for this artictle we’ll be using the GetData(int value) method for our example.
As our WCF library is now complete, we are going to add another project to our solution to host the service. In production you would usually use IIS or a Windows Service to host your WCF Service Library but for this simple example we are going to use a Console application.
From the Visual Studio main menu select File > Add > New Project > Windows > Console Application and name the project WcfHost:
As this project will be acting as the host we’ll need to add references to both the System.ServiceModel assembly and our WCF Service Library project. Right click on the WcfHost Project file, select Add Reference and select System.ServiceModel framework:
Now select Projects (in the top left hand corner) and double click the WcfZeroConfig and press the close button.
To complete the host, copy and paste the following code into the Program.cs file:
As you can see it takes one line of code to create the ServiceHost object, passing in an Endpoint address and a single method call to open the host channel to make the service available.
On startup of the host, under the covers WCF (in .Net 4.0) will detect that no Binding or Contract has been supplied, either in our App.Config file or from our code, and will provide default values. From the Endpoint address the framework can see we are using Http and will provide the default binding of basicHttpBinding. The contract type of IService1 will also be inferred from the Service1 concrete class.
To run the host, select the WcfHost project and press F5 to build and run the application.
We are now ready to create the client application to consume our WCF Service. From the Visual Studio main menu select File > Add > New Project > Windows > Console Application and name the project WcfClient:
As before you’ll need to add the System.ServiceModel framework (right click on the project file > Add Reference…) along with the WcfZeroConfig project, this is so that the WcfClient project is aware of the WCF Service contract type “IService1”.
To complete our client, just paste in the following lines of code into the Program.cs file:
To complete our example:
1) You’ll first need to build the solution by right clicking on the solution file and selecting Build Solution
2) Run the C:\Projects\WcfZeroConfig\WcfHost\bin\Debug\WcfHost.exe file
3) And finally, run the C:\Projects\WcfZeroConfig\WcfClient\bin\Debug\WcfClient.exe file and enter a number
That number then gets passed to our WCF Service and our client receives the result.
The full source code is available here: WcfZeroConfig.zip