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; }
}
}
}