diff --git a/src/Microsoft.AspNet.Http/FragmentString.cs b/src/Microsoft.AspNet.Http/FragmentString.cs
new file mode 100644
index 0000000000..1d725e742c
--- /dev/null
+++ b/src/Microsoft.AspNet.Http/FragmentString.cs
@@ -0,0 +1,136 @@
+// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace Microsoft.AspNet.Http
+{
+ ///
+ /// Provides correct handling for FragmentString value when needed to generate a URI string
+ ///
+ public struct FragmentString : IEquatable
+ {
+ ///
+ /// Represents the empty fragment string. This field is read-only.
+ ///
+ public static readonly FragmentString Empty = new FragmentString(string.Empty);
+
+ private readonly string _value;
+
+ ///
+ /// Initialize the fragment string with a given value. This value must be in escaped and delimited format with
+ /// a leading '#' character.
+ ///
+ /// The fragment string to be assigned to the Value property.
+ public FragmentString(string value)
+ {
+ if (!string.IsNullOrEmpty(value) && value[0] != '#')
+ {
+ throw new ArgumentException("The leading '#' must be included for a non-empty fragment.", "value");
+ }
+ _value = value;
+ }
+
+ ///
+ /// The escaped fragment string with the leading '#' character
+ ///
+ public string Value
+ {
+ get { return _value; }
+ }
+
+ ///
+ /// True if the fragment string is not empty
+ ///
+ public bool HasValue
+ {
+ get { return !string.IsNullOrEmpty(_value); }
+ }
+
+ ///
+ /// Provides the fragment string escaped in a way which is correct for combining into the URI representation.
+ /// A leading '#' character will be included unless the Value is null or empty. Characters which are potentially
+ /// dangerous are escaped.
+ ///
+ /// The fragment string value
+ public override string ToString()
+ {
+ return ToUriComponent();
+ }
+
+ ///
+ /// Provides the fragment string escaped in a way which is correct for combining into the URI representation.
+ /// A leading '#' character will be included unless the Value is null or empty. Characters which are potentially
+ /// dangerous are escaped.
+ ///
+ /// The fragment string value
+ public string ToUriComponent()
+ {
+ // Escape things properly so System.Uri doesn't mis-interpret the data.
+ return HasValue ? _value : string.Empty;
+ }
+
+ ///
+ /// Returns an FragmentString given the fragment as it is escaped in the URI format. The string MUST NOT contain any
+ /// value that is not a fragment.
+ ///
+ /// The escaped fragment as it appears in the URI format.
+ /// The resulting FragmentString
+ public static FragmentString FromUriComponent(string uriComponent)
+ {
+ if (String.IsNullOrEmpty(uriComponent))
+ {
+ return Empty;
+ }
+ return new FragmentString(uriComponent);
+ }
+
+ ///
+ /// Returns an FragmentString given the fragment as from a Uri object. Relative Uri objects are not supported.
+ ///
+ /// The Uri object
+ /// The resulting FragmentString
+ public static FragmentString FromUriComponent(Uri uri)
+ {
+ if (uri == null)
+ {
+ throw new ArgumentNullException("uri");
+ }
+ string fragmentValue = uri.GetComponents(UriComponents.Fragment, UriFormat.UriEscaped);
+ if (!string.IsNullOrEmpty(fragmentValue))
+ {
+ fragmentValue = "#" + fragmentValue;
+ }
+ return new FragmentString(fragmentValue);
+ }
+
+ public bool Equals(FragmentString other)
+ {
+ return string.Equals(_value, other._value);
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj))
+ {
+ return false;
+ }
+ return obj is FragmentString && Equals((FragmentString)obj);
+ }
+
+ public override int GetHashCode()
+ {
+ return (_value != null ? _value.GetHashCode() : 0);
+ }
+
+ public static bool operator ==(FragmentString left, FragmentString right)
+ {
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(FragmentString left, FragmentString right)
+ {
+ return !left.Equals(right);
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.Http/HostString.cs b/src/Microsoft.AspNet.Http/HostString.cs
index 67af893b19..ae72f819cc 100644
--- a/src/Microsoft.AspNet.Http/HostString.cs
+++ b/src/Microsoft.AspNet.Http/HostString.cs
@@ -33,6 +33,11 @@ namespace Microsoft.AspNet.Http
get { return _value; }
}
+ public bool HasValue
+ {
+ get { return !string.IsNullOrEmpty(_value); }
+ }
+
///
/// Returns the value as normalized by ToUriComponent().
///
diff --git a/src/Microsoft.AspNet.WebUtilities/UriHelper.cs b/src/Microsoft.AspNet.WebUtilities/UriHelper.cs
new file mode 100644
index 0000000000..491c599d20
--- /dev/null
+++ b/src/Microsoft.AspNet.WebUtilities/UriHelper.cs
@@ -0,0 +1,102 @@
+using System;
+using Microsoft.AspNet.Http;
+
+namespace Microsoft.AspNet.WebUtilities
+{
+ ///
+ /// A helper class for constructing encoded Uris for use in headers and other Uris.
+ ///
+ public class UriHelper
+ {
+ public UriHelper()
+ {
+ }
+
+ public UriHelper(HttpRequest request)
+ {
+ Scheme = request.Scheme;
+ Host = request.Host;
+ PathBase = request.PathBase;
+ Path = request.Path;
+ Query = request.QueryString;
+ // Fragment is not a valid request field.
+ }
+
+ public UriHelper(Uri uri)
+ {
+ Scheme = uri.Scheme;
+ Host = HostString.FromUriComponent(uri);
+ // Assume nothing is being put in PathBase
+ Path = PathString.FromUriComponent(uri);
+ Query = QueryString.FromUriComponent(uri);
+ Fragment = FragmentString.FromUriComponent(uri);
+ }
+
+ public string Scheme { get; set; }
+
+ public HostString Host { get; set; }
+
+ public PathString PathBase { get; set; }
+
+ public PathString Path { get; set; }
+
+ public QueryString Query { get; set; }
+
+ public FragmentString Fragment { get; set; }
+
+ // Always returns at least '/'
+ public string GetPartialUri()
+ {
+ string path = (PathBase.HasValue || Path.HasValue) ? (PathBase + Path).ToString() : "/";
+ return path + Query + Fragment;
+ }
+
+ // Always returns at least 'scheme://host/'
+ public string GetFullUri()
+ {
+ if (string.IsNullOrEmpty(Scheme))
+ {
+ throw new InvalidOperationException("Missing Scheme");
+ }
+ if (!Host.HasValue)
+ {
+ throw new InvalidOperationException("Missing Host");
+ }
+
+ string path = (PathBase.HasValue || Path.HasValue) ? (PathBase + Path).ToString() : "/";
+ return Scheme + "://" + Host + path + Query + Fragment;
+ }
+
+ public static string Create(PathString pathBase,
+ PathString path = new PathString(),
+ QueryString query = new QueryString(),
+ FragmentString fragment = new FragmentString())
+ {
+ return new UriHelper()
+ {
+ PathBase = pathBase,
+ Path = path,
+ Query = query,
+ Fragment = fragment
+ }.GetPartialUri();
+ }
+
+ public static string Create(string scheme,
+ HostString host,
+ PathString pathBase = new PathString(),
+ PathString path = new PathString(),
+ QueryString query = new QueryString(),
+ FragmentString fragment = new FragmentString())
+ {
+ return new UriHelper()
+ {
+ Scheme = scheme,
+ Host = host,
+ PathBase = pathBase,
+ Path = path,
+ Query = query,
+ Fragment = fragment
+ }.GetFullUri();
+ }
+ }
+}
\ No newline at end of file