Article Custom REST Host

If for some reason the hosts provided by the SDK are not sufficient you can create your own rest host.   It is rare that you should have to do this but it an option for specific scenarios.  This article assumes you are referencing the latest REST SDK in your project.

Creating Your Class

As mentioned in the Hosts article, all hosts inherit Telligent.Evolution.Extensibility.Rest.Version1.RestHost.  This class encapsulates the REST communication framework while providing members for custom hosts to implement individually.   You should never try and implement anything lower level then the RestHost abstract class.

For this example we will implement a host that does not use OAuth at all and instead uses API keys to authenticate users.  It is important to note that OAuth is still preferred however for some utility aspects API keys are easier and acceptable to use.  If you are integrating with a production website or application you should not use API keys.  To can learn more by reviewing the Rest Authentication topic.

Create a new class called APIKeyRESTHost that inherits from Telligent.Evolution.Extensibility.Rest.Version1.RestHost.

namespace Samples
{
    public class APIKeyRestHost : Telligent.Evolution.Extensibility.Rest.Version1.RestHost
    {
    }
}

If you right click Telligent.Evolution.Extensibility.Rest.Version1.RestHost in the class definition in Visual Studio and select "Implement Abstract Class" you will see it adds 3 members. 

namespace Samples
{
    public class APIKeyRestHost : Telligent.Evolution.Extensibility.Rest.Version1.RestHost
    {
        public override void ApplyAuthenticationToHostRequest(System.Net.HttpWebRequest request, bool forAccessingUser)
        {
            throw new NotImplementedException();
        }

        public override string EvolutionRootUrl
        {
            get { throw new NotImplementedException(); }
        }

        public override string Name
        {
            get { throw new NotImplementedException(); }
        }
    }
}

ApplyAuthenticationToHostRequest is the method called by each REST call to add the appropriate authentication headers.  This will be convered in detail but here we will determine the logged on user and apply the impersonation and API key headers to the request.

EvolutionRootUrl is the base URL to your community used for all requests going forward.(e.g. http://community.mysite.com/).

Name is used to identify the host in a friendly way.  In a custom host it has no specific functional value unless it is used by the implementer.  For example the Default Rest Host stores multiple hosts in memory and uses this property to reference and retrieve specific instances.  Even if not used a value should still be returned.

Implementing the API Key Host

Start by adding the name by hardcoding "APIKeyRestHost" as the property return value.  As mentioned we are not going to be using this field in this implementation so there is no need to make this dynamic.

public override string Name
{
   get { return "APIKeyRestHost"; }
}

Getting Required Information

You could also hardcode the URL to your community in the EvolutionRootUrl property but generally this isn't a good practice.  Especially if you plan on using this tool for multiple communities or different environments.  So we are going to gather this information dynamically.

Additionally we need to get some other pieces of information that are required for this host to work:

Admin API Key -  This is the default key we will use.  For simplicity's sake we are using an admin account however for this implementation this API key user minimally needs only impersonate user and view profile permissions in your community.

Admin Username - The username associated to the admin API key mentioned above.

These pieces of information are used by our host whenever a call to REST is made from the host that is set to not impersonate.  One thing to keep in mind is that a consumer of your host can make any call without impersonation (so using the admin API key in our case) which means depending on the security level you give to this API key user you may be exposing too much or too little.

Since these are all community specific, we will pass them in as part of the constructor and then use the Url value for the required EvolutionRootUrl property.

namespace Samples
{
    public class APIKeyRestHost : Telligent.Evolution.Extensibility.Rest.Version1.RestHost
    {
        private string _communityUrl = null;
        private string _adminAPIKey = null;
        private string _adminUserName = null;

        public APIKeyRestHost(string communityUrl, string adminUserName, string adminApiKey)
        {
            _communityUrl = communityUrl;
            _adminAPIKey = adminApiKey;
            _adminUserName = adminUserName;
        }
        
        public override void ApplyAuthenticationToHostRequest(System.Net.HttpWebRequest request, bool forAccessingUser)
        {
            
        }

        public override string EvolutionRootUrl
        {
            get { return _communityUrl; }
        }

        public override string Name
        {
            get { return "APIKeyRestHost"; }
        }
    }
}

Adding Authentication Headers

You will need to understand API Key authentication for this which can be found by reviewing the REST Authentication topic.

The ApplyAuthenticationToHostRequest is the most involved member.  It is here that we apply the authentication headers we gathered in the constructor and deal with impersonation.  First we must always apply the API key information we gathered in the constructor to every request.  Notice that the HttpWebRequest is given to you as an argument to this method.

public override void ApplyAuthenticationToHostRequest(System.Net.HttpWebRequest request, bool forAccessingUser)
{
    var adminKey = String.Format("{0}:{1}", _adminAPIKey, _adminUserName);
    var adminKeyBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(adminKey));

    request.Headers.Add("Rest-User-Token", adminKeyBase64);
}

Next, we use the forAccessingUser boolean argument to determine if we need to impersonate.   If it's false the we will not be adding any additional information.  If it's true we need to impersonate.  For this example we are going to use the HttpContext user and this host will only work if the user is logged in.  Notice there is a GetCurrentHttpContext() method built in to the parent class to get an HttpContextBase object.

We will obtain the username from context and then do a lookup of that username in community to make sure it exists and if not throw an exception.  You could also implement logic that automatically creates the user here but for this example we assume they are already in the community user base. Remember to set the enableImpersonation flag on the GetToDynamic call to false or your application will end up in an endless loop towards disaster!

public override void ApplyAuthenticationToHostRequest(System.Net.HttpWebRequest request, bool forAccessingUser)
{
    var adminKey = String.Format("{0}:{1}", _adminAPIKey, _adminUserName);
    var adminKeyBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(adminKey));
    request.Headers.Add("Rest-User-Token", adminKeyBase64);
    
    if (forAccessingUser)
    {
        var httpContext = this.GetCurrentHttpContext();
        if (!httpContext.User.Identity.IsAuthenticated)
            throw new AuthenticationException("This REST Host does not support anonymous access.");
    
        var username = httpContext.User.Identity.Name;
        var user = this.GetToDynamic(2, "users/{username}.json", false, new RestGetOptions()
        {
            PathParameters = { { "username", username } }
        });
    
        if (user == null)
            throw new ApplicationException(string.Format("User '{0}' wasn't found.  Create this user in your community first"));
     }      
}

If the user is found use the username in the impersonate header.

public override void ApplyAuthenticationToHostRequest(System.Net.HttpWebRequest request, bool forAccessingUser)
{
    var adminKey = String.Format("{0}:{1}", _adminAPIKey, _adminUserName);
    var adminKeyBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(adminKey));

    request.Headers.Add("Rest-User-Token", adminKeyBase64);
    if (forAccessingUser)
    {
        var httpContext = this.GetCurrentHttpContext();
        if (!httpContext.User.Identity.IsAuthenticated)
            throw new AuthenticationException("This REST Host does not support anonymous access.");
    
        var username = httpContext.User.Identity.Name;
        var user = this.GetToDynamic(2, "users/{username}.json", false, new RestGetOptions()
        {
            PathParameters = { { "username", username } }
        });
    
        if (user == null)
            throw new ApplicationException(string.Format("User '{0}' wasn't found.  Create this user in your community first"));
      
        //Apply impersonation
        request.Headers.Add("Rest-Impersonate-User", username);
     }      
}

Here is the host all put together:

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Authentication;
using System.Text;
using System.Web;
using Telligent.Evolution.Extensibility.Rest.Version1;

namespace Samples
{
    public class APIKeyRestHost : Telligent.Evolution.Extensibility.Rest.Version1.RestHost
    {
        private string _communityUrl = null;
        private string _adminAPIKey = null;
        private string _adminUserName = null;

        public APIKeyRestHost(string communityUrl, string adminUserName, string adminApiKey)
        {
            _communityUrl = communityUrl;
            _adminAPIKey = adminApiKey;
            _adminUserName = adminUserName;
        }
        public override void ApplyAuthenticationToHostRequest(System.Net.HttpWebRequest request, bool forAccessingUser)
        {
            var adminKey = String.Format("{0}:{1}", _adminAPIKey, _adminUserName);
            var adminKeyBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(adminKey));

            request.Headers.Add("Rest-User-Token", adminKeyBase64);

            if (forAccessingUser)
            {
                var httpContext = this.GetCurrentHttpContext();
                if (!httpContext.User.Identity.IsAuthenticated)
                    throw new AuthenticationException("This REST Host does not support anonymous access.");

                var username = httpContext.User.Identity.Name;
                var user = this.GetToDynamic(2, "users/{username}.json", false, new RestGetOptions()
                {
                    PathParameters = { { "username", username } }
                });

                if (user == null)
                    throw new ApplicationException(string.Format("User '{0}' wasn't found.  Create this user in your community first"));

                //NOTE: You could attempt to auto create the account using the admin API key, this host chooses not to.

                //Apply impersonation
                request.Headers.Add("Rest-Impersonate-User", username);

            }
        }

        public override string EvolutionRootUrl
        {
            get { return _communityUrl; }
        }

        public override string Name
        {
            get { return "APIKeyRestHost"; }
        }
    }
}

Using The Host

Now that the host is implemented it is simply a metter of constructing an instance of our API Key host and invoking one of the REST calls supported by the SDK host:

var host = new APIKeyRestHost("http://yourcommunityurl.com", "[Admin USername]", "[Admin API Key]");

//Call an API
var user = host.GetToDynamic(2, "users/{username}.json", true,new RestGetOptions()
{
    PathParameters = { { "username","pmason"} }
});