using System; using System.Collections.Generic; using System.Text; using System.Web; using System.Text.RegularExpressions; namespace Framework { /// /// The UrlGen class is designed to assist in everyday URL generation activities, typically /// those that involve building fully-qualified URLs or relative URLs that may include /// many parameters. It provides additional flexibility such as being able to build URLs /// from scratch or generate them from an existing URI object. /// public class UrlGen { private string scheme; private string host; private string basePage; private int port; private Dictionary parameters; private string fragment; private const int defaultPort = 80; private static readonly Regex urlRegex = new Regex( @"(/?(?[^?#]*))?" + @"(\?(?[^#]*))?" + @"(#(?.*))?", RegexOptions.ExplicitCapture | RegexOptions.Compiled | RegexOptions.IgnoreCase); public UrlGen() { scheme = string.Empty; port = defaultPort; host = string.Empty; basePage = string.Empty; parameters = new Dictionary(); } /// /// Builds a URL using a string as a starting point. The string must be a valid fully-qualified /// URL or a valid relative URL. Parameters are also allowed, including anchor links. /// /// A valid full-qualified URL ( e.g. "http://www.shoplocal.com/default.aspx") or /// a valid relative URL (e.g. "default.aspx?param1=value1") /// If True, signals to the constructor that it should parse the string as a relative /// URL. If False, then it will treat the string as a full-qualified URL. public UrlGen(string fromString, bool isRelative) { if (isRelative) { SetPropertiesFromString(fromString); } else { // Utilize the URI class to help us SetPropertiesFromURI(new Uri(fromString)); } } private void SetPropertiesFromString(string fromString) { // Let's do our damndest to extract the good stuff from a relative URL Match urlRegexMatches = urlRegex.Match(fromString); if (urlRegexMatches.Success) { scheme = string.Empty; port = defaultPort; host = string.Empty; basePage = urlRegexMatches.Groups["path"].ToString(); fragment = urlRegexMatches.Groups["fragment"].ToString(); CreateParametersFromQueryString(urlRegexMatches.Groups["query"].ToString()); } else { // Just not a valid URL. throw new ArgumentException(String.Format("Format of the URL passed is not valid: {0}", fromString)); } } private void SetPropertiesFromRelativeURL(string fromString) { int queryStringStart = fromString.IndexOf('?'); if (queryStringStart == -1) { basePage = fromString; } else { basePage = fromString.Substring(0, queryStringStart); string[] splitUrl = fromString.Split('?'); string queryString = splitUrl[1]; CreateParametersFromQueryString(queryString); } } /// /// Builds a URL using a URI as a starting point. /// /// URI to use as a basis for a new generated URL. public UrlGen(Uri fromUri) { SetPropertiesFromURI(fromUri); } private void SetPropertiesFromURI(Uri fromUri) { scheme = fromUri.Scheme; port = fromUri.Port == -1 ? defaultPort : fromUri.Port; host = fromUri.Host; basePage = fromUri.AbsolutePath; fragment = fromUri.Fragment; CreateParametersFromQueryString(fromUri.Query); } private void CreateParametersFromQueryString(string queryString) { // Parse the query portion of the URI parameters = new Dictionary(); if (!string.IsNullOrEmpty(queryString)) { string[] query = queryString.Replace("?", string.Empty).Split('&'); foreach (string fullParam in query) { string[] fullParamSpliced = fullParam.Split('='); string name = HttpUtility.UrlDecode(fullParamSpliced[0], Encoding.ASCII); string value = HttpUtility.UrlDecode(fullParamSpliced[1], Encoding.ASCII); parameters.Add(name, value); } } } /// /// Overridden ToString method spits out the URL, relative or fully-qualified, /// depending on the existence of the host parameter. Parameters are included and are /// URL-encoded, if they exist. /// /// A relative or fully-qualified URL with URL-encoded parameters public override string ToString() { StringBuilder url = new StringBuilder(); if (!string.IsNullOrEmpty(host)) { if (string.IsNullOrEmpty(scheme)) { url.Append("http"); } else { url.Append(scheme); } url.Append("://"); url.Append(host); // Append port only if different from universal default if (port != defaultPort) { url.Append(":" + port); } if (!string.IsNullOrEmpty(basePage)) { // Prepend slash to basepage only if it does not include one already if (!basePage.StartsWith("/")) { url.Append("/"); } } else { url.Append("/"); } } BuildRelativeURLSegment(url); return url.ToString(); } private void BuildRelativeURLSegment(StringBuilder url) { url.Append(basePage); BuildQueryString(url); if (!string.IsNullOrEmpty(fragment)) { url.Append("#" + fragment); } } /// /// Regardless of the properties of the URL, return a relative URL (e.g. mypage.aspx?param1=value1) /// This function ignores values like the scheme, port, and host. /// /// A relative URL public string ToRelativeString() { StringBuilder url = new StringBuilder(); BuildRelativeURLSegment(url); return url.ToString(); } private void BuildQueryString(StringBuilder url) { if (parameters != null) { bool firstParam = true; foreach (KeyValuePair param in parameters) { if (firstParam) { url.Append("?"); firstParam = false; } else { url.Append("&"); } url.Append(HttpUtility.UrlEncode(param.Key, Encoding.ASCII).Replace("+", "%20")); url.Append("="); url.Append(HttpUtility.UrlEncode(param.Value, Encoding.ASCII).Replace("+", "%20")); } } } /// /// Represents the URL fragment, if present. For example, http://www.shoplocal.com/default.asp#pagefragment /// "pageframent" is the fragment in the above URL. /// public string Fragment { get { return this.fragment; } set { this.fragment = value; } } /// /// Returns a string representing the query string as they exist in the Parameters /// collection. The query string parameters and values are properly URL encoded. /// public string QueryString { get { StringBuilder queryString = new StringBuilder(); BuildRelativeURLSegment(queryString); return queryString.ToString(); } } /// /// Represents the protocol scheme used (e.g . http, https, ftp, et al) /// public string Scheme { get { return this.scheme; } set { this.scheme = value; } } /// /// Represents the port number used for the URL. Usually this is the default of /// 80 /// public int Port { get { return this.port; } set { this.port = value; } } /// /// Represents the fully-qualified hostname, if used (e.g. www.shoplocal.com) /// public string Host { get { return this.host; } set { this.host = value; } } /// /// Represents the base page of the URL, for example: http://www.shoplocal.com/folder/default.aspx /// In this case, "/folder/default.aspx" is considered the base page. /// public string BasePage { get { return this.basePage; } set { this.basePage = value; } } /// /// A typed dictionary of parameters. These parameters are not url-encoded until they /// are used in ToString(). Therefore, do not put URL-encoded parameters or values in the /// collection or they will be URL-encoded twice. /// public Dictionary Parameters { get { return this.parameters; } set { this.parameters = value; } } } }