Click here to Skip to main content
15,894,646 members
Articles / Web Development / CSS

Building a 3-Tier App with Silverlight 3, .NET RIA Services, and Azure Table Storage

Rate me:
Please Sign up or sign in to vote.
4.89/5 (28 votes)
11 Jul 2009CDDL19 min read 165.5K   1.7K   148  
This article presents the techniques and caveats of building a 3-tire Azure hosted application using Silverlight 3 (presentation tier), .NET RIA services (business logic and data access), and Windows Azure Table (data storage).
//
// <copyright file="RestHelpers.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Net;
using System.Diagnostics;
using System.Globalization;
using System.Collections.Specialized;
using System.Web;
using System.Xml;
using System.Text.RegularExpressions;

namespace Microsoft.Azure.StorageClient
{
    namespace StorageHttpConstants
    {

        internal static class ConstChars
        {
            internal const string Linefeed = "\n";
            internal const string CarriageReturnLinefeed = "\r\n";
            internal const string Colon = ":";
            internal const string Comma = ",";
            internal const string Slash = "/";
            internal const string BackwardSlash = @"\";
            internal const string Space = " ";
            internal const string Ampersand = "&";
            internal const string QuestionMark = "?";
            internal const string Equal = "=";
            internal const string Bang = "!";
            internal const string Star = "*";
            internal const string Dot = ".";
        }

        internal static class RequestParams
        {
            internal const string NumOfMessages = "numofmessages";
            internal const string VisibilityTimeout = "visibilitytimeout";
            internal const string PeekOnly = "peekonly";
            internal const string MessageTtl = "messagettl";
            internal const string Messages = "messages";
            internal const string PopReceipt = "popreceipt";
        }

        internal static class QueryParams
        {
            internal const string SeparatorForParameterAndValue = "=";
            internal const string QueryParamTimeout = "timeout";
            internal const string QueryParamComp = "comp";

            // Other query string parameter names
            internal const string QueryParamBlockId = "blockid";
            internal const string QueryParamPrefix = "prefix";
            internal const string QueryParamMarker = "marker";
            internal const string QueryParamMaxResults = "maxresults";
            internal const string QueryParamDelimiter = "delimiter";
            internal const string QueryParamModifiedSince = "modifiedsince";
        }

        internal static class CompConstants
        {
            internal const string Metadata = "metadata";
            internal const string List = "list";
            internal const string BlobList = "bloblist";
            internal const string BlockList = "blocklist";
            internal const string Block = "block";
            internal const string Acl = "acl";
        }

        internal static class XmlElementNames
        {
            internal const string BlockList = "BlockList";
            internal const string Block = "Block";
            internal const string EnumerationResults = "EnumerationResults";
            internal const string Prefix = "Prefix";
            internal const string Marker = "Marker";
            internal const string MaxResults = "MaxResults";
            internal const string Delimiter = "Delimiter";
            internal const string NextMarker = "NextMarker";
            internal const string Containers = "Containers";
            internal const string Container = "Container";
            internal const string ContainerName = "Name";
            internal const string ContainerNameAttribute = "ContainerName";
            internal const string AccountNameAttribute = "AccountName";
            internal const string LastModified = "LastModified";
            internal const string Etag = "Etag";
            internal const string Url = "Url";
            internal const string CommonPrefixes = "CommonPrefixes";
            internal const string ContentType = "ContentType";
            internal const string ContentEncoding = "ContentEncoding";
            internal const string ContentLanguage = "ContentLanguage";
            internal const string Size = "Size";
            internal const string Blobs = "Blobs";
            internal const string Blob = "Blob";
            internal const string BlobName = "Name";
            internal const string BlobPrefix = "BlobPrefix";
            internal const string BlobPrefixName = "Name";
            internal const string Name = "Name";
            internal const string Queues = "Queues";
            internal const string Queue = "Queue";
            internal const string QueueName = "QueueName";
            internal const string QueueMessagesList = "QueueMessagesList";
            internal const string QueueMessage = "QueueMessage";
            internal const string MessageId = "MessageId";
            internal const string PopReceipt = "PopReceipt";
            internal const string InsertionTime = "InsertionTime";
            internal const string ExpirationTime = "ExpirationTime";
            internal const string TimeNextVisible = "TimeNextVisible";
            internal const string MessageText = "MessageText";

            // Error specific constants
            internal const string ErrorRootElement = "Error";
            internal const string ErrorCode = "Code";
            internal const string ErrorMessage = "Message";
            internal const string ErrorException = "ExceptionDetails";
            internal const string ErrorExceptionMessage = "ExceptionMessage";
            internal const string ErrorExceptionStackTrace = "StackTrace";
            internal const string AuthenticationErrorDetail = "AuthenticationErrorDetail";

            //The following are for table error messages
            internal const string DataWebMetadataNamespace = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata";
            internal const string TableErrorCodeElement = "code";
            internal const string TableErrorMessageElement = "message";
        }

        internal static class HeaderNames
        {
            internal const string PrefixForStorageProperties = "x-ms-prop-";
            internal const string PrefixForMetadata = "x-ms-meta-";
            internal const string PrefixForStorageHeader = "x-ms-";
            internal const string PrefixForTableContinuation = "x-ms-continuation-";

            //
            // Standard headers...
            //
            internal const string ContentLanguage = "Content-Language";
            internal const string ContentLength = "Content-Length";
            internal const string ContentType = "Content-Type";
            internal const string ContentEncoding = "Content-Encoding";
            internal const string ContentMD5 = "Content-MD5";
            internal const string ContentRange = "Content-Range";
            internal const string LastModifiedTime = "Last-Modified";
            internal const string Server = "Server";
            internal const string Allow = "Allow";
            internal const string ETag = "ETag";
            internal const string Range = "Range";
            internal const string Date = "Date";
            internal const string Authorization = "Authorization";
            internal const string IfModifiedSince = "If-Modified-Since";
            internal const string IfUnmodifiedSince = "If-Unmodified-Since";
            internal const string IfMatch = "If-Match";
            internal const string IfNoneMatch = "If-None-Match";
            internal const string IfRange = "If-Range";
            internal const string NextPartitionKey = "NextPartitionKey";
            internal const string NextRowKey = "NextRowKey";
            internal const string NextTableName = "NextTableName";

            //
            // Storage specific custom headers...
            //
            internal const string StorageDateTime = PrefixForStorageHeader + "date";
            internal const string PublicAccess = PrefixForStorageProperties + "publicaccess";
            internal const string StorageRange = PrefixForStorageHeader + "range";

            internal const string CreationTime = PrefixForStorageProperties + "creation-time";
            internal const string ForceUpdate = PrefixForStorageHeader + "force-update";            
            internal const string ApproximateMessagesCount = PrefixForStorageHeader + "approximate-messages-count";     
        }

        internal static class HeaderValues
        {
            internal const string ContentTypeXml = "application/xml";

            /// <summary>
            /// This is the default content-type xStore uses when no content type is specified
            /// </summary>
            internal const string DefaultContentType = "application/octet-stream";

            // The Range header value is "bytes=start-end", both start and end can be empty
            internal const string RangeHeaderFormat = "bytes={0}-{1}";

        }

        internal static class AuthenticationSchemeNames
        {
            internal const string SharedKeyAuthSchemeName = "SharedKey";
            internal const string SharedKeyLiteAuthSchemeName = "SharedKeyLite";
        }

        internal static class HttpMethod
        {
            internal const string Get = "GET";
            internal const string Put = "PUT";
            internal const string Post = "POST";
            internal const string Head = "HEAD";
            internal const string Delete = "DELETE";
            internal const string Trace = "TRACE";
            internal const string Options = "OPTIONS";
            internal const string Connect = "CONNECT";
        }

        internal static class BlobBlockConstants
        {
            internal const int KB = 1024;
            internal const int MB = 1024 * KB;
            /// <summary>
            /// When transmitting a blob that is larger than this constant, this library automatically
            /// transmits the blob as individual blocks. I.e., the blob is (1) partitioned
            /// into separate parts (these parts are called blocks) and then (2) each of the blocks is 
            /// transmitted separately.
            /// The maximum size of this constant as supported by the real blob storage service is currently 
            /// 64 MB; the development storage tool currently restricts this value to 2 MB.
            /// Setting this constant can have a significant impact on the performance for uploading or
            /// downloading blobs.
            /// As a general guideline: If you run in a reliable environment increase this constant to reduce
            /// the amount of roundtrips. In an unreliable environment keep this constant low to reduce the 
            /// amount of data that needs to be retransmitted in case of connection failures.
            /// </summary>
            internal const long MaximumBlobSizeBeforeTransmittingAsBlocks = 2 * MB;
            /// <summary>
            /// The size of a single block when transmitting a blob that is larger than the 
            /// MaximumBlobSizeBeforeTransmittingAsBlocks constant (see above).
            /// The maximum size of this constant is currently 4 MB; the development storage 
            /// tool currently restricts this value to 1 MB.
            /// Setting this constant can have a significant impact on the performance for uploading or 
            /// downloading blobs.
            /// As a general guideline: If you run in a reliable environment increase this constant to reduce
            /// the amount of roundtrips. In an unreliable environment keep this constant low to reduce the 
            /// amount of data that needs to be retransmitted in case of connection failures.
            /// </summary>
            internal const long BlockSize = 1 * MB;            
        }

        internal static class ListingConstants
        {
            internal const int MaxContainerListResults = 100;
            internal const int MaxBlobListResults = 100;
            internal const int MaxQueueListResults = 50;
            internal const int MaxTableListResults = 50;
        }

        /// <summary>
        /// Contains regular expressions for checking whether container and table names conform
        /// to the rules of the storage REST protocols.
        /// </summary>
        public static class RegularExpressionStrings
        {
            /// <summary>
            /// Container or queue names that match against this regular expression are valid.
            /// </summary>
            public const string ValidContainerNameRegex = @"^([a-z]|\d){1}([a-z]|-|\d){1,61}([a-z]|\d){1}$";

            /// <summary>
            /// Table names that match against this regular expression are valid.
            /// </summary>
            public const string ValidTableNameRegex = @"^([a-z]|[A-Z]){1}([a-z]|[A-Z]|\d){2,62}$";
        }

        internal static class StandardPortalEndpoints
        {
            internal const string BlobStorage = "blob";
            internal const string QueueStorage = "queue";
            internal const string TableStorage = "table";
            internal const string StorageHostSuffix = ".core.windows.net";
            internal const string BlobStorageEndpoint = BlobStorage + StorageHostSuffix;
            internal const string QueueStorageEndpoint = QueueStorage + StorageHostSuffix;
            internal const string TableStorageEndpoint = TableStorage + StorageHostSuffix;
        }
    }

    internal static partial class Utilities
    {
        internal static HttpWebRequest CreateHttpRequest(Uri uri, string httpMethod, TimeSpan timeout)
        {
            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);
            request.Timeout = (int)timeout.TotalMilliseconds;
            request.ReadWriteTimeout = (int)timeout.TotalMilliseconds;
            request.Method = httpMethod;
            request.ContentLength = 0;
            request.Headers.Add(StorageHttpConstants.HeaderNames.StorageDateTime,
                                Utilities.ConvertDateTimeToHttpString(DateTime.UtcNow));
            return request;
        }

        /// <summary>
        /// Converts the date time to a valid string form as per HTTP standards
        /// </summary>
        internal static string ConvertDateTimeToHttpString(DateTime dateTime)
        {
            // On the wire everything should be represented in UTC. This assert will catch invalid callers who
            // are violating this rule.
            Debug.Assert(dateTime == DateTime.MaxValue || dateTime == DateTime.MinValue || dateTime.Kind == DateTimeKind.Utc);

            // 'R' means rfc1123 date which is what our server uses for all dates...
            // It will be in the following format:
            // Sun, 28 Jan 2008 12:11:37 GMT
            return dateTime.ToString("R", CultureInfo.InvariantCulture);
        }

        /// <summary>
        /// Parse a string having the date time information in acceptable formats according to HTTP standards
        /// </summary>
        internal static bool TryGetDateTimeFromHttpString(string dateString, out DateTime? result)
        {
            DateTime dateTime;
            result = null;

            // 'R' means rfc1123 date which is the preferred format used in HTTP
            bool parsed = DateTime.TryParseExact(dateString, "R", null, DateTimeStyles.None, out dateTime);
            if (parsed)
            {
                // For some reason, format string "R" makes the DateTime.Kind as Unspecified while it's actually
                // Utc. Specifying DateTimeStyles.AssumeUniversal also doesn't make the difference. If we also
                // specify AdjustToUniversal it works as expected but we don't really want Parse to adjust 
                // things automatically.
                result = DateTime.SpecifyKind(dateTime, DateTimeKind.Utc);
                return true;
            }

            return false;
        }


        /// <summary>
        /// Copies from one stream to another
        /// </summary>
        /// <param name="sourceStream">The stream to copy from</param>
        /// <param name="destinationStream">The stream to copy to</param>
        internal static long CopyStream(Stream sourceStream, Stream destinationStream)
        {
            const int BufferSize = 0x10000;
            byte[] buffer = new byte[BufferSize];
            int n = 0;
            long totalRead = 0;
            do
            {
                n = sourceStream.Read(buffer, 0, BufferSize);
                if (n > 0)
                {
                    totalRead += n;
                    destinationStream.Write(buffer, 0, n);
                }
                
            } while (n > 0);
            return totalRead;
        }

        internal static void CopyStream(Stream sourceStream, Stream destinationStream, long length)
        {
            const int BufferSize = 0x10000;
            byte[] buffer = new byte[BufferSize];
            int n = 0;
            long amountLeft = length;           

            do
            {
                amountLeft -= n;
                n = sourceStream.Read(buffer, 0, (int)Math.Min(BufferSize, amountLeft));
                if (n > 0)
                {
                    destinationStream.Write(buffer, 0, n);
                }

            } while (n > 0);
        }

        internal static int CopyStreamToBuffer(Stream sourceStream, byte[] buffer, int bytesToRead)
        {
            int n = 0;
            int amountLeft = bytesToRead;
            do
            {
                n = sourceStream.Read(buffer, bytesToRead - amountLeft, amountLeft);
                amountLeft -= n;
            } while (n > 0);
            return bytesToRead - amountLeft;
        }

        internal static Uri CreateRequestUri(
                                Uri baseUri,
                                bool usePathStyleUris,
                                string accountName,
                                string containerName,
                                string blobName,
                                TimeSpan Timeout,
                                NameValueCollection queryParameters,
                                out ResourceUriComponents uriComponents
                                )
        {
            uriComponents = 
                new ResourceUriComponents(accountName, containerName, blobName);
            Uri uri = HttpRequestAccessor.ConstructResourceUri(baseUri, uriComponents, usePathStyleUris);

            if (queryParameters != null)
            {
                UriBuilder builder = new UriBuilder(uri);

                if (queryParameters.Get(StorageHttpConstants.QueryParams.QueryParamTimeout) == null)
                {
                    queryParameters.Add(StorageHttpConstants.QueryParams.QueryParamTimeout,
                    Timeout.TotalSeconds.ToString(CultureInfo.InvariantCulture));
                }

                StringBuilder sb = new StringBuilder();
                bool firstParam = true;
                foreach (string queryKey in queryParameters.AllKeys)
                {
                    if (!firstParam)
                        sb.Append("&");
                    sb.Append(HttpUtility.UrlEncode(queryKey));
                    sb.Append('=');
                    sb.Append(HttpUtility.UrlEncode(queryParameters[queryKey]));
                    firstParam = false;
                }

                if (sb.Length > 0)
                {
                    builder.Query = sb.ToString();
                }
                return builder.Uri;
            }
            else
            {
                return uri;
            }
        }

        internal static bool StringIsIPAddress(string address)
        {
            IPAddress outIPAddress;

            return IPAddress.TryParse(address, out outIPAddress);
        }

        internal static void AddMetadataHeaders(HttpWebRequest request, NameValueCollection metadata)
        {
            foreach (string key in metadata.Keys)
            {
                request.Headers.Add(
                    StorageHttpConstants.HeaderNames.PrefixForMetadata + key,
                    metadata[key]
                    );
            }
        }

        internal static bool IsValidTableName(string name)
        {
            if (string.IsNullOrEmpty(name))
            {
                return false;
            }
            Regex reg = new Regex(StorageHttpConstants.RegularExpressionStrings.ValidTableNameRegex);
            if (reg.IsMatch(name))
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        internal static bool IsValidContainerOrQueueName(string name)
        {
            if (string.IsNullOrEmpty(name))
            {
                return false;
            }
            Regex reg = new Regex(StorageHttpConstants.RegularExpressionStrings.ValidContainerNameRegex);
            if (reg.IsMatch(name))
            {
                return true;
            }
            else
            {
                return false;
            }
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Common Development and Distribution License (CDDL)


Written By
Technical Lead
United States United States
https://github.com/modesty

https://www.linkedin.com/in/modesty-zhang-9a43771

https://twitter.com/modestyqz

Comments and Discussions