using System; using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Linq; using Microsoft.AspNet.Abstractions; using Microsoft.AspNet.DependencyInjection; using Microsoft.AspNet.Routing; namespace Microsoft.AspNet.Mvc { public class UrlHelper : IUrlHelper { private readonly HttpContext _httpContext; private readonly IRouter _router; private readonly IDictionary _ambientValues; private readonly IActionSelector _actionSelector; public UrlHelper(IContextAccessor contextAccessor, IActionSelector actionSelector) { _httpContext = contextAccessor.Value.HttpContext; _router = contextAccessor.Value.Router; _ambientValues = contextAccessor.Value.RouteValues; _actionSelector = actionSelector; } public string Action(string action, string controller, object values, string protocol, string host, string fragment) { var valuesDictionary = TypeHelper.ObjectToDictionary(values); if (action != null) { valuesDictionary["action"] = action; } if (controller != null) { valuesDictionary["controller"] = controller; } var context = new VirtualPathContext(_httpContext, _ambientValues, valuesDictionary); var actions = _actionSelector.GetCandidateActions(context); var actionCandidate = actions.FirstOrDefault(); if (actionCandidate == null) { return null; } foreach (var constraint in actionCandidate.RouteConstraints) { if (constraint.KeyHandling == RouteKeyHandling.DenyKey && _ambientValues.ContainsKey(constraint.RouteKey)) { valuesDictionary[constraint.RouteKey] = null; } } var path = GeneratePathFromRoute(valuesDictionary); if (path == null) { return null; } return GenerateUrl(protocol, host, path, fragment); } public 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 string RouteUrl(string routeName, object values, string protocol, string host, string fragment) { var valuesDictionary = TypeHelper.ObjectToDictionary(values); var path = GeneratePathFromRoute(routeName, valuesDictionary); if (path == null) { return null; } return GenerateUrl(protocol, host, path, fragment); } private string GeneratePathFromRoute(IDictionary values) { return GeneratePathFromRoute(routeName: null, values: values); } private string GeneratePathFromRoute(string routeName, IDictionary values) { var context = new VirtualPathContext(_httpContext, _ambientValues, values, routeName); var path = _router.GetVirtualPath(context); if (path == null) { return null; } // See Routing Issue#31 if (path.Length > 0 && path[0] != '/') { path = "/" + path; } var fullPath = _httpContext.Request.PathBase.Add(new PathString(path)).Value; if (fullPath.Length == 0) { return "/"; } else { return fullPath; } } public string Content([NotNull] string contentPath) { return GenerateClientUrl(_httpContext.Request.PathBase, contentPath); } private static string GenerateClientUrl([NotNull] PathString applicationPath, [NotNull] string path) { if (path.StartsWith("~/", StringComparison.Ordinal)) { var segment = new PathString(path.Substring(1)); return applicationPath.Add(segment).Value; } return path; } private string GenerateUrl(string protocol, string host, string path, string fragment) { // We should have a robust and centrallized version of this code. See HttpAbstractions#28 Contract.Assert(path != null); var url = path; if (!string.IsNullOrEmpty(fragment)) { url += "#" + fragment; } if (string.IsNullOrEmpty(protocol) && string.IsNullOrEmpty(host)) { // We're returning a partial url (just path + query + fragment), but we still want it // to be rooted. if (!url.StartsWith("/", StringComparison.Ordinal)) { url = "/" + url; } return url; } else { protocol = string.IsNullOrEmpty(protocol) ? "http" : protocol; host = string.IsNullOrEmpty(host) ? _httpContext.Request.Host.Value : host; url = protocol + "://" + host + url; return url; } } } }