Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#4.0

REST : A Simple REST framework

4.93/5 (74 votes)
24 Dec 2014CPOL16 min read 171.7K  
A simple REST framework written from scratch using just the .NET base class library

Introduction

Where I work I am lucky enough to work with a right mixture of technology, this includes web work, desktop work, messaging, threading etc. etc. I have however always been interested in how people have been able to create entire REST stacks, and wondered what it would take to do that yourself using just your wits and the .NET BCL.

So I thought about this a bit more and came up with the following requirements/questions:

  1. How do I listen for Http requests
  2. How do I allow user code to be as simple as possible
  3. How to I allow user code to use other dependencies via IOC / dependency injection
  4. Can I support Json/Xml and other possible serialization preferences

So with that set of bullet points in mind, I set about trying to create a REST framework from scratch.

For those of you that DO NOT know what REST means/stands for, here is what Wikipedia says about it:

Representational state transfer (REST) is an abstraction of the architecture of the World Wide Web; more precisely, REST is an architectural style consisting of a coordinated set of architectural constraints applied to components, connectors, and data elements, within a distributed hypermedia system. REST ignores the details of component implementation and protocol syntax in order to focus on the roles of components, the constraints upon their interaction with other components, and their interpretation of significant data elements.

The term representational state transfer was introduced and defined in 2000 by Roy Fielding in his doctoral dissertation at UC Irvine.REST has been applied to describe desired web architecture, to identify existing problems, to compare alternative solutions and to ensure that protocol extensions would not violate the core constraints that make the web successful. Fielding developed REST in collaboration with his colleagues during the same period he worked on HTTP 1.1 and Uniform Resource Identifiers (URI).

The REST architectural style is also applied to the development of web services as an alternative to other distributed-computing specifications such as SOAP. One can characterize web services as "RESTful" if they conform to the constraints described in the architectural constraints section. See the applied to web services section if you are only interested in the application of REST to web APIs.

http://en.wikipedia.org/wiki/Representational_state_transfer up on date 03/10/14

ATTENTION : There is no way that this code is production ready code, it was written for a bit of fun really, just to try and create a REST framework from scratch. If you want a production ready REST framework you should probably look at one of these

 

Where is the code

The code is available at my github page : https://github.com/sachabarber/rest

 

How do I run the demo code?

The code is contained in one solution, which is a Visual Studio 2013 solution, that when opened should look something like this.

Image 1

In order to run it correctly you should ensure that the following 2 projects are started:

  1. RESTServerConsoleHost
  2. RESTClientConsoleApp

You can actually do this manually, or via running a multi project in Visual Studio. Which you can do as shown in this screen shot:

Image 2

When you run it correctly, you should see some output from the RESTClientConsoleApp something like this:

Image 3

 

How Does It Work

This section outlines how the simple REST framework that I have written works

 

The General Idea

The REST framework I have written can be broken down into a few simple steps really

  1. Listen for incoming Http requests
  2. For the incoming Http request try and identify a IHandler (more on this later don't worry) that will handle the request
  3. Identify the correct method to call in the handler based on the http method of the request, and the content/url provided by the request url
  4. Pass the handler method the correct input parameters from the request stream, and get the result
  5. Take the result and return it to the calling code using the response stream

 

Hosting / IOC Support (End user code)

I wanted to create something that could be hosted very easily, and in pretty much any sort of app, and I did not want to rely on any other framework.

Hosting (End user code)

C#
Task.Run(() =>
    {

        IWindsorContainer container = new WindsorContainer();
        container.Install(new DomainInstaller(), new HandlerInstaller());

        //run the server
        IDependencyResolver dependencyResolver = new WindsorDependencyResolver(container);
        HttpServer httpServer = new HttpServer(dependencyResolver);
        httpServer.Run(new string[] { @"http://localhost:8001/" });
    });

There are a couple of points of note here:

  1. We use an IOC container (I have gone for Castle Windsor here, but you can you what you like)
  2. YOUR code needs to implement a IDependencyResolver, which is then handed to the REST framework, which allows it to resolve its own dependencies and user code (i.e. your code) dependencies

 

IOC Code (End user code)

Your code should provide the following things, it may be worth checking out the demo RESTServerConsoleHost project.  As I say I am using Castle Windsor, so some of the following will likely look different for you depending on what IOC container you go for

 

Dependencies Registration (End user code)

Register any dependencies you have, which are used by the IHandler based used code (which we will look at soon)

C#
public class DomainInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(Component
            .For(typeof(IRepository<Person,int>))
            .ImplementedBy(typeof(InMemoryPersonRepository))
            .LifestyleSingleton());

        container.Register(Component
            .For(typeof(IRepository<Account,int>))
            .ImplementedBy(typeof(InMemoryAccountRepository))
            .LifestyleSingleton());
            
        container.Register(Component
            .For(typeof(IRepository<User,int>))
            .ImplementedBy(typeof(InMemoryUserRepository))
            .LifestyleSingleton());
    }
}

 

Handler Registration (End user code)

Register any IHandler(s)

C#
public class HandlerInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        foreach (var item in this.GetType().Assembly.GetTypes())
        {
            if (item.GetInterfaces().Any(x => x.FullName.Contains("IHandler")))
            {
                AddHandler(container, item);
            }
        }
    }


    private void AddHandler(IWindsorContainer container, Type item)
    {
        container.Register(Component
        .For(typeof(IHandler))
        .ImplementedBy(item)
        .LifestyleTransient());
    }
}

 

Custom IDependencyResolver (End user code)

Create a custom IDependencyResolver

C#
public class WindsorDependencyResolver : IDependencyResolver
{
    private readonly IWindsorContainer container;

    public WindsorDependencyResolver(IWindsorContainer container)
    {
        this.container = container;
    }

    public object GetService(Type serviceType)
    {
        return container.Kernel.HasComponent(serviceType) ? container.Resolve(serviceType) : null;
    }

    public IEnumerable<object> GetServices(Type serviceType)
    {
        return container.Kernel.HasComponent(serviceType) ? 
            container.ResolveAll(serviceType).Cast<object>() : new object[] { };
    }
}

 

The Server (Part of framework)

The basic requirement was how do I listen for Http requests. I googled a bit and came across the HttpListener class, which allows user code to listen to Http requests.

The one down side of using the HttpListener class is you WILL need to create a url acl, but for the purpose of learning I was fine with that.

If this is not cool by you, you may wish to check  out this project, which although not production ready looks interesting, and makes use of the OWIN stack, and does not have any specific ACL requirements at all.

https://github.com/Bobris/Nowin

 

The server portion of the simple REST framework I present in this article, is suprisingly simple, all I am doing is listening for incoming Http requests, and delegating the actual work to what I am calling routing actioners. The routing actioners are examined one by one using the chain of responsibility pattern.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using RESTServer.Handlers;
using RESTServer.IOC;
using RESTServer.Routing;

namespace RESTServer
{
    public class HttpServer
    {

        private readonly HttpListener listener = new HttpListener();
        private readonly int accepts = 4;
        private readonly IDependencyResolver dependencyResolver;
        private Semaphore sem;

        public HttpServer(IDependencyResolver dependencyResolver, int accepts = 4)
        {
            listener.IgnoreWriteExceptions = true;

            // Multiply by number of cores:
            this.accepts = accepts * Environment.ProcessorCount;
            this.dependencyResolver = dependencyResolver;
        }


        public async void Run(params string[] uriPrefixes)
        {
            // Add the server bindings:
            foreach (var prefix in uriPrefixes)
                listener.Prefixes.Add(prefix);
            listener.Start();

            //Accept connections:
            //1. Higher values mean more connections can be maintained yet at a 
            //   much slower average response time; fewer connections will be rejected.
            //2. Lower values mean less connections can be maintained yet at a 
            //   much faster average response time; more connections will be rejected.
            sem = new Semaphore(accepts, accepts);
            await RunServer();

        }


        private async Task RunServer()
        {
            while (true)
            {
                // Fall through until we've initialized all our connection listeners.
                // When the semaphore blocks (its count reaches 0) we wait until a connection occurs, 
                // upon which the semaphore is released and we create another connection "awaiter."
                sem.WaitOne();
                await StartConnectionListener();
            }
        }
 
        private async Task StartConnectionListener()
        {
            // Wait for a connection
            HttpListenerContext context = await listener.GetContextAsync();
 
            // Allow a new connection listener to be set up.
            sem.Release();

            //process the current request
            await ProcessRequest(context);
        }


        private async Task<bool> ProcessRequest(HttpListenerContext context)
        {
            // Setup Chain of Responsibility for route processing
            VerbRouteActioner verbRouteActioner = new VerbRouteActioner();
            DynamicRouteActioner dynamicRouteActioner = new DynamicRouteActioner();
            BadRouteActioner badRouteActioner = new BadRouteActioner();
            verbRouteActioner.Successor = dynamicRouteActioner;
            dynamicRouteActioner.Successor = badRouteActioner;

            var handlers = dependencyResolver.GetServices(typeof(IHandler)).Cast<IHandler>().ToList();
            await verbRouteActioner.ActionRequest(context, handlers);

            return true;
        }
    }
}

As you can see I did not lie, the basic loop is very simple, wait for a request, when a new request is seen, called the route actioner classes. As I also stated the route actioners are arranged using the chain of responsibility pattern.

The following order is used

  1. VerbRouteActioner : This attempted to match a route with a user based IVerbHandler<T,TKey>. If one is found it is used, otherwise the successor (DynamicRouteHandler) is asked to deal with the request
  2. DynamicRouteHandler : This attempted to match a route with a user based IDynamicRouteHandler<T,TKey>. If one is found it is used, otherwise the successor (BadRouteHandler) is asked to deal with the request
  3. BadRouteHandler : Which is final link in the chain, and it simply returns a Http status code of 404, Not Found

So we have just mentioned a few new things there, what exactly is a xxxRouteActioner? Well put simply it is an internal class within the simple REST framework that I presenting here, that knows how to take a http request, and call the relevant derived IHandler objects, where the exact method called will depend not only on the request url but also the http verb GET/PUT/POST/DELETE.

I am sticking to the standard REST practices of there being certain input parameters, and certain return values for each of the methods that deal with the REST request. The following table shows the rules that this REST framework follows

Image 4

Don't worry if you don't quite get what the IHandler code is doing yet, we will be looking at that in more detail later on.

 

Serialization

I have allowed for 2 different types of serialization, namely Json/Xml, the serializer to support this is as shown below

Json

C#
public class JsonPipelineSerializer : ISerializer
{
    public async Task<T> Deserialize<T>(string rawBodyData)
    {
        return await Task.Run(() => JsonConvert.DeserializeObject<T>(rawBodyData));
    }

    public async Task<Byte[]> SerializeAsBytes<T>(T item)
    {
        return await Task.Run(() => Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(item)));
    }
    public async Task<string> Serialize<T>(T item)
    {
        return await Task.Run(() => JsonConvert.SerializeObject(item));
    }
}

Xml

public class XmlPipelineSerializer : ISerializer
{
    private XmlSerializer xmlSerializer;

    public async Task<T> Deserialize<T>(string rawBodyData)
    {
        xmlSerializer = new XmlSerializer(typeof(T));

        return await Task.Run(async () =>
        {
            T temp = default(T);
            using (Stream ms = await GenerateStreamFromString(rawBodyData))
            {
                temp = (T)xmlSerializer.Deserialize(ms);
            }
            return temp;
        });
    }

    public async Task<Byte[]> SerializeAsBytes<T>(T item)
    {
        xmlSerializer = new XmlSerializer(typeof(T));
        return await Task.Run(() =>
        {
            using (MemoryStream ms = new MemoryStream())
            {
                xmlSerializer.Serialize(ms, item);
                ms.Position = 0;
                return ms.ToArray();
            }
        });
    }

    public async Task<string> Serialize<T>(T item)
    {
        xmlSerializer = new XmlSerializer(typeof(T));
        return await Task.Run(() =>
        {
            using (MemoryStream ms = new MemoryStream())
            {
                xmlSerializer.Serialize(ms, item);
                ms.Position = 0;
                using (StreamReader sr = new StreamReader(ms))
                {
                    return sr.ReadToEnd();
                }
            }
        });
    }

    private async Task<Stream> GenerateStreamFromString(string s)
    {
        MemoryStream stream = new MemoryStream();
        await Task.Run(() =>
        {
            StreamWriter writer = new StreamWriter(stream, Encoding.UTF8);
            writer.Write(s);
            writer.Flush();
            stream.Position = 0;
        });
        return stream;
    }
}

 

Routing / Handlers

We already mentioned the simple REST framework has the concept of some code that is used to deal with a certain request, and if it can not be handled, calls it successor using the chain of responsibility pattern that is set up within the HttpServer code.

But what exactly does one of these xxxRouteActioner classes look like? Well there is the following abstract class that any xxxRouteActioner inherits from.

C#
public abstract class RouteActioner
{
    public abstract Task<bool> ActionRequest(
        HttpListenerContext context, 
        IList<IHandler> handlers);

    public RouteActioner Successor { set; protected get; }
}

I have actually provided 3 RouteActioner based classes within the REST framework presented here. The following table describes them.

Image 5

The xxxRouteHandler code deals with the following sort of concerns

  • Is the http request for this type of xxxRouteActioner/IHandler
  • Is there a IHandler of the right type that matches the route requested
  • Does the request contain the correct parameters to work with the xxxRouteActioner/IHandler

We will be looking at the main 2 xxxRouteHandlers and their end user code counter parts below

 

VerbRouteActioner / IVerbHandler<T,TKey>

We now know that there is 1:1 mapping between the xxxRouteActioners, and their corresponing user code. This section talks about VerbRouteActioner / IVerbHandler<T,TKey>.

 

IVerbHandler<T,TKey> (Part of the framework)

And since there is a IVerbHandler<T,TKey> interface, which is as shown below, there is also a VerbRouteActioner that goes with the user code supplied implemenation of the IVerbHandler<T,TKey>

Here is the  IVerbHandler<T,TKey> interface where it can be seen that we already have a bunch of existing methods that the user code must implement. These methods were outlined in the table we saw above, where we are expecting the user code to implement these methods:

C#
/// <summary>
/// A standard REST interface
/// </summary>
/// <typeparam name="T">An intention of the type of 
/// REST resource</typeparam>
/// <typeparam name="TKey">An intention of the type of 
/// the Id field of the REST resource</typeparam>
public interface IVerbHandler<T, TKey> : IHandler
{
    /// <summary>
    /// Gets a REST resource by its Id
    /// </summary>
    Task<T> Get(TKey id);

    /// <summary>
    /// Gets all instances of REST resource
    /// </summary>
    Task<IEnumerable<T>> Get();

    /// <summary>
    /// Add a new REST resource. Where the newly added resource is returned
    /// </summary>
    Task<T> Post(T item);

    /// <summary>
    /// Updates the REST resource identified by its Id, with the new REST resource
    /// </summary>
    Task<bool> Put(TKey id, T item);

    /// <summary>
    /// Deletes a new REST resource by its Id
    /// </summary>
    Task<bool> Delete(TKey id);
}

It can be seen that since ALL the methods we need to implement are part of the interface, I took the decision to make the return type Task<T>. This allow the REST framework to use async/await to await the return value from the user supplied implemenation of the IVerbHandler<T,TKey>

 

IVerbHandler<T,TKey> Implementation (End user code)

So we have now seen the interface, so what exactly does the user supplied mplemenation of the IVerbHandler<T,TKey> look like?

Well it looks like this

C#
[RouteBase("/people", SerializationToUse.Xml)]
public class PersonHandler : IVerbHandler<Person, int>
{
    private readonly IRepository<Person,int> personRepository;

    public PersonHandler(IRepository<Person,int> personRepository)
    {
        this.personRepository = personRepository;
    }

    #region IVerbHandler<Person,int> Members

    public async Task<Person> Get(int id)
    {
        return await Task.Run(() => personRepository.Get(id));
    }

    public async Task<IEnumerable<Person>> Get()
    {
        return await Task.Run(() => personRepository.GetAll());
    }

    public async Task<Person> Post(Person item)
    {
        return await Task.Run(() => personRepository.Add(item));
    }

    public async Task<bool> Put(int id, Person item)
    {
            
        return await Task.Run(() =>
        {
            item.Id = id;
            return personRepository.Update(item);
        });
    }

    public async Task<bool> Delete(int id)
    {
        return await Task.Run(() => personRepository.Delete(id));
    }

    #endregion
}

There are a couple of things to dicsuss here.

  1. We implement the IVerbHandler<T,TKey> where T is the type of the resource, and TKey is the type of the Id field used by the type of T. We must then write all the methods within the  IVerbHandler<T,TKey>
  2. We are able to take dependecies on other services. This is shown here where we take a dependency on a IRepository<Person>, which is an in memory repository
  3. We use a special attribute to supply some of the routing metadata. This attribute is called RouteBaseAttribute and allows the specification of 2 things
    1. The base part of the route for the the request. This is similiar to what you would get using some convention based thing, such as ASP MVC, where we would be essentially resolving the http request to a specific controller to deal with the request. I opted to go for an attribute for simplicity. We will see how this is used when we continue to look at the VerbRouteActioner
    2. We are able to specify the serialization to use, which is either Xml/Json

 

VerbRouteActioner (Part of the framework)

The VerbRouteActioner is part of chain of responsibility pattern  used within the HttpServer within this small REST framework. What happens inside the VerbRouteActioner is roughly as shown in the following bullet points:

  • We attempt to find a IVerbHandler<T,TKey> from the list of IHandlers that we have that match the given request route. This is done by using the RouteBaseAttribute which is part of the user implementation of IVerbHandler<T,TKey>
  • We then examine the http request to see what method is used, this allows us to identity which REST call is being requested.
  • We then extract the Id (type of TKey for the  IVerbHandler<T,TKey>) and Content (type of T for the  IVerbHandler<T,TKey>), and then call the relevant code in the user implementation of IVerbHandler<T,TKey>

Here is the full code for the VerbRouteActioner, It does make use of an additional helper class, which I have not included here, as I think the code is readable enough without going into that helper classes inner workings too. If you are interested that class is called RestMethodActioner

C#
public class VerbRouteActioner : RouteActioner
{
    RestMethodActioner restMethodActioner = new RestMethodActioner();

    public override async Task<bool> ActionRequest(
        HttpListenerContext context, 
        IList<IHandler> handlers)
    {
        return await Task.Run(async () =>
        {
            object matchingHandler = null;

            //1. try and find the handler who has same base address as the request url
            //   if we find a handler, go to step 2, otherwise try successor
            //2. find out what verb is being used

            var httpMethod = context.Request.HttpMethod;
            var url = context.Request.RawUrl;
            bool result = false;

            var routeResult = await restMethodActioner.FindHandler(
                typeof (IVerbHandler<,>), context, handlers, false);

            if (routeResult.Handler != null)
            {
                //handler is using RouteBase, so fair chance it is a VerbHandler
                var genericArgs = GetVerbHandlerGenericArgs(routeResult.Handler.GetType());

                MethodInfo method = typeof (VerbRouteActioner).GetMethod("DispatchToHandler",
                    BindingFlags.NonPublic | BindingFlags.Instance);

                MethodInfo generic = method.MakeGenericMethod(genericArgs[0], genericArgs[1]);
                result = await (Task<bool>) generic.Invoke(this, new object[]
                {
                    context, routeResult.Handler, httpMethod, routeResult.SerializationToUse
                });

                return result;

            }

            result = await this.Successor.ActionRequest(context, handlers);
            return result;
        });
    }

    private async Task<bool> DispatchToHandler<T, TKey>(
        HttpListenerContext context, object handler,
        string httpMethod, SerializationToUse serializationToUse)
    {
        return await Task.Run(async () =>
        {
            MethodInfo method = typeof (VerbRouteActioner).GetMethod("CreateVerbHandler",
                BindingFlags.NonPublic | BindingFlags.Instance);
            MethodInfo generic = method.MakeGenericMethod(new[] {typeof (T), typeof (TKey)});
            IVerbHandler<T, TKey> actualHandler =
                (IVerbHandler<T, TKey>) generic.Invoke(this, new[] {handler});
            var result = false;


            switch (httpMethod)
            {
                case "GET":
                    result = await HandleGet<T, TKey>(
                        actualHandler, context, serializationToUse);
                    break;
                case "PUT":
                    result = await HandlePut<T, TKey>(
                        actualHandler, context, serializationToUse);
                    break;
                case "POST":
                    result = await HandlePost<T, TKey>(
                        actualHandler, context, serializationToUse);
                    break;
                case "DELETE":
                    result = await HandleDelete<T, TKey>(
                        actualHandler, context, serializationToUse);
                    break;
            }
            return result;
        });
    }

    private async Task<bool> HandleGet<T, TKey>(
        IVerbHandler<T, TKey> actualHandler, 
        HttpListenerContext context, 
        SerializationToUse serializationToUse)
    {

        return await Task.Run(async () =>
        {
            var result = false;
            if (restMethodActioner.IsGetAll(context.Request.RawUrl))
            {
                var items = await actualHandler.Get();
                result = await restMethodActioner.SetResponse<List<T>>(
                    context, items.ToList(),
                    serializationToUse);
            }
            else
            {
                TKey id = await restMethodActioner.ExtractId<TKey>(
                    context.Request);
                var item = await actualHandler.Get(id);
                result = await restMethodActioner.SetResponse<T>(
                    context, item, serializationToUse);
            }

            return result;
        });
    }

    private async Task<bool> HandlePost<T, TKey>(
        IVerbHandler<T, TKey> actualHandler, 
        HttpListenerContext context, 
        SerializationToUse serializationToUse)
    {
        return await Task.Run(async () =>
        {
            T item = await restMethodActioner.ExtractContent<T>(
                context.Request, serializationToUse);
            T itemAdded = await actualHandler.Post(item);
            bool result = await restMethodActioner.SetResponse<T>(
                context, itemAdded, serializationToUse);
            return result;
        });
    }
  
    private async Task<bool> HandleDelete<T, TKey>(
        IVerbHandler<T, TKey> actualHandler, 
        HttpListenerContext context, 
        SerializationToUse serializationToUse)
    {
        return await Task.Run(async () =>
        {
            TKey id = await restMethodActioner.ExtractId<TKey>(context.Request);
            bool updatedOk = await actualHandler.Delete(id);
            updatedOk &= await restMethodActioner.SetOkResponse(context);
            return updatedOk;
        });
    }

    private async Task<bool> HandlePut<T, TKey>(
        IVerbHandler<T, TKey> actualHandler, 
        HttpListenerContext context, 
        SerializationToUse serializationToUse)
    {
        return await Task.Run(async () =>
        {
            TKey id = await restMethodActioner.ExtractId<TKey>(context.Request);
            T item = await restMethodActioner.ExtractContent<T>(context.Request, 
            	serializationToUse);
            bool updatedOk = await actualHandler.Put(id, item);
            updatedOk &= await restMethodActioner.SetOkResponse(context);
            return updatedOk;
        });
    }


    /// <summary>
    /// Called via Reflection
    /// </summary>
    private IVerbHandler<T, TKey> CreateVerbHandler<T, TKey>(object item)
    {
        Expression convertExpr = Expression.Convert(
                                    Expression.Constant(item),
                                    typeof(IVerbHandler<T, TKey>)
                                    );

        var x = Expression.Lambda<
            Func<IVerbHandler<T, TKey>>>(convertExpr).Compile()();
        return x;
    }

    private Type[] GetVerbHandlerGenericArgs(Type item)
    {

        var ints = item.GetInterfaces();
        var verbInterface = item.GetInterfaces().Single(
            x => x.FullName.Contains("IVerbHandler"));
        return verbInterface.GenericTypeArguments;
    }
}

 

DynamicRouteActioner / IDynamicRouteHandler<T,TKey>

We now know that there is 1:1 mapping between the xxxRouteActioners, and their corresponing user code. This section talks about DynamicRouteActioner / IDynamicRouteHandler<T,TKey>.

 

IDynamicRouteHandler<T,TKey> (Part of the framework)

There is a IDynamicRouteHandler<T,TKey> interface, which is as shown below, there is also a DynamicRouteActioner that goes with the user code supplied implemenation of the IDynamicRouteHandler<T,TKey>

Here is the  IDynamicRouteHandler<T,TKey> interface where it can be seen that this time there are no methods AT ALL. This means the user is free to specify whatever method names/signatures they want. This obvously means the end user code could in fact be bad, and not resolvable to a standard REST method, so we need to do way more checking with this type of end user handler, which we will see in just a minute

C#
/// <summary>
/// Used as a marker interface when you want to supply your own custom routes
/// by using the 
/// </summary>
/// <typeparam name="T">An intention of the type of 
/// REST resource</typeparam>
/// <typeparam name="TKey">An intention of the type of the Id 
/// field of the REST resource</typeparam>
public interface IDynamicRouteHandler<T,TKey> : IHandler
{
}

 

IDynamicRouteHandler<T,TKey> Implementation (End user code)

So we have now seen the interface, so what exactly does the user supplied implemenation of the IDynamicRouteHandler<T,TKey> look like? Well it looks like this

C#
[RouteBase("/users", SerializationToUse.Json)]
public class UserHandler : IDynamicRouteHandler<User, int>
{
    private readonly IRepository<User,int> userRepository;

    public UserHandler(IRepository<User,int> userRepository)
    {
        this.userRepository = userRepository;
    }

    #region IDynamicRouteHandler<User,int> Members


    [Route("/GetUserByTheirId/{0}", HttpMethod.Get)]
    public async Task<User> GetUserByTheirId(int id)
    {
        return await Task.Run(() => userRepository.Get(id));
    }

    [Route("/GetAllUsers", HttpMethod.Get)]
    public async Task<IEnumerable<User>> GetAllUsers()
    {
        return await Task.Run(() => userRepository.GetAll());
    }

    [Route("/AddASingleUser", HttpMethod.Post)]
    public async Task<User> AddASingleUser(User item)
    {
        return await Task.Run(() => userRepository.Add(item));
    }

    [Route("/UpdateAUserUsingId/{0}", HttpMethod.Put)]
    public async Task<bool> UpdateTheUserWithId(int id, User item)
    {
        return await Task.Run(() =>
        {
            item.Id = id;
            return userRepository.Update(item);
        });
    }

    [Route("/DeleteUserByTheirId/{0}", HttpMethod.Delete)]
    public async Task<bool> DeleteAUser(int id)
    {
        return await Task.Run(() => userRepository.Delete(id));
    }
   
    #endregion
}

There are a couple of things to dicsuss here.

  1. We implement the IDynamicRouteHandler<T,TKey> where T is the type of the resource, and TKey is the type of the Id field used by the type of T. We must then write all the methods within the  IDynamicRouteHandler<T,TKey>
  2. We are able to take dependecies on other services. This is shown here where we take a dependency on a IRepository<User>, which is an in memory repository
  3. We use a special attribute to supply some of the routing metadata. This attribute is called RouteBaseAttribute and allows the specification of 2 things
    1. The base part of the route for the the request. This is similiar to what you would get using some convention based thing, such as ASP MVC, where we would be essentially resolving the http request to a specific controller to deal with the request. I opted to go for an attribute for simplicity. We will see how this is used when we continue to look at the VerbRouteActioner
    2. We are able to specify the serialization to use, which is either Xml/Json
  4. Each of the methods uses another special attribute to supply some more routing metadata. This attribute is called RouteAttribute and allows the specification of 2 things
    1. The action part of the route for the the request. This is similiar to what you would get using some convention based thing, such as ASP MVC, where we would be essentially resolving the http request to a specific action to deal with the request. I opted to go for an attribute for simplicity. We will see how this is used when we continue to look at the DynaicRouteActioner
    2. We are able to specify the HttpMethod to use, which is either GET/PUT/POST/DELETE

 

DynamicRouteActioner (Part of the framework)

The DynamicRouteActioner is part of chain of responsibility pattern  used within the HttpServer within this small REST framework. What happens inside the DynamicRouteActioner is roughly as shown in the following bullet points:

  • We attempt to find a IDynamicHandler<T,TKey> from the list of IHandlers that we have that match the given request route. This is done by using the RouteBaseAttribute which is part of the user implementation of IVerbHandler<T,TKey>
  • We then examine the http request to see what method is used, this allows us to identity which REST call is being requested. So we get a http method from the request, but we still need to identity the correct method to call on the IDynamicHandler<T,TKey>, where the IDynamicHandler<T,TKey> is really just a marker interface, where the end user code could provide any method signatures it likes, and these may be valid or possibly invalid. So how do we deal with this? How do we know which method to call. This is the job of another custom attribute namely RouteAttribute, which we can use as follows [Route("/GetUserByTheirId/{0}", HttpMethod.Get)] on any method within the end user IDynamicHandler<T,TKey> implementation. By using the RouteAttribute, we are able to specify a url portion for matching against the incoming http request (in convention based systems such as ASP MVC think of this attribute as the ability to locate an specific action), and also specify the correct http method, that the IDynamicHandler<T,TKey> is intended to deal with. So how do we use the RouteAttribute to help us identify the correct IDynamicHandler<T,TKey> method to use? Well the answer to that question, lies in very thorough parameter/return value checking. We only assume one of the supplied IDynamicHandler<T,TKey> methods is correct, and can be invoked if the following are ALL true
    • The RouteBaseAttribute + RouteAttribute portions represent the entire request url
    • The RouteAttribute HttpMethod value matches the incoming http request method
    • The RouteAttribute attributed method signature parameters / return value (which may or may not be Task<T>) match
  • We then extract the Id (type of TKey for the  IDynamicRouteHandler<T,TKey>) and Content (type of T for the  IDynamicRouteHandler<T,TKey>), and then call the relevant code in the user implementation of IDynamicRouteHandler<T,TKey>
  • If the current IDynamicRouteHandler<T,TKey> method is found to be of Task<T> async/await will be used to await the result of the method invocation, and the write it to the http response stream. if however it is just a standard return value T, there is no awaiting required, the return value is just used to write to the http response stream.

Here is the full code for the DynamicRouteHandler, It does make use of an additional helper class, which I have not included here, as I think the code is readable enough without going into that helper classes inner workings too. If you are interested that class is called RestMethodActioner

 

C#
public class DynamicRouteActioner : RouteActioner
{
    RestMethodActioner restMethodActioner = new RestMethodActioner();

    public override async Task<bool> ActionRequest(
        HttpListenerContext context, IList<IHandler> handlers)
    {
        return await Task.Run(async () =>
        {


            object matchingHandler = null;

            //1. try and find the handler who has same base address as the request url
            //   if we find a handler, go to step 2, otherwise try successor
            //2. find out what verb is being used

            var httpMethod = context.Request.HttpMethod;
            var url = context.Request.RawUrl;
            bool result = false;

            var routeResult = await restMethodActioner.FindHandler(
                typeof (IDynamicRouteHandler<,>), context, handlers, true);

            if (routeResult.Handler != null)
            {
                //handler is using RouteBase, so fair chance it is a VerbHandler
                var genericArgs = GetDynamicRouteHandlerGenericArgs(
                    routeResult.Handler.GetType());

                MethodInfo method = typeof (DynamicRouteActioner).GetMethod("DispatchToHandler",
                    BindingFlags.NonPublic | BindingFlags.Instance);

                MethodInfo generic = method.MakeGenericMethod(genericArgs[0], genericArgs[1]);
                result = await (Task<bool>) generic.Invoke(this, new object[]
                {
                    context, routeResult.Handler, httpMethod, url, 
                    routeResult.SerializationToUse
                });

                return result;

            }

            result = await this.Successor.ActionRequest(context, handlers);
            return result;
        });
    }

    private async Task<bool> DispatchToHandler<T, TKey>(
        HttpListenerContext context, object handler,
        string httpMethod, string url, SerializationToUse serializationToUse)
    {
        return await Task.Run(async () =>
        {
            var result = false;

            DynamicMethodInfo method = null;
            switch (httpMethod)
            {
                case "GET":
                    method = await ObtainGetMethod<T, TKey>(handler, url);
                    result = await HandleGet<T, TKey>(method, handler, 
                        context, serializationToUse);
                    break;
                case "PUT":
                    method = await ObtainPutMethod<T, TKey>(handler, url);
                    result = await HandlePut<T, TKey>(method, handler, 
                        context, serializationToUse);
                    break;
                case "POST":
                    method = await ObtainPostMethod<T, TKey>(handler, url);
                    result = await HandlePost<T, TKey>(method, handler, 
                        context, serializationToUse);
                    break;
                case "DELETE":
                    method = await ObtainDeleteMethod<T, TKey>(handler, url);
                    result = await HandleDelete<T, TKey>(method, handler, 
                        context, serializationToUse);
                    break;
            }
            return result;
        });
    }


    private async Task<DynamicMethodInfo> ObtainGetMethod<T, TKey>(
        object handler, string url)
    {
        return await Task.Run(async () =>
        {
            var possibleGetMethods = ObtainPossibleMethodMatches(handler, HttpMethod.Get);

            if (restMethodActioner.IsGetAll(url))
            {
                string attributeUrl = url.Substring(url.LastIndexOf("/"));

                var method = possibleGetMethods.Where(x => x.Route.Route == attributeUrl)
                    .Select(x => x.Method).FirstOrDefault();

                if (MethodResultIsCorrectType<Task<IEnumerable<T>>>(method))
                {
                    return new DynamicMethodInfo(method, true);
                }

                if (MethodResultIsCorrectType<IEnumerable<T>>(method))
                {
                    return new DynamicMethodInfo(method, false);
                }
                throw new HttpResponseException(string.Format(
                    "Incorrect return type/parameters for route '{0}'", url));
            }
            else
            {
                var method = GetIdMethodMatch(possibleGetMethods, url);

                if (MethodResultIsCorrectType<Task<T>>(method)
                    && MethodHasSingleCorrectParameterOfType<TKey>(method))
                {
                    return new DynamicMethodInfo(method, true);
                }

                if (MethodResultIsCorrectType<T>(method)
                    && MethodHasSingleCorrectParameterOfType<TKey>(method))
                {
                    return new DynamicMethodInfo(method, false);
                }
                throw new HttpResponseException(string.Format(
                    "Incorrect return type/parameters for route '{0}'", url));
            }
        });
    }

    private async Task<DynamicMethodInfo> ObtainPutMethod<T, TKey>(
        object handler, string url)
    {
        return await Task.Run(async () =>
        {
            var possiblePutMethods = ObtainPossibleMethodMatches(handler, HttpMethod.Put);

            var method = GetIdMethodMatch(possiblePutMethods, url);

            if (MethodResultIsCorrectType<Task<bool>>(method)
                && MethodHasCorrectPutParameters<T, TKey>(method))
            {
                return new DynamicMethodInfo(method, true);
            }

            if (MethodResultIsCorrectType<bool>(method)
                && MethodHasCorrectPutParameters<T, TKey>(method))
            {
                return new DynamicMethodInfo(method, false);
            }
            throw new HttpResponseException(string.Format(
                "Incorrect return type/parameters for route '{0}'", url));
        });

    }

    private async Task<DynamicMethodInfo> ObtainPostMethod<T, TKey>(
        object handler, string url)
    {
        return await Task.Run(async () =>
        {
            var possiblePostMethods = ObtainPossibleMethodMatches(handler, HttpMethod.Post);

            string attributeUrl = url.Substring(url.LastIndexOf("/"));

            var method = possiblePostMethods.Where(x => x.Route.Route == attributeUrl)
                .Select(x => x.Method).FirstOrDefault();

            if (MethodResultIsCorrectType<Task<T>>(method)
                && MethodHasSingleCorrectParameterOfType<T>(method))
            {
                return new DynamicMethodInfo(method, true);
            }

            if (MethodResultIsCorrectType<T>(method)
                && MethodHasSingleCorrectParameterOfType<T>(method))
            {
                return new DynamicMethodInfo(method, false);
            }
            throw new HttpResponseException(string.Format(
                "Incorrect return type/parameters for route '{0}'", url));
        });

    }

    private async Task<DynamicMethodInfo> ObtainDeleteMethod<T, TKey>(
        object handler, string url)
    {
        return await Task.Run(async () =>
        {
            var possibleDeleteMethods = ObtainPossibleMethodMatches(handler, HttpMethod.Delete);

            var method = GetIdMethodMatch(possibleDeleteMethods, url);

            if (MethodResultIsCorrectType<Task<bool>>(method)
                && MethodHasSingleCorrectParameterOfType<TKey>(method))
            {
                return new DynamicMethodInfo(method, true);
            }

            if (MethodResultIsCorrectType<bool>(method)
                && MethodHasSingleCorrectParameterOfType<TKey>(method))
            {
                return new DynamicMethodInfo(method, false);
            }
            throw new HttpResponseException(string.Format(
                "Incorrect return type/parameters for route '{0}'", url));
        });
    }


    private async Task<bool> HandleGet<T, TKey>(
        DynamicMethodInfo methodInfo, object handler, 
        HttpListenerContext context, 
        SerializationToUse serializationToUse)
    {
        return await Task.Run(async () =>
        {
            var result = false;
            if (restMethodActioner.IsGetAll(context.Request.RawUrl))
            {
                if (methodInfo.IsTask)
                {
                    var items = await (Task<IEnumerable<T>>) methodInfo.Method.Invoke(handler, null);
                    result = await restMethodActioner.SetResponse<List<T>>(context,
                        items.ToList(), serializationToUse);
                }
                else
                {
                    var items = (IEnumerable<T>) methodInfo.Method.Invoke(handler, null);
                    result = await restMethodActioner.SetResponse<List<T>>(context,
                        items.ToList(), serializationToUse);
                }
            }
            else
            {
                TKey id = await restMethodActioner.ExtractId<TKey>(context.Request);

                if (methodInfo.IsTask)
                {
                    var item = await (Task<T>) methodInfo.Method.Invoke(
                        handler, new object[] {id});
                    result = await restMethodActioner.SetResponse<T>(context,
                        item, serializationToUse);
                }
                else
                {
                    var item = (T) methodInfo.Method.Invoke(handler, new object[] {id});
                    result = await restMethodActioner.SetResponse<T>(context,
                        item, serializationToUse);
                }
            }
            return result;
        });
    }


    private async Task<bool> HandlePut<T, TKey>(
        DynamicMethodInfo methodInfo, object handler,
        HttpListenerContext context, 
        SerializationToUse serializationToUse)
    {
        return await Task.Run(async () =>
        {
            T item = await restMethodActioner.ExtractContent<T>(
                context.Request, serializationToUse);
            TKey id = await restMethodActioner.ExtractId<TKey>(
                context.Request);
            var updatedOk = false;

            if (methodInfo.IsTask)
            {
                updatedOk = await (Task<bool>) methodInfo.Method.Invoke(handler, 
                    new object[] {id, item});
            }
            else
            {
                updatedOk = (bool) methodInfo.Method.Invoke(handler, null);
            }
            updatedOk &= await restMethodActioner.SetOkResponse(context);
            return updatedOk;
        });
    }

    private async Task<bool> HandlePost<T, TKey>(
        DynamicMethodInfo methodInfo, object handler,
        HttpListenerContext context, 
        SerializationToUse serializationToUse)
    {
        return await Task.Run(async () =>
        {
            T itemAdded = default(T);
            T item = await restMethodActioner.ExtractContent<T>(
                context.Request, serializationToUse);

            if (methodInfo.IsTask)
            {
                itemAdded = await (Task<T>) methodInfo.Method.Invoke(
                    handler, new object[] {item});
            }
            else
            {
                itemAdded = (T) methodInfo.Method.Invoke(handler, 
                    new object[] {item});
            }

            bool result = await restMethodActioner.SetResponse<T>(
                context, itemAdded, serializationToUse);
            return result;
        });
    }


    private async Task<bool> HandleDelete<T, TKey>(
        DynamicMethodInfo methodInfo, object handler,
        HttpListenerContext context, 
        SerializationToUse serializationToUse)
    {
        return await Task.Run(async () =>
        {
            var updatedOk = false;
            TKey id = await restMethodActioner.ExtractId<TKey>(context.Request);

            if (methodInfo.IsTask)
            {
                updatedOk = await (Task<bool>) methodInfo.Method.Invoke(
                    handler, new object[] {id});
            }
            else
            {
                updatedOk = (bool) methodInfo.Method.Invoke(handler, new object[] {id});
            }
            updatedOk &= await restMethodActioner.SetOkResponse(context);
            return updatedOk;
        });
    }


    private bool MethodHasSingleCorrectParameterOfType<TKey>(MethodInfo method)
    {
        var parameters = method.GetParameters();
        return parameters.Count() == 1 && parameters[0].ParameterType == typeof (TKey);
    }

    private bool MethodHasCorrectPutParameters<T,TKey>(MethodInfo method)
    {
        var parameters = method.GetParameters();
        return parameters.Count() == 2 &&
                parameters[0].ParameterType == typeof (TKey) &&
                parameters[1].ParameterType == typeof (T);

    }


    private bool MethodResultIsCorrectType<T>(MethodInfo method)
    {
        return method.ReturnType.IsAssignableFrom(typeof (T));
    }

    private IEnumerable<MethodMatch> ObtainPossibleMethodMatches(
        object handler, HttpMethod httpMethod)
    {
        return from x in handler.GetType().GetMethods()
                let attrib = x.GetCustomAttributes(typeof(RouteAttribute), false)
                where attrib.Length > 0 && ((RouteAttribute)attrib[0]).HttpVerb == httpMethod
                select new MethodMatch(x, (RouteAttribute)attrib[0]);
    }

    private MethodInfo GetIdMethodMatch(IEnumerable<MethodMatch> possibleMatches, string url)
    {
        string attributeUrl = url.Substring(0, url.LastIndexOf("/"));
        attributeUrl = attributeUrl.Substring(attributeUrl.LastIndexOf("/"));
        attributeUrl = attributeUrl + "/{0}";
        return possibleMatches.Where(x => x.Route.Route == attributeUrl)
            .Select(x => x.Method).FirstOrDefault();
    }

    private Type[] GetDynamicRouteHandlerGenericArgs(Type item)
    {

        var ints = item.GetInterfaces();
        var verbInterface = item.GetInterfaces().Single(
            x => x.FullName.Contains("IDynamicRouteHandler"));
        return verbInterface.GenericTypeArguments;
    }

}

 

 

IBadRouteActioner

As I have stated already, the final chain in the  chain of responsibility pattern that this simple REST framework uses is the BadRouteActioner, which is simply used to return a Http status code of 404, Not Found.

Here is the code for it

C#
public class BadRouteActioner : RouteActioner
{
    public override async Task<bool> ActionRequest(
	    HttpListenerContext context, 
        IList<IHandler> handlers)
    {
        return await Task.Run(async () =>
        {

            HttpListenerResponse response = context.Response;
            using (System.IO.Stream output = response.OutputStream)
            {
                var buffer = Encoding.UTF8.GetBytes("Not Found");
                output.Write(buffer, 0, buffer.Length);
                response.StatusCode = 404;
                response.StatusDescription = Enum.GetName(typeof (HttpStatusCode),
                    HttpStatusCode.NotFound);
            }
            return true;
        });
    }
}

 

 

.NET Client

I wanted to create some helper code to make using a .NET client as simple as possible. To that end I came up with the following helper code, which makes writing REST methods pretty easy.

C#
public class RESTWebClient : WebClient
{
    private WebRequest request = null;
    private XmlPipelineSerializer xmlPipelineSerializer = new XmlPipelineSerializer();
    private JsonPipelineSerializer jsonPipelineSerializer = new JsonPipelineSerializer();

    protected override WebRequest GetWebRequest(Uri address)
    {
        this.request = base.GetWebRequest(address);

        if (this.request is HttpWebRequest)
        {
            ((HttpWebRequest)this.request).AllowAutoRedirect = false;
        }

        return this.request;
    }

    public async Task<RESTResponse<T>> Get<T>(
        string url, SerializationToUse serializationToUse)
    {
        return await Task.Run(async () =>
        {
            string response = await Task.Run(() => DownloadString(url));
            return await CreateResponse<T>(response, serializationToUse);
        });
    }

    public async Task<RESTResponse<T>> Post<T>(
        string url, T item, SerializationToUse serializationToUse)
    {
        return await Task.Run(async () =>
        {
            byte[] responsebytes = await UploadDataForMethod(url, "POST", item, serializationToUse);
            string responsebody = string.Empty;
            if (serializationToUse == SerializationToUse.Xml)
            {
                responsebody = Encoding.UTF8.GetString(responsebytes);
            }
            if (serializationToUse == SerializationToUse.Json)
            {
                responsebody = Encoding.UTF8.GetString(responsebytes);
            }
            return await CreateResponse<T>(responsebody, serializationToUse);
        });
    }

    public async Task<HttpStatusCode> Delete(string url)
    {
        return await Task.Run(async () =>
        {
            var request = WebRequest.Create(url);
            request.Method = "DELETE";
            var response = await request.GetResponseAsync();
            return ((HttpWebResponse) response).StatusCode;
        });
    }


    public async Task<HttpStatusCode> Put<T>(
        string url, T item, SerializationToUse serializationToUse)
    {
        return await Task.Run(async () =>
        {
            await UploadDataForMethod(url, "PUT", item, serializationToUse);
            return await StatusCode();
        });
    }




    private async Task<byte[]> UploadDataForMethod<T>(
        string url, string httpMethod, T item, SerializationToUse serializationToUse)
    {
        return await Task.Run(async () =>
        {
            if (serializationToUse == SerializationToUse.Xml)
            {
                Headers.Add("Content-Type", "application/xml");
                var serialized = await xmlPipelineSerializer.SerializeAsBytes(item);
                return await Task.Run(() => UploadData(url, httpMethod, serialized));
            }
            if (serializationToUse == SerializationToUse.Json)
            {
                Headers.Add("Content-Type", "application/json");
                var serialized = await jsonPipelineSerializer.SerializeAsBytes(item);
                return await Task.Run(() => UploadData(url, httpMethod, serialized));
            }
            throw new InvalidOperationException("You need to specify either Xml or Json serialization");
        });

    }


    private async Task<HttpStatusCode> StatusCode()
    {
        return await Task.Run(() =>
        {
            if (this.request == null)
            {
                throw (new InvalidOperationException(
                    "Unable to retrieve the status code, maybe you haven't made a request yet."));
            }

            HttpWebResponse response = base.GetWebResponse(this.request) as HttpWebResponse;

            if (response != null)
            {
                return response.StatusCode;
            }
            throw (new InvalidOperationException(
                "Unable to retrieve the status code, maybe you haven't made a request yet."));
        });
    }

    private async Task<RESTResponse<T>> CreateResponse<T>(
        string response, SerializationToUse serializationToUse)
    {
        return await Task.Run(async () =>
        {
            if (serializationToUse == SerializationToUse.Xml)
            {
                return new RESTResponse<T>()
                {
                    Content = await xmlPipelineSerializer.Deserialize<T>(response),
                    StatusCode = await StatusCode()
                };
            }
            if (serializationToUse == SerializationToUse.Json)
            {
                return new RESTResponse<T>()
                {
                    Content = await jsonPipelineSerializer.Deserialize<T>(response),
                    StatusCode = await StatusCode()
                };
            }
            throw new InvalidOperationException(
                "You need to specify either Xml or Json serialization");
        });
    }

}

By using this code, performing the various REST calls required by this simple REST framework boils down to code like this

GET

A simple GET would be something like this

C#
public async Task GetPerson(int id)
{
    await Task.Run(async () =>
    {
        using (RESTWebClient client = new RESTWebClient())
        {
            string getUrl = string.Format("http://localhost:8001/people/{0}", id);
            var response = await client.Get<Person>(getUrl, SerializationToUse.Xml);
        }
    });
}

 

PUT

A simple PUT would be something like this

C#
public async Task PutPerson()
{
    await Task.Run(async () =>
    {
        using (RESTWebClient client = new RESTWebClient())
        {
            string getUrl = string.Format("http://localhost:8001/people/{0}", 1);
            var response = await client.Get<Person>(getUrl, SerializationToUse.Xml);
            var person = response.Content;
            string newLastName = string.Format("{0}_Modified_{1}", person.LastName, DateTime.Now.Ticks);
            person.LastName = newLastName;

            string putUrl = string.Format("http://localhost:8001/people/{0}", 1);
            var statusCode = await client.Put(putUrl, person, SerializationToUse.Xml);
        }
    });
}

POST

A simple POST would be something like this

C#
public async Task PostPerson()
{
    await Task.Run(async () =>
    {
        using (RESTWebClient client = new RESTWebClient())
        {
            string postUrl = "http://localhost:8001/people";
            Person newPerson = new Person();
            newPerson.FirstName = string.Format("FirstName_{0}", DateTime.Now.Ticks);
            newPerson.LastName = string.Format("LastName_{0}", DateTime.Now.Ticks);
            var response = await client.Post<Person>(postUrl, newPerson, SerializationToUse.Xml);
        }
    });
}

DELETE

A simple DELETE would be something like this

C#
public async Task DeletePerson()
{
    await Task.Run(async () =>
    {
        using (RESTWebClient client = new RESTWebClient())
        {
            string deleteUrl = string.Format("http://localhost:8001/people/{0}", 1);
            var statusCode = await client.Delete(deleteUrl);
        }
    });
}

 

Other Clients

To use other clients such as JavaScript ones, there are a host of useful tools out there, such as JQuery/Angular. So if you plan on using a JavaScript client to experiment with, the internet / StackOverflow are your friends here.

 

That's It

Anyway that is all I wanted to say in this one. I realise this is not really much use to people, other than a learing excercise, but if you feel inclined to write a comment or cast a vote them are most welcome.

 

 

 

 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)