// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Diagnostics; using System.Text; using Microsoft.AspNet.Http; using Microsoft.AspNet.Routing; namespace Microsoft.AspNet.Mvc.Routing { /// /// An implementation of that contains methods to /// build URLs for ASP.NET MVC within an application. /// public class UrlHelper : IUrlHelper { /// /// Initializes a new instance of the class using the specified action context and /// action selector. /// /// The for the current request. public UrlHelper(ActionContext actionContext) { if (actionContext == null) { throw new ArgumentNullException(nameof(actionContext)); } ActionContext = actionContext; } /// public ActionContext ActionContext { get; } protected RouteValueDictionary AmbientValues => ActionContext.RouteData.Values; protected HttpContext HttpContext => ActionContext.HttpContext; protected IRouter Router => ActionContext.RouteData.Routers[0]; /// public virtual string Action(UrlActionContext actionContext) { if (actionContext == null) { throw new ArgumentNullException(nameof(actionContext)); } var valuesDictionary = new RouteValueDictionary(actionContext.Values); if (actionContext.Action == null) { object action; if (!valuesDictionary.ContainsKey("action") && AmbientValues.TryGetValue("action", out action)) { valuesDictionary["action"] = action; } } else { valuesDictionary["action"] = actionContext.Action; } if (actionContext.Controller == null) { object controller; if (!valuesDictionary.ContainsKey("controller") && AmbientValues.TryGetValue("controller", out controller)) { valuesDictionary["controller"] = controller; } } else { valuesDictionary["controller"] = actionContext.Controller; } var virtualPathData = GetVirtualPathData(routeName: null, values: valuesDictionary); return GenerateUrl(actionContext.Protocol, actionContext.Host, virtualPathData, actionContext.Fragment); } /// public virtual bool IsLocalUrl(string url) { return !string.IsNullOrEmpty(url) && // Allows "/" or "/foo" but not "//" or "/\". ((url[0] == '/' && (url.Length == 1 || (url[1] != '/' && url[1] != '\\'))) || // Allows "~/" or "~/foo". (url.Length > 1 && url[0] == '~' && url[1] == '/')); } /// public virtual string RouteUrl(UrlRouteContext routeContext) { if (routeContext == null) { throw new ArgumentNullException(nameof(routeContext)); } var valuesDictionary = new RouteValueDictionary(routeContext.Values); var virtualPathData = GetVirtualPathData(routeContext.RouteName, valuesDictionary); return GenerateUrl(routeContext.Protocol, routeContext.Host, virtualPathData, routeContext.Fragment); } /// /// Gets the for the specified route values by using the specified route name. /// /// The name of the route that is used to generate the . /// /// A dictionary that contains the parameters for a route. /// The . protected virtual VirtualPathData GetVirtualPathData(string routeName, RouteValueDictionary values) { var context = new VirtualPathContext(HttpContext, AmbientValues, values, routeName); return Router.GetVirtualPath(context); } // Internal for unit testing. internal void AppendPathAndFragment(StringBuilder builder, VirtualPathData pathData, string fragment) { var pathBase = HttpContext.Request.PathBase; if (!pathBase.HasValue) { if (pathData.VirtualPath.Length == 0) { builder.Append("/"); } else { if (!pathData.VirtualPath.StartsWith("/", StringComparison.Ordinal)) { builder.Append("/"); } builder.Append(pathData.VirtualPath); } } else { if (pathData.VirtualPath.Length == 0) { builder.Append(pathBase.Value); } else { builder.Append(pathBase.Value); if (pathBase.Value.EndsWith("/", StringComparison.Ordinal)) { builder.Length--; } if (!pathData.VirtualPath.StartsWith("/", StringComparison.Ordinal)) { builder.Append("/"); } builder.Append(pathData.VirtualPath); } } if (!string.IsNullOrEmpty(fragment)) { builder.Append("#").Append(fragment); } } /// public virtual string Content(string contentPath) { if (string.IsNullOrEmpty(contentPath)) { return null; } else if (contentPath[0] == '~') { var segment = new PathString(contentPath.Substring(1)); var applicationPath = HttpContext.Request.PathBase; return applicationPath.Add(segment).Value; } return contentPath; } /// public virtual string Link(string routeName, object values) { return RouteUrl(new UrlRouteContext() { RouteName = routeName, Values = values, Protocol = HttpContext.Request.Scheme, Host = HttpContext.Request.Host.ToUriComponent() }); } /// /// Generates the URL using the specified components. /// /// The protocol. /// The host. /// The . /// The URL fragment. /// The generated URL. protected virtual string GenerateUrl(string protocol, string host, VirtualPathData pathData, string fragment) { if (pathData == null) { return null; } // VirtualPathData.VirtualPath returns string.Empty instead of null. Debug.Assert(pathData.VirtualPath != null); var builder = new StringBuilder(); if (string.IsNullOrEmpty(protocol) && string.IsNullOrEmpty(host)) { AppendPathAndFragment(builder, pathData, fragment); // We're returning a partial URL (just path + query + fragment), but we still want it to be rooted. if (builder.Length == 0 || builder[0] != '/') { builder.Insert(0, '/'); } } else { protocol = string.IsNullOrEmpty(protocol) ? "http" : protocol; builder.Append(protocol); builder.Append("://"); host = string.IsNullOrEmpty(host) ? HttpContext.Request.Host.Value : host; builder.Append(host); AppendPathAndFragment(builder, pathData, fragment); } return builder.ToString(); } } }