diff --git a/samples/MvcSample/LinkController.cs b/samples/MvcSample/LinkController.cs new file mode 100644 index 0000000000..159985f2ae --- /dev/null +++ b/samples/MvcSample/LinkController.cs @@ -0,0 +1,23 @@ + +using Microsoft.AspNet.Mvc; + +namespace MvcSample +{ + public class LinkController : Controller + { + public IActionResult Details() + { + return View(); + } + + public string About() + { + return Url.Action(null); + } + + public string Get() + { + return Url.Route(new { controller = "Home", action = "Details" }); + } + } +} diff --git a/samples/MvcSample/Startup.cs b/samples/MvcSample/Startup.cs index 780d0e89ed..1b18767b33 100644 --- a/samples/MvcSample/Startup.cs +++ b/samples/MvcSample/Startup.cs @@ -49,6 +49,7 @@ namespace MvcSample new { controller = "Home" }); builder.UseRouter(routes); + } } } diff --git a/samples/MvcSample/Views/Link/Details.cshtml b/samples/MvcSample/Views/Link/Details.cshtml new file mode 100644 index 0000000000..f627dfdcba --- /dev/null +++ b/samples/MvcSample/Views/Link/Details.cshtml @@ -0,0 +1,3 @@ + + +@Url.Action("About") diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionContext.cs b/src/Microsoft.AspNet.Mvc.Core/ActionContext.cs index 1f9979c941..642c39464e 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ActionContext.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ActionContext.cs @@ -1,19 +1,23 @@ using System.Collections.Generic; using Microsoft.AspNet.Abstractions; +using Microsoft.AspNet.Routing; namespace Microsoft.AspNet.Mvc { public class ActionContext { - public ActionContext(HttpContext httpContext, IDictionary routeValues, ActionDescriptor actionDescriptor) + public ActionContext(HttpContext httpContext, IRouter router, IDictionary routeValues, ActionDescriptor actionDescriptor) { HttpContext = httpContext; + Router = router; RouteValues = routeValues; ActionDescriptor = actionDescriptor; } public HttpContext HttpContext { get; private set; } + public IRouter Router { get; private set; } + public IDictionary RouteValues { get; private set; } public ActionDescriptor ActionDescriptor { get; private set; } diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/ViewResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/ViewResult.cs index 6a20f28b49..c333ce7771 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ActionResults/ViewResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/ViewResult.cs @@ -48,7 +48,7 @@ namespace Microsoft.AspNet.Mvc { string locationsText = String.Join(Environment.NewLine, result.SearchedLocations); const string message = @"The view '{0}' was not found. The following locations were searched:{1}."; - throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, message, viewName, locationsText)); + throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, message, viewName, locationsText)); } return result.View; diff --git a/src/Microsoft.AspNet.Mvc.Core/Controller.cs b/src/Microsoft.AspNet.Mvc.Core/Controller.cs index f16aa5fb66..0892b23fa6 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Controller.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Controller.cs @@ -15,6 +15,8 @@ namespace Microsoft.AspNet.Mvc public HttpContext Context { get; set; } + public IRenderUrl Url { get; set; } + public ViewData ViewData { get; set; } public dynamic ViewBag diff --git a/src/Microsoft.AspNet.Mvc.Core/DefaultActionSelector.cs b/src/Microsoft.AspNet.Mvc.Core/DefaultActionSelector.cs index a04de05dfb..a04875e9f7 100644 --- a/src/Microsoft.AspNet.Mvc.Core/DefaultActionSelector.cs +++ b/src/Microsoft.AspNet.Mvc.Core/DefaultActionSelector.cs @@ -67,7 +67,7 @@ namespace Microsoft.AspNet.Mvc { Action = action, }; - var actionContext = new ActionContext(context.HttpContext, context.RouteValues, action); + var actionContext = new ActionContext(context.HttpContext, null, context.RouteValues, action); var actionBindingContext = await _bindingProvider.GetActionBindingContextAsync(actionContext); foreach (var parameter in action.Parameters.Where(p => p.ParameterBindingInfo != null)) diff --git a/src/Microsoft.AspNet.Mvc.Core/DefaultControllerFactory.cs b/src/Microsoft.AspNet.Mvc.Core/DefaultControllerFactory.cs index 9664d45b14..25399476fc 100644 --- a/src/Microsoft.AspNet.Mvc.Core/DefaultControllerFactory.cs +++ b/src/Microsoft.AspNet.Mvc.Core/DefaultControllerFactory.cs @@ -4,6 +4,7 @@ using System.Reflection; using Microsoft.AspNet.Abstractions; using Microsoft.AspNet.DependencyInjection; using Microsoft.AspNet.Mvc.ModelBinding; +using Microsoft.AspNet.Routing; namespace Microsoft.AspNet.Mvc { @@ -58,6 +59,15 @@ namespace Microsoft.AspNet.Mvc { prop.SetValue(controller, modelState); } + else if (prop.Name == "Url" && prop.PropertyType == typeof(IRenderUrl)) + { + var generator = new DefaultRenderUrl( + actionContext.HttpContext, + actionContext.Router, + actionContext.RouteValues); + + prop.SetValue(controller, generator); + } } var method = controllerType.GetRuntimeMethods().FirstOrDefault(m => m.Name.Equals("Initialize", StringComparison.OrdinalIgnoreCase)); @@ -72,6 +82,5 @@ namespace Microsoft.AspNet.Mvc method.Invoke(controller, args); } - } } diff --git a/src/Microsoft.AspNet.Mvc.Core/DefaultRenderUrl.cs b/src/Microsoft.AspNet.Mvc.Core/DefaultRenderUrl.cs new file mode 100644 index 0000000000..bcd2289770 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/DefaultRenderUrl.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using Microsoft.AspNet.Abstractions; +using Microsoft.AspNet.Routing; + +namespace Microsoft.AspNet.Mvc +{ + public class DefaultRenderUrl : IRenderUrl + { + private readonly HttpContext _httpContext; + private readonly IRouter _router; + private readonly IDictionary _ambientValues; + + public DefaultRenderUrl(HttpContext httpContext, IRouter router, IDictionary ambientValues) + { + _httpContext = httpContext; + _router = router; + _ambientValues = ambientValues; + } + + public virtual string Action(string action, string controller, object values) + { + var valuesDictionary = new RouteValueDictionary(values); + + if (action != null) + { + valuesDictionary["action"] = action; + } + + if (controller != null) + { + valuesDictionary["controller"] = controller; + } + + return RouteCore(valuesDictionary); + } + + public virtual string Route(object values) + { + return RouteCore(new RouteValueDictionary(values)); + + } + + protected virtual string RouteCore(IDictionary values) + { + var context = new VirtualPathContext(_httpContext, _ambientValues, values); + var path = _router.GetVirtualPath(context); + + // We need to add the host part in here, currently blocked on http abstractions support. + // The intent is to use full URLs by default. + return _httpContext.Request.PathBase + path; + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Core/MvcApplication.cs b/src/Microsoft.AspNet.Mvc.Core/MvcApplication.cs index c49a752971..49bfde2cc3 100644 --- a/src/Microsoft.AspNet.Mvc.Core/MvcApplication.cs +++ b/src/Microsoft.AspNet.Mvc.Core/MvcApplication.cs @@ -62,7 +62,7 @@ namespace Microsoft.AspNet.Mvc return; } - var actionContext = new ActionContext(context.HttpContext, context.Values, actionDescriptor); + var actionContext = new ActionContext(context.HttpContext, context.Router, context.Values, actionDescriptor); var invoker = ActionInvokerFactory.CreateInvoker(actionContext); if (invoker == null) { diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs index 2b7e8c6847..7d82f18247 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs @@ -5,7 +5,6 @@ using System.Text; using System.Threading.Tasks; using Microsoft.AspNet.Abstractions; using Microsoft.AspNet.DependencyInjection; -using Microsoft.AspNet.Mvc.ModelBinding; namespace Microsoft.AspNet.Mvc.Razor { @@ -17,10 +16,14 @@ namespace Microsoft.AspNet.Mvc.Razor protected TextWriter Output { get; set; } + public IRenderUrl Url { get; set; } + private string BodyContent { get; set; } public virtual async Task RenderAsync(ViewContext context, TextWriter writer) { + Url = context.RenderUrl; + var contentBuilder = new StringBuilder(1024); using (var bodyWriter = new StringWriter(contentBuilder)) { diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorViewOfT.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorViewOfT.cs index 767f1bf412..23560971e5 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/RazorViewOfT.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/RazorViewOfT.cs @@ -1,6 +1,5 @@ using System.IO; using System.Threading.Tasks; -using Microsoft.AspNet.Mvc.ModelBinding; namespace Microsoft.AspNet.Mvc.Razor { @@ -17,6 +16,8 @@ namespace Microsoft.AspNet.Mvc.Razor public HtmlHelper Html { get; set; } + public IRenderUrl RenderUrl { get; set; } + public override Task RenderAsync(ViewContext context, TextWriter writer) { var viewData = context.ViewData as ViewData; diff --git a/src/Microsoft.AspNet.Mvc.Rendering/IRenderUrl.cs b/src/Microsoft.AspNet.Mvc.Rendering/IRenderUrl.cs new file mode 100644 index 0000000000..1783491fa8 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Rendering/IRenderUrl.cs @@ -0,0 +1,12 @@ + +using System.Collections.Generic; + +namespace Microsoft.AspNet.Mvc +{ + public interface IRenderUrl + { + string Action(string action, string controller, object values); + + string Route(object values); + } +} diff --git a/src/Microsoft.AspNet.Mvc.Rendering/RenderUrlExtensions.cs b/src/Microsoft.AspNet.Mvc.Rendering/RenderUrlExtensions.cs new file mode 100644 index 0000000000..3c2f7f5185 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Rendering/RenderUrlExtensions.cs @@ -0,0 +1,26 @@ + +namespace Microsoft.AspNet.Mvc +{ + public static class RenderUrlExtension + { + public static string Action(this IRenderUrl generator) + { + return generator.Action(null, null, null); + } + + public static string Action(this IRenderUrl generator, string action) + { + return generator.Action(action, null, null); + } + + public static string Action(this IRenderUrl generator, string action, object values) + { + return generator.Action(action, null, values); + } + + public static string Action(this IRenderUrl generator, string action, string controller) + { + return generator.Action(action, controller, null); + } + } +}