[Fixes #926] Protocol & Host name ignored when creating action link
This commit is contained in:
parent
b9d433168e
commit
fdeff1188b
|
|
@ -124,7 +124,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
object routeValues,
|
object routeValues,
|
||||||
object htmlAttributes)
|
object htmlAttributes)
|
||||||
{
|
{
|
||||||
var url = _urlHelper.Action(actionName, controllerName, routeValues);
|
var url = _urlHelper.Action(actionName, controllerName, routeValues, protocol, hostname, fragment);
|
||||||
return GenerateLink(linkText, url, GetHtmlAttributeDictionaryOrNull(htmlAttributes));
|
return GenerateLink(linkText, url, GetHtmlAttributeDictionaryOrNull(htmlAttributes));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,11 @@ namespace Microsoft.AspNet.Mvc.Core
|
||||||
return GetHtmlHelper<ObjectTemplateModel>(null);
|
return GetHtmlHelper<ObjectTemplateModel>(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static HtmlHelper<ObjectTemplateModel> GetHtmlHelper(IUrlHelper urlHelper)
|
||||||
|
{
|
||||||
|
return GetHtmlHelper<ObjectTemplateModel>(null, urlHelper, CreateViewEngine(), CreateModelMetadataProvider());
|
||||||
|
}
|
||||||
|
|
||||||
public static HtmlHelper<TModel> GetHtmlHelper<TModel>(TModel model)
|
public static HtmlHelper<TModel> GetHtmlHelper<TModel>(TModel model)
|
||||||
{
|
{
|
||||||
return GetHtmlHelper(model, CreateViewEngine());
|
return GetHtmlHelper(model, CreateViewEngine());
|
||||||
|
|
@ -60,16 +65,17 @@ namespace Microsoft.AspNet.Mvc.Core
|
||||||
|
|
||||||
public static HtmlHelper<TModel> GetHtmlHelper<TModel>(TModel model, IModelMetadataProvider provider)
|
public static HtmlHelper<TModel> GetHtmlHelper<TModel>(TModel model, IModelMetadataProvider provider)
|
||||||
{
|
{
|
||||||
return GetHtmlHelper(model, CreateViewEngine(), provider);
|
return GetHtmlHelper(model, CreateUrlHelper(), CreateViewEngine(), provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static HtmlHelper<TModel> GetHtmlHelper<TModel>(TModel model, ICompositeViewEngine viewEngine)
|
public static HtmlHelper<TModel> GetHtmlHelper<TModel>(TModel model, ICompositeViewEngine viewEngine)
|
||||||
{
|
{
|
||||||
return GetHtmlHelper(model, viewEngine, new DataAnnotationsModelMetadataProvider());
|
return GetHtmlHelper(model, CreateUrlHelper(), viewEngine, CreateModelMetadataProvider());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static HtmlHelper<TModel> GetHtmlHelper<TModel>(
|
public static HtmlHelper<TModel> GetHtmlHelper<TModel>(
|
||||||
TModel model,
|
TModel model,
|
||||||
|
IUrlHelper urlHelper,
|
||||||
ICompositeViewEngine viewEngine,
|
ICompositeViewEngine viewEngine,
|
||||||
IModelMetadataProvider provider)
|
IModelMetadataProvider provider)
|
||||||
{
|
{
|
||||||
|
|
@ -94,7 +100,6 @@ namespace Microsoft.AspNet.Mvc.Core
|
||||||
Mock.Of<IValueProvider>(),
|
Mock.Of<IValueProvider>(),
|
||||||
Mock.Of<IInputFormatterSelector>(),
|
Mock.Of<IInputFormatterSelector>(),
|
||||||
Mock.Of<IModelValidatorProvider>());
|
Mock.Of<IModelValidatorProvider>());
|
||||||
var urlHelper = Mock.Of<IUrlHelper>();
|
|
||||||
var actionBindingContextProvider = new Mock<IActionBindingContextProvider>();
|
var actionBindingContextProvider = new Mock<IActionBindingContextProvider>();
|
||||||
actionBindingContextProvider
|
actionBindingContextProvider
|
||||||
.Setup(c => c.GetActionBindingContextAsync(It.IsAny<ActionContext>()))
|
.Setup(c => c.GetActionBindingContextAsync(It.IsAny<ActionContext>()))
|
||||||
|
|
@ -180,5 +185,15 @@ namespace Microsoft.AspNet.Mvc.Core
|
||||||
metadata.PropertyName ?? "(null)",
|
metadata.PropertyName ?? "(null)",
|
||||||
metadata.SimpleDisplayText ?? "(null)");
|
metadata.SimpleDisplayText ?? "(null)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static IUrlHelper CreateUrlHelper()
|
||||||
|
{
|
||||||
|
return Mock.Of<IUrlHelper>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IModelMetadataProvider CreateModelMetadataProvider()
|
||||||
|
{
|
||||||
|
return new DataAnnotationsModelMetadataProvider();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,172 @@
|
||||||
|
using System.Linq;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.AspNet.Mvc.Core;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.Rendering
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Tests the <see cref="HtmlHelper"/>'s link generation methods.
|
||||||
|
/// </summary>
|
||||||
|
public class HtmlHelperLinkGenerationTest
|
||||||
|
{
|
||||||
|
public static IEnumerable<object[]> ActionLinkGenerationData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
yield return new object[] {
|
||||||
|
"Details", "Product", new { isprint = "true", showreviews = "true" }, "https", "www.contoso.com", "h1",
|
||||||
|
new { p1 = "p1-value" } };
|
||||||
|
yield return new object[] {
|
||||||
|
"Details", "Product", new { isprint = "true", showreviews = "true" }, "https", "www.contoso.com", null, null };
|
||||||
|
yield return new object[] {
|
||||||
|
"Details", "Product", new { isprint = "true", showreviews = "true" }, "https", null, null, null };
|
||||||
|
yield return new object[] {
|
||||||
|
"Details", "Product", new { isprint = "true", showreviews = "true" }, null, null, null, null };
|
||||||
|
yield return new object[] {
|
||||||
|
"Details", "Product", null, null, null, null, null };
|
||||||
|
yield return new object[] {
|
||||||
|
null, null, null, null, null, null, null };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(ActionLinkGenerationData))]
|
||||||
|
public void ActionLink_GeneratesLink_WithExpectedValues(
|
||||||
|
string action,
|
||||||
|
string controller,
|
||||||
|
object routeValues,
|
||||||
|
string protocol,
|
||||||
|
string hostname,
|
||||||
|
string fragment,
|
||||||
|
object htmlAttributes)
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
string expectedLink = string.Format(@"<a href=""{0}{1}{2}{3}{4}{5}""{6}>Details</a>",
|
||||||
|
protocol,
|
||||||
|
hostname,
|
||||||
|
controller,
|
||||||
|
action,
|
||||||
|
GetRouteValuesAsString(routeValues),
|
||||||
|
fragment,
|
||||||
|
GetHtmlAttributesAsString(htmlAttributes));
|
||||||
|
|
||||||
|
var urlHelper = new Mock<IUrlHelper>();
|
||||||
|
urlHelper.Setup(
|
||||||
|
h => h.Action(
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<object>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>()))
|
||||||
|
.Returns<string, string, object, string, string, string>(
|
||||||
|
(actn, cntrlr, rvalues, prtcl, hname, frgmt) =>
|
||||||
|
string.Format("{0}{1}{2}{3}{4}{5}",
|
||||||
|
prtcl,
|
||||||
|
hname,
|
||||||
|
cntrlr,
|
||||||
|
actn,
|
||||||
|
GetRouteValuesAsString(rvalues),
|
||||||
|
frgmt));
|
||||||
|
|
||||||
|
var htmlHelper = DefaultTemplatesUtilities.GetHtmlHelper(urlHelper.Object);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actualLink = htmlHelper.ActionLink(
|
||||||
|
linkText: "Details",
|
||||||
|
actionName: action,
|
||||||
|
controllerName: controller,
|
||||||
|
protocol: protocol,
|
||||||
|
hostname: hostname,
|
||||||
|
fragment: fragment,
|
||||||
|
routeValues: routeValues,
|
||||||
|
htmlAttributes: htmlAttributes).ToString();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expectedLink, actualLink);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> RouteLinkGenerationData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
yield return new object[] {
|
||||||
|
"default", new { isprint = "true", showreviews = "true" }, "https", "www.contoso.com", "h1",
|
||||||
|
new { p1 = "p1-value" } };
|
||||||
|
yield return new object[] {
|
||||||
|
"default", new { isprint = "true", showreviews = "true" }, "https", "www.contoso.com", null, null };
|
||||||
|
yield return new object[] {
|
||||||
|
"default", new { isprint = "true", showreviews = "true" }, "https", null, null, null };
|
||||||
|
yield return new object[] {
|
||||||
|
"default", new { isprint = "true", showreviews = "true" }, null, null, null, null };
|
||||||
|
yield return new object[] {
|
||||||
|
"default", null, null, null, null, null };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(RouteLinkGenerationData))]
|
||||||
|
public void RouteLink_GeneratesLink_WithExpectedValues(
|
||||||
|
string routeName,
|
||||||
|
object routeValues,
|
||||||
|
string protocol,
|
||||||
|
string hostname,
|
||||||
|
string fragment,
|
||||||
|
object htmlAttributes)
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
string expectedLink = string.Format(@"<a href=""{0}{1}{2}{3}""{4}>Details</a>",
|
||||||
|
protocol,
|
||||||
|
hostname,
|
||||||
|
GetRouteValuesAsString(routeValues),
|
||||||
|
fragment,
|
||||||
|
GetHtmlAttributesAsString(htmlAttributes));
|
||||||
|
|
||||||
|
var urlHelper = new Mock<IUrlHelper>();
|
||||||
|
urlHelper
|
||||||
|
.Setup(
|
||||||
|
h => h.RouteUrl(
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<object>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>(),
|
||||||
|
It.IsAny<string>()))
|
||||||
|
.Returns<string, object, string, string, string>(
|
||||||
|
(rname, rvalues, prtcl, hname, frgmt) =>
|
||||||
|
string.Format("{0}{1}{2}{3}",
|
||||||
|
prtcl,
|
||||||
|
hname,
|
||||||
|
GetRouteValuesAsString(rvalues),
|
||||||
|
frgmt));
|
||||||
|
|
||||||
|
var htmlHelper = DefaultTemplatesUtilities.GetHtmlHelper(urlHelper.Object);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var actualLink = htmlHelper.RouteLink(
|
||||||
|
linkText: "Details",
|
||||||
|
routeName: routeName,
|
||||||
|
protocol: protocol,
|
||||||
|
hostName: hostname,
|
||||||
|
fragment: fragment,
|
||||||
|
routeValues: routeValues,
|
||||||
|
htmlAttributes: htmlAttributes).ToString();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(expectedLink, actualLink);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetRouteValuesAsString(object routeValues)
|
||||||
|
{
|
||||||
|
var dict = TypeHelper.ObjectToDictionary(routeValues);
|
||||||
|
return string.Join(string.Empty, dict.Select(kvp => string.Format("{0}={1}", kvp.Key, kvp.Value.ToString())));
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetHtmlAttributesAsString(object routeValues)
|
||||||
|
{
|
||||||
|
var dict = TypeHelper.ObjectToDictionary(routeValues);
|
||||||
|
return string.Join(string.Empty, dict.Select(kvp => string.Format(" {0}=\"{1}\"", kvp.Key, kvp.Value.ToString())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
|
|
@ -184,5 +185,57 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> HtmlHelperLinkGenerationData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
yield return new[] {
|
||||||
|
"ActionLink_ActionOnSameController",
|
||||||
|
@"<a href=""/Links/Details"">linktext</a>" };
|
||||||
|
yield return new[] {
|
||||||
|
"ActionLink_ActionOnOtherController",
|
||||||
|
@"<a href=""/Products/Details?print=true"">linktext</a>"
|
||||||
|
};
|
||||||
|
yield return new[] {
|
||||||
|
"ActionLink_SecurePage_ImplicitHostName",
|
||||||
|
@"<a href=""https://localhost/Products/Details?print=true"">linktext</a>"
|
||||||
|
};
|
||||||
|
yield return new[] {
|
||||||
|
"ActionLink_HostNameFragmentAttributes",
|
||||||
|
// note: attributes are alphabetically ordered
|
||||||
|
@"<a href=""https://www.contoso.com:9000/Products/Details?print=true#details"" p1=""p1-value"">linktext</a>"
|
||||||
|
};
|
||||||
|
yield return new[] {
|
||||||
|
"RouteLink_RestLinkToOtherController",
|
||||||
|
@"<a href=""/api/orders/10"">linktext</a>"
|
||||||
|
};
|
||||||
|
yield return new[] {
|
||||||
|
"RouteLink_SecureApi_ImplicitHostName",
|
||||||
|
@"<a href=""https://localhost/api/orders/10"">linktext</a>"
|
||||||
|
};
|
||||||
|
yield return new[] {
|
||||||
|
"RouteLink_HostNameFragmentAttributes",
|
||||||
|
@"<a href=""https://www.contoso.com:9000/api/orders/10?print=True#details"" p1=""p1-value"">linktext</a>"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(HtmlHelperLinkGenerationData))]
|
||||||
|
public async Task HtmlHelperLinkGeneration(string viewName, string expectedLink)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_provider, _app);
|
||||||
|
var client = new HttpClient(server.CreateHandler(), false);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync("http://localhost/Links/Index?view=" + viewName);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
var responseData = await response.Content.ReadAsStringAsync();
|
||||||
|
Assert.Contains(expectedLink, responseData, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNet.Mvc;
|
||||||
|
|
||||||
|
namespace BasicWebSite.Controllers.LinkGeneration
|
||||||
|
{
|
||||||
|
public class LinksController : Controller
|
||||||
|
{
|
||||||
|
public IActionResult Index(string view)
|
||||||
|
{
|
||||||
|
return View(viewName: view);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Details()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNet.Mvc;
|
||||||
|
|
||||||
|
namespace BasicWebSite.Controllers.LinkGeneration
|
||||||
|
{
|
||||||
|
[Route("api/orders/{id?}", Name = "OrdersApi")]
|
||||||
|
public class OrdersController : Controller
|
||||||
|
{
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetAll()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult GetById(int id)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNet.Mvc;
|
||||||
|
|
||||||
|
namespace BasicWebSite.Controllers.LinkGeneration
|
||||||
|
{
|
||||||
|
public class ProductsController : Controller
|
||||||
|
{
|
||||||
|
public IActionResult Index()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IActionResult Details()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
@* Generate link to a different controller and with non-route parameters *@
|
||||||
|
@Html.ActionLink("linktext", "Details", new { controller = "Products", print = "true" })
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
@* Generate link to action on current controller *@
|
||||||
|
@Html.ActionLink("linktext", "Details")
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
@Html.ActionLink(
|
||||||
|
linkText: "linktext",
|
||||||
|
actionName: "Details",
|
||||||
|
controllerName: "Products",
|
||||||
|
protocol: "https",
|
||||||
|
hostname: "www.contoso.com:9000",
|
||||||
|
fragment: "details",
|
||||||
|
routeValues: new { print = "true" },
|
||||||
|
htmlAttributes: new { p1 = "p1-value" })
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
@* Notice that the 'hostname' here is null but the protocol is 'https'. Its a link to a secure page on this application *@
|
||||||
|
@Html.ActionLink(
|
||||||
|
linkText: "linktext",
|
||||||
|
actionName: "Details",
|
||||||
|
controllerName: "Products",
|
||||||
|
protocol: "https",
|
||||||
|
hostname: null,
|
||||||
|
fragment:null,
|
||||||
|
routeValues: new { print = "true" },
|
||||||
|
htmlAttributes: null)
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
@Html.RouteLink(
|
||||||
|
linkText: "linktext",
|
||||||
|
routeName:"OrdersApi",
|
||||||
|
protocol: "https",
|
||||||
|
hostName:"www.contoso.com:9000",
|
||||||
|
fragment: "details",
|
||||||
|
routeValues: new { controller = "Orders", id = 10, print = true },
|
||||||
|
htmlAttributes: new { p1 = "p1-value" })
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
@Html.RouteLink("linktext", "OrdersApi", new { controller = "Orders", id = 10 })
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
@Html.RouteLink(
|
||||||
|
linkText: "linktext",
|
||||||
|
routeName: "OrdersApi",
|
||||||
|
protocol: "https",
|
||||||
|
hostName:null,
|
||||||
|
fragment: null,
|
||||||
|
routeValues: new { controller = "Orders", id = 10 },
|
||||||
|
htmlAttributes: null)
|
||||||
Loading…
Reference in New Issue