File: System\Data\Services\DataServiceHostWrapper.cs
Project: ndp\fx\src\DataWeb\Server\System.Data.Services.csproj (System.Data.Services)
//---------------------------------------------------------------------
// <copyright file="DataServiceHostWrapper.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <summary>
//      Provides the wrapper class for DataService hosts.
// </summary>
//
// @owner  Microsoft
//---------------------------------------------------------------------
 
namespace System.Data.Services
{
    #region Namespaces.
 
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Net;
    using System.Diagnostics;
 
    #endregion Namespaces.
 
    /// <summary>
    /// Enum to represent various http methods
    /// </summary>
    internal enum AstoriaVerbs
    {
        #region Values.
 
        /// <summary>Not Initialized.</summary>
        None,
 
        /// <summary>Represents the GET http method.</summary>
        GET,
 
        /// <summary>Represents the PUT http method.</summary>
        PUT,
 
        /// <summary>Represents the POST http method.</summary>
        POST,
 
        /// <summary>Represents the DELETE http method.</summary>
        DELETE,
 
        /// <summary>Represents the MERGE http method.</summary>
        MERGE
 
        #endregion Values.
    }
 
    /// <summary>
    /// Provides access to the environment for a DataService,
    /// including information about the current request.
    /// </summary>
    internal class DataServiceHostWrapper
    {
        #region Private Fields
 
        /// <summary>Reference to the IDataServiceHost object we are wrapping</summary>
        private readonly IDataServiceHost host;
 
        /// <summary>Gets a comma-separated list of client-supported MIME Accept types.</summary>
        private readonly string requestAccept;
 
        /// <summary>Gets the string with the specification for the character set encoding that the client requested, possibly null.</summary>
        private readonly string requestAcceptCharSet;
 
        /// <summary>Gets the HTTP MIME type of the request stream.</summary>
        private readonly string requestContentType;
 
        /// <summary>Gets the value of the If-Match header from the request made</summary>
        private readonly string requestIfMatch;
 
        /// <summary>Gets the value of the If-None-Match header from the request made</summary>
        private readonly string requestIfNoneMatch;
        
        /// <summary>Gets the value for the MaxDataServiceVersion request header.</summary>
        private readonly string requestMaxVersion;
 
        /// <summary>Gets the value for the DataServiceVersion request header.</summary>
        private readonly string requestVersion;
 
        /// <summary>
        /// Get the enum representing the http method name.
        /// We have this for perf reason since enum comparison is faster than string comparison.
        /// </summary>
        private AstoriaVerbs astoriaHttpVerb;
 
        /// <summary>Gets the HTTP data transfer method (such as GET, POST, or HEAD) used by the client.</summary>
        private string requestHttpMethod;
 
        /// <summary>Gets the absolute URI to the resource upon which to apply the request.</summary>
        private Uri absoluteRequestUri;
 
        /// <summary>Gets the absolute URI to the service.</summary>
        private Uri absoluteServiceUri;
 
        /// <summary>Gets the <see cref="Stream"/> from which the input must be read to the client.</summary>
        private Stream requestStream;
 
        /// <summary>Gets the <see cref="Stream"/> to be written to send a response to the client.</summary>
        private Stream responseStream;
 
        /// <summary>Request headers</summary>
        private WebHeaderCollection requestHeaders;
 
        /// <summary>Response headers</summary>
        private WebHeaderCollection responseHeaders;
 
        #endregion Private Fields
 
        #region Constructor
 
        /// <summary>
        /// Constructs an instance of the DataServiceHostWrapper object.
        /// </summary>
        /// <param name="host">IDataServiceHost instance to wrap.</param>
        internal DataServiceHostWrapper(IDataServiceHost host)
        {
            Debug.Assert(host != null, "host != null");
            this.host = host;
            this.astoriaHttpVerb = AstoriaVerbs.None;
            this.requestAccept = host.RequestAccept;
            this.requestAcceptCharSet = host.RequestAcceptCharSet;
            this.requestContentType = host.RequestContentType;
            this.requestIfMatch = host.RequestIfMatch;
            this.requestIfNoneMatch = host.RequestIfNoneMatch;
            this.requestMaxVersion = host.RequestMaxVersion;
            this.requestVersion = host.RequestVersion;
        }
 
        #endregion Constructor
 
        #region Properties.
 
        /// <summary>
        /// Get the enum representing the http method name.
        /// </summary>
        /// <param name="httpMethodName">http method used for the request.</param>
        /// <returns>enum representing the http method name.</returns>
        internal AstoriaVerbs AstoriaHttpVerb
        {
            get
            {
                if (this.astoriaHttpVerb == AstoriaVerbs.None)
                {
                    string requestMethod = this.RequestHttpMethod;
                    Debug.Assert(!string.IsNullOrEmpty(requestMethod), "!string.IsNullOrEmpty(requestMethod)");
                    switch (requestMethod)
                    {
                        case XmlConstants.HttpMethodGet:
                            this.astoriaHttpVerb = AstoriaVerbs.GET;
                            break;
 
                        case XmlConstants.HttpMethodPost:
                            this.astoriaHttpVerb = AstoriaVerbs.POST;
                            break;
 
                        case XmlConstants.HttpMethodPut:
                            this.astoriaHttpVerb = AstoriaVerbs.PUT;
                            break;
 
                        case XmlConstants.HttpMethodMerge:
                            this.astoriaHttpVerb = AstoriaVerbs.MERGE;
                            break;
 
                        case XmlConstants.HttpMethodDelete:
                            this.astoriaHttpVerb = AstoriaVerbs.DELETE;
                            break;
 
                        default:
                            // 501: Not Implemented (rather than 405 - Method Not Allowed, 
                            // which implies it was understood and rejected).
                            throw DataServiceException.CreateMethodNotImplemented(Strings.DataService_NotImplementedException);
                    }
                }
 
                return this.astoriaHttpVerb;
            }
        }
 
        /// <summary>Gets the absolute resource upon which to apply the request.</summary>
        internal Uri AbsoluteRequestUri
        {
            get
            {
                if (this.absoluteRequestUri == null)
                {
                    this.absoluteRequestUri = this.host.AbsoluteRequestUri;
                    if (this.absoluteRequestUri == null)
                    {
                        throw new InvalidOperationException(Strings.RequestUriProcessor_AbsoluteRequestUriCannotBeNull);
                    }
                    else if (!this.absoluteRequestUri.IsAbsoluteUri)
                    {
                        throw new InvalidOperationException(Strings.RequestUriProcessor_AbsoluteRequestUriMustBeAbsolute);
                    }
                }
 
                return this.absoluteRequestUri;
            }
        }
 
        /// <summary>Gets the absolute URI to the service.</summary>
        internal Uri AbsoluteServiceUri
        {
            get
            {
                if (this.absoluteServiceUri == null)
                {
                    this.absoluteServiceUri = this.host.AbsoluteServiceUri;
                    if (this.absoluteServiceUri == null)
                    {
                        throw new InvalidOperationException(Strings.RequestUriProcessor_AbsoluteServiceUriCannotBeNull);
                    }
                    else if (!this.absoluteServiceUri.IsAbsoluteUri)
                    {
                        throw new InvalidOperationException(Strings.RequestUriProcessor_AbsoluteServiceUriMustBeAbsolute);
                    }
                }
 
                return this.absoluteServiceUri;
            }
        }
 
        /// <summary>Gets a comma-separated list of client-supported MIME Accept types.</summary>
        internal string RequestAccept
        {
            get { return this.requestAccept; }
        }
 
        /// <summary>Gets the string with the specification for the character set encoding that the client requested, possibly null.</summary>
        internal string RequestAcceptCharSet
        {
            get { return this.requestAcceptCharSet; }
        }
 
        /// <summary>Gets the HTTP MIME type of the request stream.</summary>
        internal string RequestContentType
        {
            get { return this.requestContentType; }
        }
 
        /// <summary>Gets the HTTP data transfer method (such as GET, POST, or HEAD) used by the client.</summary>
        internal string RequestHttpMethod
        {
            get
            {
                if (string.IsNullOrEmpty(this.requestHttpMethod))
                {
                    this.requestHttpMethod = this.host.RequestHttpMethod;
                    if (String.IsNullOrEmpty(this.requestHttpMethod))
                    {
                        throw new InvalidOperationException(Strings.DataServiceHost_EmptyHttpMethod);
                    }
                }
 
                return this.requestHttpMethod;
            }
        }
 
        /// <summary>Gets the value of the If-Match header from the request made</summary>
        internal string RequestIfMatch
        {
            get { return this.requestIfMatch; }
        }
 
        /// <summary>Gets the value of the If-None-Match header from the request made</summary>
        internal string RequestIfNoneMatch
        {
            get { return this.requestIfNoneMatch; }
        }
 
        /// <summary>Gets the value for the MaxDataServiceVersion request header.</summary>
        internal string RequestMaxVersion
        {
            get { return this.requestMaxVersion; }
        }
 
        /// <summary>Gets the <see cref="Stream"/> from which the input must be read to the client.</summary>
        internal Stream RequestStream
        {
            get
            {
                if (this.requestStream == null)
                {
                    this.requestStream = this.host.RequestStream;
                    if (this.requestStream == null)
                    {
                        throw DataServiceException.CreateBadRequestError(Strings.BadRequest_NullRequestStream);
                    }
                }
 
                return this.requestStream;
            }
        }
 
        /// <summary>Gets the value for the DataServiceVersion request header.</summary>
        internal string RequestVersion
        {
            get { return this.requestVersion; }
        }
 
        /// <summary>Request headers</summary>
        internal WebHeaderCollection RequestHeaders
        {
            get
            {
                if (this.requestHeaders == null)
                {
                    IDataServiceHost2 host2 = DataServiceHostWrapper.ValidateAndCast<IDataServiceHost2>(this.host);
                    this.requestHeaders = host2.RequestHeaders;
                    if (this.requestHeaders == null)
                    {
                        throw new InvalidOperationException(Strings.DataServiceHost_RequestHeadersCannotBeNull);
                    }
                }
 
                return this.requestHeaders;
            }
        }
 
        /// <summary>Response headers</summary>
        internal WebHeaderCollection ResponseHeaders
        {
            get
            {
                if (this.responseHeaders == null)
                {
                    IDataServiceHost2 host2 = DataServiceHostWrapper.ValidateAndCast<IDataServiceHost2>(this.host);
                    this.responseHeaders = host2.ResponseHeaders;
                    if (this.responseHeaders == null)
                    {
                        throw new InvalidOperationException(Strings.DataServiceHost_ResponseHeadersCannotBeNull);
                    }
                }
 
                return this.responseHeaders;
            }
        }
 
        /// <summary>Gets or sets the Cache-Control header on the response.</summary>
        internal string ResponseCacheControl
        {
            // No up stream caller
            // get { return this.host.ResponseCacheControl; }
            set { this.host.ResponseCacheControl = value; }
        }
 
        /// <summary>Gets or sets the HTTP MIME type of the output stream.</summary>
        internal string ResponseContentType
        {
            get { return this.host.ResponseContentType; }
            set { this.host.ResponseContentType = value; }
        }
 
        /// <summary>Gets/Sets the value of the ETag header on the response</summary>
        internal string ResponseETag
        {
            get { return this.host.ResponseETag; }
            set { this.host.ResponseETag = value; }
        }
 
        /// <summary>Gets or sets the Location header on the response.</summary>
        internal string ResponseLocation
        {
            // No up stream caller
            // get { return this.host.ResponseLocation; }
            set { this.host.ResponseLocation = value; }
        }
 
        /// <summary>
        /// Returns the status code for the request made
        /// </summary>
        internal int ResponseStatusCode
        {
            get { return this.host.ResponseStatusCode; }
            set { this.host.ResponseStatusCode = value; }
        }
 
        /// <summary>Gets the <see cref="Stream"/> to be written to send a response to the client.</summary>
        internal Stream ResponseStream
        {
            get
            {
                if (this.responseStream == null)
                {
                    this.responseStream = this.host.ResponseStream;
                    if (this.responseStream == null)
                    {
                        throw DataServiceException.CreateBadRequestError(Strings.BadRequest_NullResponseStream);
                    }
                }
 
                return this.responseStream;
            }
        }
 
        /// <summary>Gets or sets the value for the DataServiceVersion response header.</summary>
        internal string ResponseVersion
        {
            set { this.host.ResponseVersion = value; }
        }
 
        /// <summary>If the wrapped host is a HttpContextServiceHost, returns the host</summary>
        internal HttpContextServiceHost HttpContextServiceHost
        {
            get { return this.host as HttpContextServiceHost; }
        }
 
        /// <summary>If the wrapped host is a BatchServiceHost, returns the batch host</summary>
        internal BatchServiceHost BatchServiceHost
        {
            get
            {
                Debug.Assert(this.host is BatchServiceHost, "We shouldn't be calling this outside of the batching code path.");
                return this.host as BatchServiceHost;
            }
        }
 
        #endregion Properties.
 
        #region Methods.
 
        /// <summary>Gets the value for the specified item in the request query string.</summary>
        /// <param name="item">Item to return.</param>
        /// <returns>
        /// The value for the specified item in the request query string;
        /// null if <paramref name="item"/> is not found.
        /// </returns>
        internal string GetQueryStringItem(string item)
        {
            return this.host.GetQueryStringItem(item);
        }
 
        /// <summary>Method to handle a data service exception during processing.</summary>
        /// <param name="args">Exception handling description.</param>
        internal void ProcessException(HandleExceptionArgs args)
        {
            this.host.ProcessException(args);
        }
 
        /// <summary>Verifies that query parameters are valid.</summary>
        internal void VerifyQueryParameters()
        {
            if (this.host is HttpContextServiceHost)
            {
                ((HttpContextServiceHost)this.host).VerifyQueryParameters();
            }
        }
 
        /// <summary>Checks whether the given object instance is of type T.</summary>
        /// <param name="instance">object instance.</param>
        /// <typeparam name="T">type which we need to cast the given instance to.</typeparam>
        /// <returns>Returns strongly typed instance of T, if the given object is an instance of type T.</returns>
        /// <exception cref="InvalidOperationException">If the given object is not of Type T.</exception>
        private static T ValidateAndCast<T>(object instance) where T : class
        {
            T instanceAsT = instance as T;
            if (instanceAsT == null)
            {
                throw new InvalidOperationException(Strings.DataServiceException_GeneralError);
            }
 
            return instanceAsT;
        }
 
        #endregion Methods.
    }
}