[Fixes #926] Protocol & Host name ignored when creating action link

This commit is contained in:
Kiran Challa 2014-09-24 14:14:04 -07:00
parent b9d433168e
commit fdeff1188b
14 changed files with 341 additions and 4 deletions

View File

@ -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));
}

View File

@ -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();
}
}
}

View File

@ -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())));
}
}
}

View File

@ -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);
}
}
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}
}

View File

@ -0,0 +1,2 @@
@* Generate link to a different controller and with non-route parameters *@
@Html.ActionLink("linktext", "Details", new { controller = "Products", print = "true" })

View File

@ -0,0 +1,2 @@
@* Generate link to action on current controller *@
@Html.ActionLink("linktext", "Details")

View File

@ -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" })

View File

@ -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)

View File

@ -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" })

View File

@ -0,0 +1 @@
@Html.RouteLink("linktext", "OrdersApi", new { controller = "Orders", id = 10 })

View File

@ -0,0 +1,8 @@
@Html.RouteLink(
linkText: "linktext",
routeName: "OrdersApi",
protocol: "https",
hostName:null,
fragment: null,
routeValues: new { controller = "Orders", id = 10 },
htmlAttributes: null)