Creating the OData Service

The type of service used to host this project is the WCF Data Services. This service is also known as the REST service in WCF.
All the example in the documenation will assume a service called Api.svc that is available from the root of your application. Ofcourse you can change this to your own needs/liking.

Creating the class

Creating the class is fairly simple in C#.NET.
Simple add a new Service class and alter the code to look like this:

    /// <summary>
    /// <para>This class represents the entire OData WCF Service that handles incoming requests and processes the data needed
    /// for those requests. The class inherits from the <see cref="ODataService<T>">ODataService</see> class in the toolkit to
    /// implement the desired functionality.</para>
    /// </summary>
    [ServiceBehavior(IncludeExceptionDetailInFaults = true, InstanceContextMode = InstanceContextMode.PerSession)]
    public class Api : ODataService<Context>
    {
        #region Initialization & Authentication

        // This method is called only once to initialize service-wide policies.
        public static void InitializeService(DataServiceConfiguration config)
        {
            config.UseVerboseErrors = true;
            config.SetEntitySetAccessRule("*", EntitySetRights.All);
            config.SetServiceOperationAccessRule("*", ServiceOperationRights.All);
            config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
            Factory.SetImplementation(typeof(Api2.Implementation.Api));
        }

        /// <summary>
        /// <para>This function is called when a request needs to be processed by the OData API.</para>
        /// <para>This function will look at the headers that are supplied to the request and try to extract the relevant
        /// user credentials from these headers. Using those credentials, a login is attempted. If the login is successfull,
        /// the request is processed. If the login fails, an AuthenticationException is raised instead.</para>
        /// <para>The function will also add the required response headers to the service reply to indicate the success
        /// or failure of the Authentication attempt.</para>
        /// </summary>
        /// <param name="args">The arguments needed to process the incoming request.</param>
        /// <exception cref="AuthenticationException">Invalid username and/or password.</exception>
        /// <exception cref="AuthenticationException">No account exists for the url '{0}'.</exception>
        protected override void OnStartProcessingRequest(ProcessRequestArgs args)
        {
            try
            {
                string uri = DetermineUrl();
                bool authSuccess = Authenticate(args.OperationContext, uri);
                if (!authSuccess) throw new AuthenticationException(@"Invalid username and/or password");
                base.OnStartProcessingRequest(args);
            }
            catch (Exception e)
            {
                throw new AuthenticationException(string.Format("No account exists for the url '{0}'.", System.Web.HttpContext.Current.Request.Url.AbsoluteUri), e);
            }
        }

        /// <summary>
        /// <para>Determines the URL for the current request.</para>
        /// </summary>
        /// <returns>The URL for the current request.</returns>
        /// <exception cref="InvalidOperationException">Could not determine the Uri for the DataService</exception>
        private static string DetermineUrl()
        {
            if (System.Web.HttpContext.Current != null) return System.Web.HttpContext.Current.Request.Url.ToString();
            if (OperationContext.Current != null)
            {
                if (OperationContext.Current.IncomingMessageProperties.ContainsKey("MicrosoftDataServicesRequestUri"))
                {
                    var uri = (OperationContext.Current.IncomingMessageProperties["MicrosoftDataServicesRequestUri"] as Uri);
                    if (uri != null) return uri.ToString();
                    throw new InvalidOperationException("Could not determine the Uri for the DataService.");
                }
                if (OperationContext.Current.IncomingMessageHeaders != null) return OperationContext.Current.IncomingMessageHeaders.To.AbsoluteUri;
            }
            throw new InvalidOperationException("Could not determine the Uri for the DataService.");
        }

        /// <summary>
        /// <para>Performs authentication based upon the data present in the custom headers supplied by the client.</para>
        /// </summary>
        /// <param name="context">The OperationContext for the request</param>
        /// <param name="url">The URL for the request</param>
        /// <returns>True if the Authentication succeeded; otherwise false.</returns>
        private static bool Authenticate(DataServiceOperationContext context, string url)
        {
            // Check if the header is present
            string header = context.RequestHeaders["TenForce-Auth"];
            if (string.IsNullOrEmpty(header)) return false;

            // Decode the header from the base64 encoding
            header = Encoding.UTF8.GetString(Convert.FromBase64String(header));

            // Split the header and try to authenticate.
            string[] components = header.Split('|');
            string databaseId = Factory.CreateApi().Login.ConstructDatabaseId(new Uri(url));
            return (components.Length >= 2) && Factory.CreateApi().Login.Authenticate(databaseId, components[0], components[1]);
        }

        #endregion
   }


The above code gives a demonstration on how the service is configured on start up and provides a simple authentication mechanism through the use of a custom header. This header contains the password and username for the person using the Api and is encoded as a base64 string. This is ofcourse not an ideal mechanism, but provides simply one way of inserting authentication into the OData protocol without breaking the actual protocol.

Note: A special function was added that allows the determination of the URL accesing the application. When running the application on a multi-instance server or from a local console, it's not always possible to see the external URL that maps to the application. This function tries various ways to determine the actual URL.

Last edited Oct 13, 2011 at 3:14 PM by Airslash, version 1

Comments

No comments yet.