[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 htmlAttributes)
|
||||
{
|
||||
var url = _urlHelper.Action(actionName, controllerName, routeValues);
|
||||
var url = _urlHelper.Action(actionName, controllerName, routeValues, protocol, hostname, fragment);
|
||||
return GenerateLink(linkText, url, GetHtmlAttributeDictionaryOrNull(htmlAttributes));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -48,6 +48,11 @@ namespace Microsoft.AspNet.Mvc.Core
|
|||
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)
|
||||
{
|
||||
return GetHtmlHelper(model, CreateViewEngine());
|
||||
|
|
@ -60,16 +65,17 @@ namespace Microsoft.AspNet.Mvc.Core
|
|||
|
||||
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)
|
||||
{
|
||||
return GetHtmlHelper(model, viewEngine, new DataAnnotationsModelMetadataProvider());
|
||||
return GetHtmlHelper(model, CreateUrlHelper(), viewEngine, CreateModelMetadataProvider());
|
||||
}
|
||||
|
||||
public static HtmlHelper<TModel> GetHtmlHelper<TModel>(
|
||||
TModel model,
|
||||
IUrlHelper urlHelper,
|
||||
ICompositeViewEngine viewEngine,
|
||||
IModelMetadataProvider provider)
|
||||
{
|
||||
|
|
@ -94,7 +100,6 @@ namespace Microsoft.AspNet.Mvc.Core
|
|||
Mock.Of<IValueProvider>(),
|
||||
Mock.Of<IInputFormatterSelector>(),
|
||||
Mock.Of<IModelValidatorProvider>());
|
||||
var urlHelper = Mock.Of<IUrlHelper>();
|
||||
var actionBindingContextProvider = new Mock<IActionBindingContextProvider>();
|
||||
actionBindingContextProvider
|
||||
.Setup(c => c.GetActionBindingContextAsync(It.IsAny<ActionContext>()))
|
||||
|
|
@ -180,5 +185,15 @@ namespace Microsoft.AspNet.Mvc.Core
|
|||
metadata.PropertyName ?? "(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.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
|
|
@ -184,5 +185,57 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
|||
// Assert
|
||||
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