Introduction
This article describes a method to utilize WCF self hosted services to be able to serve web 2.0 user interface as well as web services.
By using this method tools or applications can be built using web 2.0 technologies like HTML5, CSS3 with self hosted WCF service as backend business layer. Thus developers can benefit from advanced JavaScript libraries like JQuery or Underscore, etc.. to build user interface. This eliminates need to install .NET Framework on client machines.
Traditionally, self hosted WCF services has user interface built using WinForms and WPF technologies. And to be able to use browser as UI platform ASP.NET and IIS become hard dependencies. For tools and applications which typically are executed out of a single machine or intranet with limited users, IIS is considered as an overkill. Thus browser as a UI platform with HTML5, CSS3 and backed by powerful JavaScript engines is a very promising option for self hosted WCF services.
Background
Initial versions of WCF could only work with SOAP messages. With bindings like WebHttpBinding
from .NET 3.5 onwards WCF could offer direct support to consume web services from Ajax calls from JavaScript. However consuming dataformats like JSON is still not so much so an out of the box experience. Effort to write DataContract
s for every argument and return types is quite high. And with other data formats like XML, client end couldn't directly benefit from JavaScript libraries due to the extra step involved to convert from XML to JSON.
This method considers 'Stream
' as input and out
type for UI interfacing operations. It also supports basic authentication which can be extended for advanced usage. And no customization is done at any layer and just works based on out of box features of WCF in .NET 3.5. This can also be used with .NET 4.0. However JSON.Net is used to format json objects.
Using the Code
Download and build the source. You can use Visual Studio 2010 or VS command line. (Code is built for .NET 4.0, but can be used with .NET 3.5 also.)
- Start WCFWebApp.exe. If port 2011 is not free, change to a different port. And you need admin privileges.
- When server is running, open any browser with JavaScript support and navigate to "http:/<machinename>:<port>/index.htm".
- Enter username # 'user' and password # 'pass' and click on login. You will login and an About page is loaded.
- Click on States link.
- Check on visited option for any state and click on Update.
- Change page to About and come back to States and observe that last selection is persisted.
- Click on 'Logout' on right top corner.
All these operations are run out of a self hosted WCF service. And this demonstrated features like authentication, fetching static files, getting as well as setting and data. Following sections walk through code.
Server Code
Main class just starts a WCF service using WebHttpBinding
and WebServiceHost
.
class Program
{
static void Main(string[] args)
{
string baseAddress = "http://" + Environment.MachineName + ":2011/";
using (WebServiceHost host =
new WebServiceHost(typeof(WebApp), new Uri(baseAddress)))
{
WebHttpBinding binding = new WebHttpBinding();
host.AddServiceEndpoint(typeof
(IWCFWebApp01), binding, "").Behaviors.Add(new WebHttpBehavior());
host.Open();
... other lines left for brevity
}
}
}
Service contract defines a method 'Files
' to serve all static
HTML files, another method 'Links
' serves all linked files like JavaScript, stylesheets and data. Other resources like login. logout, States and State are service operations. Observable point here is 'Stream
' data type for input as well as output.
[ServiceContract]
public interface IWCFWebApp01
{
[OperationContract, WebGet(UriTemplate = "/{resource}.{extension}")]
Stream Files(string resource, string extension);
[OperationContract, WebGet(UriTemplate = "/{path}/{resource}.{extension}")]
Stream Links(string path, string resource, string extension);
[OperationContract, WebInvoke(Method = "POST", UriTemplate = "/login")]
Stream Login(Stream request);
[OperationContract, WebInvoke(Method = "POST", UriTemplate = "/logout")]
Stream Logout(Stream request);
[OperationContract, WebGet(UriTemplate = "/states")]
Stream States();
[OperationContract, WebInvoke(Method = "POST", UriTemplate = "/state")]
Stream State(Stream request);
}
Now coming to service implementation. As this method is primarily intended for self hosted WCF services, singleton instance with concurrent threads is good enough. Consider sessions as applicable. But unlike IIS hosted services, self hosted services would normally serve limited users and thus default concurrency is good enough. And on functional line constructor is just loading data onto local member.
[ServiceBehavior(InstanceContextMode =
InstanceContextMode.Single,ConcurrencyMode=ConcurrencyMode.Multiple)]
public class WebApp : IWCFWebApp01
{
JObject states;
public WebApp()
{
if (states==null)
states = JObject.Parse(File.ReadAllText("web\\data\\states.json"));
}
... other lines left for brevity
}
Now that the server is running, when user tries to access for the first time, several HTM, CSS and JavaScript files are served. These are handled by methods 'Files
' and 'Links
'. Links are files referred in index.htm in head section like JQuery. And in 'Files
' method, different types of files are picked up from separate folders based on extension. Switch
cases can be extended based types of files.
public Stream Links(string path, string resource, string extension)
{
... other lines left for brevity
}
public Stream Files(string resource, string extension)
{
switch (extension)
{
case "htm":
... other lines left for brevity
case "js":
... other lines left for brevity
}
}
When user makes a login request, basic authentication token is sent in standard header "Authorization
". That is validated in a separate method 'Authenticate
' described later. Also username is sent as JSON object in request stream which is parsed into JSON object using JSON.Net library. Logout method is similar to login.
public Stream Login(Stream request)
{
if (!Authenticate()) return null;
... other lines left for brevity
JObject o = JObject.Parse(data);
}
When user clicks on 'States
' request reaches the following method. As this resource doesn't have any extension, request will not go through 'Files
' method. Here request is authenticated and data is sent from member variable.
public Stream States()
{
if (!Authenticate()) return null;
WebOperationContext.Current.OutgoingResponse.ContentType = "application/json";
return new MemoryStream(Encoding.ASCII.GetBytes(states.ToString()),false);
}
When user does a modification and clicks on 'Update
', the following method would be invoked. This parses state id and update class member variable and returns updated list back to client.
public Stream State(Stream request)
{
... other lines left for brevity
JObject data = JObject.Parse(new string(buffer));
int id = ((int)data["id"]) -1;
states["states"][id]["visited"] = true;
return States();
}
Authentication methods which require authorization invoke the following method:
public bool Authenticate()
{
string userName = "user";
string password = "pass";
string basicAuthCode = Convert.ToBase64String
(Encoding.ASCII.GetBytes (string. Format ("{0}: {1}", userName, password)));
string token = WebOperationContext.Current.IncomingRequest.Headers["Authorization"];
if (token.Contains(basicAuthCode))
{
return true;
}
else
{
WebOperationContext.Current.OutgoingResponse.StatusCode =
HttpStatusCode.Unauthorized;
return false;
}
}
Client Code
Client code is placed in separate folder by name 'web'. At the root of this folder, all static
HTM files are placed. And separate sub-folders are included for images, JavaScript and stylesheets. These are referred from 'Files
' method in server code based on extension.
Client follows Single Page Application design. Thus only 'index.htm' is a full HTML page. Other HTML files are filled into 'content
' division using Ajax calls as shown below for states:
function StatesPage () {
this.loadStatesPage = function (data) {
content = undefined;
$.each (data, function (index, value) {
if (data[index]["id"]=="content") {
content = data[index].innerHTML;
$("#content")[0].innerHTML = content;
$("#b_update")[0].onclick = updateStates;
loadStatesTable();
}
});
if (content == undefined) {alert("Failed to load page: Content missing"); return;}
}
... other lines left for brevity
}
Authentication: Client side authentication token is in login class. This token is added in header section in 'beforeSend
' function of each call after login. Other code in client requires understanding about Jquery, JavaScript and Ajax concepts which are well explained on the web.
Points of Interest
If windows authentication is required, service host can be customized.
More structured JavaScript libraries with MVC architecture can also be used without making any change to server side code.
Consider using JQuery UI plugins for common look and feel.
As UI is browser based, extending UI for handheld devices becomes quite easy.
History
- 2011-07-22: First version
- 2011-07-23: Article updated