Making UrlHelper's methods virtual
This commit is contained in:
parent
71964a813c
commit
43c7ddb9b7
15
Mvc.sln
15
Mvc.sln
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio 14
|
# Visual Studio 14
|
||||||
VisualStudioVersion = 14.0.22013.1
|
VisualStudioVersion = 14.0.22115.0
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}"
|
||||||
EndProject
|
EndProject
|
||||||
|
|
@ -78,6 +78,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "FiltersWebSite", "test\WebS
|
||||||
EndProject
|
EndProject
|
||||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "XmlSerializerWebSite", "test\WebSites\XmlSerializerWebSite\XmlSerializerWebSite.kproj", "{96107AC0-18E2-474D-BAB4-2FFF2185FBCD}"
|
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "XmlSerializerWebSite", "test\WebSites\XmlSerializerWebSite\XmlSerializerWebSite.kproj", "{96107AC0-18E2-474D-BAB4-2FFF2185FBCD}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "UrlHelperWebSite", "test\WebSites\UrlHelperWebSite\UrlHelperWebSite.kproj", "{A192E504-2881-41DC-90D1-B7F1DD1134E8}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
|
@ -398,6 +400,16 @@ Global
|
||||||
{96107AC0-18E2-474D-BAB4-2FFF2185FBCD}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
{96107AC0-18E2-474D-BAB4-2FFF2185FBCD}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||||
{96107AC0-18E2-474D-BAB4-2FFF2185FBCD}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
{96107AC0-18E2-474D-BAB4-2FFF2185FBCD}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||||
{96107AC0-18E2-474D-BAB4-2FFF2185FBCD}.Release|x86.ActiveCfg = Release|Any CPU
|
{96107AC0-18E2-474D-BAB4-2FFF2185FBCD}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{A192E504-2881-41DC-90D1-B7F1DD1134E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{A192E504-2881-41DC-90D1-B7F1DD1134E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{A192E504-2881-41DC-90D1-B7F1DD1134E8}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||||
|
{A192E504-2881-41DC-90D1-B7F1DD1134E8}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||||
|
{A192E504-2881-41DC-90D1-B7F1DD1134E8}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{A192E504-2881-41DC-90D1-B7F1DD1134E8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{A192E504-2881-41DC-90D1-B7F1DD1134E8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{A192E504-2881-41DC-90D1-B7F1DD1134E8}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||||
|
{A192E504-2881-41DC-90D1-B7F1DD1134E8}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||||
|
{A192E504-2881-41DC-90D1-B7F1DD1134E8}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
@ -435,5 +447,6 @@ Global
|
||||||
{6A0B65CE-6B01-40D0-840D-EFF3680D1547} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
{6A0B65CE-6B01-40D0-840D-EFF3680D1547} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||||
{1976AC4A-FEA4-4587-A158-D9F79736D2B6} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
{1976AC4A-FEA4-4587-A158-D9F79736D2B6} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||||
{96107AC0-18E2-474D-BAB4-2FFF2185FBCD} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
{96107AC0-18E2-474D-BAB4-2FFF2185FBCD} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||||
|
{A192E504-2881-41DC-90D1-B7F1DD1134E8} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,52 @@
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc
|
namespace Microsoft.AspNet.Mvc
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the contract for the helper to build URLs for ASP.NET MVC within an application.
|
||||||
|
/// </summary>
|
||||||
public interface IUrlHelper
|
public interface IUrlHelper
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a fully qualified or absolute URL for an action method by using the specified action name,
|
||||||
|
/// controller name, route values, protocol to use, host name and fragment.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="action">The name of the action method.</param>
|
||||||
|
/// <param name="controller">The name of the controller.</param>
|
||||||
|
/// <param name="values">An object that contains the parameters for a route.</param>
|
||||||
|
/// <param name="protocol">The protocol for the URL, such as "http" or "https".</param>
|
||||||
|
/// <param name="host">The host name for the URL.</param>
|
||||||
|
/// <param name="fragment">The fragment for the URL.</param>
|
||||||
|
/// <returns>The fully qualified or absolute URL to an action method.</returns>
|
||||||
string Action(string action, string controller, object values, string protocol, string host, string fragment);
|
string Action(string action, string controller, object values, string protocol, string host, string fragment);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts a virtual (relative) path to an application absolute path.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// If the specified content path does not start with the tilde (~) character,
|
||||||
|
/// this method returns <paramref name="contentPath"/> unchanged.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="contentPath">The virtual path of the content.</param>
|
||||||
|
/// <returns>The application absolute path.</returns>
|
||||||
string Content(string contentPath);
|
string Content(string contentPath);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a value that indicates whether the URL is local.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="url">The URL.</param>
|
||||||
|
/// <returns>true if the URL is local; otherwise, false.</returns>
|
||||||
bool IsLocalUrl(string url);
|
bool IsLocalUrl(string url);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a fully qualified or absolute URL for the specified route values by
|
||||||
|
/// using the specified route name, protocol to use, host name and fragment.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="routeName">The name of the route that is used to generate URL.</param>
|
||||||
|
/// <param name="values">An object that contains the parameters for a route.</param>
|
||||||
|
/// <param name="protocol">The protocol for the URL, such as "http" or "https".</param>
|
||||||
|
/// <param name="host">The host name for the URL.</param>
|
||||||
|
/// <param name="fragment">The fragment for the URL.</param>
|
||||||
|
/// <returns>The fully qualified or absolute URL.</returns>
|
||||||
string RouteUrl(string routeName, object values, string protocol, string host, string fragment);
|
string RouteUrl(string routeName, object values, string protocol, string host, string fragment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,10 @@ using Microsoft.Framework.DependencyInjection;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc
|
namespace Microsoft.AspNet.Mvc
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An implementation of <see cref="IUrlHelper"/> that contains methods to
|
||||||
|
/// build URLs for ASP.NET MVC within an application.
|
||||||
|
/// </summary>
|
||||||
public class UrlHelper : IUrlHelper
|
public class UrlHelper : IUrlHelper
|
||||||
{
|
{
|
||||||
private readonly HttpContext _httpContext;
|
private readonly HttpContext _httpContext;
|
||||||
|
|
@ -18,6 +22,14 @@ namespace Microsoft.AspNet.Mvc
|
||||||
private readonly IDictionary<string, object> _ambientValues;
|
private readonly IDictionary<string, object> _ambientValues;
|
||||||
private readonly IActionSelector _actionSelector;
|
private readonly IActionSelector _actionSelector;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="UrlHelper"/> class using the specified action context and action selector.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="contextAccessor">The <see cref="IContextAccessor{TContext}"/> to access the action context
|
||||||
|
/// of the current request.</param>
|
||||||
|
/// <param name="actionSelector">The <see cref="IActionSelector"/> to be used for verifying the correctness of
|
||||||
|
/// supplied parameters for a route.
|
||||||
|
/// </param>
|
||||||
public UrlHelper(IContextAccessor<ActionContext> contextAccessor, IActionSelector actionSelector)
|
public UrlHelper(IContextAccessor<ActionContext> contextAccessor, IActionSelector actionSelector)
|
||||||
{
|
{
|
||||||
_httpContext = contextAccessor.Value.HttpContext;
|
_httpContext = contextAccessor.Value.HttpContext;
|
||||||
|
|
@ -26,12 +38,13 @@ namespace Microsoft.AspNet.Mvc
|
||||||
_actionSelector = actionSelector;
|
_actionSelector = actionSelector;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Action(
|
/// <inheritdoc />
|
||||||
string action,
|
public virtual string Action(
|
||||||
string controller,
|
string action,
|
||||||
object values,
|
string controller,
|
||||||
string protocol,
|
object values,
|
||||||
string host,
|
string protocol,
|
||||||
|
string host,
|
||||||
string fragment)
|
string fragment)
|
||||||
{
|
{
|
||||||
var valuesDictionary = TypeHelper.ObjectToDictionary(values);
|
var valuesDictionary = TypeHelper.ObjectToDictionary(values);
|
||||||
|
|
@ -55,6 +68,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
return GenerateUrl(protocol, host, path, fragment);
|
return GenerateUrl(protocol, host, path, fragment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public bool IsLocalUrl(string url)
|
public bool IsLocalUrl(string url)
|
||||||
{
|
{
|
||||||
return
|
return
|
||||||
|
|
@ -67,9 +81,11 @@ namespace Microsoft.AspNet.Mvc
|
||||||
(url.Length > 1 && url[0] == '~' && url[1] == '/'));
|
(url.Length > 1 && url[0] == '~' && url[1] == '/'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public string RouteUrl(string routeName, object values, string protocol, string host, string fragment)
|
/// <inheritdoc />
|
||||||
|
public virtual string RouteUrl(string routeName, object values, string protocol, string host, string fragment)
|
||||||
{
|
{
|
||||||
var valuesDictionary = TypeHelper.ObjectToDictionary(values);
|
var valuesDictionary = TypeHelper.ObjectToDictionary(values);
|
||||||
|
|
||||||
var path = GeneratePathFromRoute(routeName, valuesDictionary);
|
var path = GeneratePathFromRoute(routeName, valuesDictionary);
|
||||||
if (path == null)
|
if (path == null)
|
||||||
{
|
{
|
||||||
|
|
@ -84,7 +100,14 @@ namespace Microsoft.AspNet.Mvc
|
||||||
return GeneratePathFromRoute(routeName: null, values: values);
|
return GeneratePathFromRoute(routeName: null, values: values);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GeneratePathFromRoute(string routeName, IDictionary<string, object> values)
|
/// <summary>
|
||||||
|
/// Generates the absolute path of the url for the specified route values by
|
||||||
|
/// using the specified route name.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="routeName">The name of the route that is used to generate the URL.</param>
|
||||||
|
/// <param name="values">A dictionary that contains the parameters for a route.</param>
|
||||||
|
/// <returns>The absolute path of the URL.</returns>
|
||||||
|
protected virtual string GeneratePathFromRoute(string routeName, IDictionary<string, object> values)
|
||||||
{
|
{
|
||||||
var context = new VirtualPathContext(_httpContext, _ambientValues, values, routeName);
|
var context = new VirtualPathContext(_httpContext, _ambientValues, values, routeName);
|
||||||
var path = _router.GetVirtualPath(context);
|
var path = _router.GetVirtualPath(context);
|
||||||
|
|
@ -110,7 +133,8 @@ namespace Microsoft.AspNet.Mvc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Content([NotNull] string contentPath)
|
/// <inheritdoc />
|
||||||
|
public virtual string Content([NotNull] string contentPath)
|
||||||
{
|
{
|
||||||
return GenerateClientUrl(_httpContext.Request.PathBase, contentPath);
|
return GenerateClientUrl(_httpContext.Request.PathBase, contentPath);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,10 +38,9 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
||||||
[InlineData(null, "~/Home/About", "/Home/About")]
|
[InlineData(null, "~/Home/About", "/Home/About")]
|
||||||
[InlineData("/", "~/Home/About", "/Home/About")]
|
[InlineData("/", "~/Home/About", "/Home/About")]
|
||||||
[InlineData("/", "~/", "/")]
|
[InlineData("/", "~/", "/")]
|
||||||
[InlineData("", "~/Home/About", "/Home/About")]
|
|
||||||
[InlineData("/myapproot", "~/", "/myapproot/")]
|
[InlineData("/myapproot", "~/", "/myapproot/")]
|
||||||
[InlineData("", "~/Home/About", "/Home/About")]
|
[InlineData("", "~/Home/About", "/Home/About")]
|
||||||
[InlineData("/myapproot", "~/", "/myapproot/")]
|
[InlineData("/myapproot", "~/Content/bootstrap.css", "/myapproot/Content/bootstrap.css")]
|
||||||
public void Content_ReturnsAppRelativePath_WhenItStartsWithToken(string appRoot,
|
public void Content_ReturnsAppRelativePath_WhenItStartsWithToken(string appRoot,
|
||||||
string contentPath,
|
string contentPath,
|
||||||
string expectedPath)
|
string expectedPath)
|
||||||
|
|
@ -382,11 +381,11 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
||||||
// Act
|
// Act
|
||||||
var url = urlHelper.RouteUrl(routeName: "namedroute",
|
var url = urlHelper.RouteUrl(routeName: "namedroute",
|
||||||
values: new
|
values: new
|
||||||
{
|
{
|
||||||
Action = "newaction",
|
Action = "newaction",
|
||||||
Controller = "home2",
|
Controller = "home2",
|
||||||
id = "someid"
|
id = "someid"
|
||||||
},
|
},
|
||||||
protocol: "https");
|
protocol: "https");
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -436,19 +435,74 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
||||||
// Act
|
// Act
|
||||||
var url = urlHelper.RouteUrl(routeName: "namedroute",
|
var url = urlHelper.RouteUrl(routeName: "namedroute",
|
||||||
values: new
|
values: new
|
||||||
{
|
{
|
||||||
Action = "newaction",
|
Action = "newaction",
|
||||||
Controller = "home2",
|
Controller = "home2",
|
||||||
id = "someid"
|
id = "someid"
|
||||||
});
|
});
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal("/app/named/home2/newaction/someid", url);
|
Assert.Equal("/app/named/home2/newaction/someid", url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void UrlAction_RouteValuesAsDictionary_CaseSensitive()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var urlHelper = CreateUrlHelperWithRouteCollection("/app");
|
||||||
|
|
||||||
|
// We're using a dictionary with a case-sensitive comparer and loading it with data
|
||||||
|
// using casings differently from the route. This should still successfully generate a link.
|
||||||
|
var dict = new Dictionary<string, object>();
|
||||||
|
var id = "suppliedid";
|
||||||
|
var isprint = "true";
|
||||||
|
dict["ID"] = id;
|
||||||
|
dict["isprint"] = isprint;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var url = urlHelper.Action(
|
||||||
|
action: "contact",
|
||||||
|
controller: "home",
|
||||||
|
values: dict);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(2, dict.Count);
|
||||||
|
Assert.Same(id, dict["ID"]);
|
||||||
|
Assert.Same(isprint, dict["isprint"]);
|
||||||
|
Assert.Equal("/app/home/contact/suppliedid?isprint=true", url);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void UrlRouteUrl_RouteValuesAsDictionary_CaseSensitive()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var urlHelper = CreateUrlHelperWithRouteCollection("/app");
|
||||||
|
|
||||||
|
// We're using a dictionary with a case-sensitive comparer and loading it with data
|
||||||
|
// using casings differently from the route. This should still successfully generate a link.
|
||||||
|
var dict = new Dictionary<string, object>();
|
||||||
|
var action = "contact";
|
||||||
|
var controller = "home";
|
||||||
|
var id = "suppliedid";
|
||||||
|
|
||||||
|
dict["ACTION"] = action;
|
||||||
|
dict["Controller"] = controller;
|
||||||
|
dict["ID"] = id;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var url = urlHelper.RouteUrl(routeName: "namedroute", values: dict);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(3, dict.Count);
|
||||||
|
Assert.Same(action, dict["ACTION"]);
|
||||||
|
Assert.Same(controller, dict["Controller"]);
|
||||||
|
Assert.Same(id, dict["ID"]);
|
||||||
|
Assert.Equal("/app/named/home/contact/suppliedid", url);
|
||||||
|
}
|
||||||
|
|
||||||
private static HttpContext CreateHttpContext(string appRoot, ILoggerFactory factory = null)
|
private static HttpContext CreateHttpContext(string appRoot, ILoggerFactory factory = null)
|
||||||
{
|
{
|
||||||
if(factory == null)
|
if (factory == null)
|
||||||
{
|
{
|
||||||
factory = NullLoggerFactory.Instance;
|
factory = NullLoggerFactory.Instance;
|
||||||
}
|
}
|
||||||
|
|
@ -553,7 +607,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
|
||||||
serviceProviderMock.Setup(o => o.GetService(typeof(IInlineConstraintResolver)))
|
serviceProviderMock.Setup(o => o.GetService(typeof(IInlineConstraintResolver)))
|
||||||
.Returns(new DefaultInlineConstraintResolver(serviceProviderMock.Object,
|
.Returns(new DefaultInlineConstraintResolver(serviceProviderMock.Object,
|
||||||
accessorMock.Object));
|
accessorMock.Object));
|
||||||
|
|
||||||
rt.ServiceProvider = serviceProviderMock.Object;
|
rt.ServiceProvider = serviceProviderMock.Object;
|
||||||
rt.MapRoute(string.Empty,
|
rt.MapRoute(string.Empty,
|
||||||
"{controller}/{action}/{id}",
|
"{controller}/{action}/{id}",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Builder;
|
||||||
|
using Microsoft.AspNet.TestHost;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The tests here verify the extensibility of <see cref="UrlHelper"/>.
|
||||||
|
///
|
||||||
|
/// Following are some of the scenarios exercised here:
|
||||||
|
/// 1. Based on configuration, generate Content urls pointing to local or a CDN server
|
||||||
|
/// 2. Based on configuration, generate lower case urls
|
||||||
|
/// </summary>
|
||||||
|
public class CustomUrlHelperTests
|
||||||
|
{
|
||||||
|
private readonly IServiceProvider _services = TestHelper.CreateServices("UrlHelperWebSite");
|
||||||
|
private readonly Action<IApplicationBuilder> _app = new UrlHelperWebSite.Startup().Configure;
|
||||||
|
private const string _cdnServerBaseUrl = "http://cdn.contoso.com";
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CustomUrlHelper_GeneratesUrlFromController()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync("http://localhost/Home/UrlContent");
|
||||||
|
|
||||||
|
string responseData = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
Assert.Equal(_cdnServerBaseUrl + "/bootstrap.min.css", responseData);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CustomUrlHelper_GeneratesUrlFromView()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync("http://localhost/Home/Index");
|
||||||
|
|
||||||
|
string responseData = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
Assert.Contains(_cdnServerBaseUrl + "/bootstrap.min.css", responseData);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("http://localhost/Home/LinkByUrlRouteUrl", "/api/simplepoco/10")]
|
||||||
|
[InlineData("http://localhost/Home/LinkByUrlAction", "/home/urlcontent")]
|
||||||
|
public async Task LowercaseUrls_LinkGeneration(string url, string expectedLink)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var server = TestServer.Create(_services, _app);
|
||||||
|
var client = server.CreateClient();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.GetAsync(url);
|
||||||
|
|
||||||
|
string responseData = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
Assert.Equal(expectedLink, responseData, ignoreCase: false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
"BasicWebSite": "",
|
"BasicWebSite": "",
|
||||||
"CompositeViewEngine": "",
|
"CompositeViewEngine": "",
|
||||||
"ConnegWebsite": "",
|
"ConnegWebsite": "",
|
||||||
"FiltersWebSite": "",
|
"FiltersWebSite": "",
|
||||||
"FormatterWebSite": "",
|
"FormatterWebSite": "",
|
||||||
"InlineConstraintsWebSite": "",
|
"InlineConstraintsWebSite": "",
|
||||||
"Microsoft.AspNet.TestHost": "1.0.0-*",
|
"Microsoft.AspNet.TestHost": "1.0.0-*",
|
||||||
|
|
@ -27,6 +27,7 @@
|
||||||
"RazorWebSite": "",
|
"RazorWebSite": "",
|
||||||
"ValueProvidersSite": "",
|
"ValueProvidersSite": "",
|
||||||
"XmlSerializerWebSite": "",
|
"XmlSerializerWebSite": "",
|
||||||
|
"UrlHelperWebSite": "",
|
||||||
"Xunit.KRunner": "1.0.0-*"
|
"Xunit.KRunner": "1.0.0-*"
|
||||||
},
|
},
|
||||||
"commands": {
|
"commands": {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace UrlHelperWebSite
|
||||||
|
{
|
||||||
|
public class AppOptions
|
||||||
|
{
|
||||||
|
public bool ServeCDNContent { get; set; }
|
||||||
|
|
||||||
|
public string CDNServerBaseUrl { get; set; }
|
||||||
|
|
||||||
|
public bool GenerateLowercaseUrls { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"ServeCDNContent": "true",
|
||||||
|
"CDNServerBaseUrl" : "http://cdn.contoso.com",
|
||||||
|
"GenerateLowercaseUrls": "true"
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,30 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNet.Mvc;
|
||||||
|
|
||||||
|
namespace UrlHelperWebSite.Controllers
|
||||||
|
{
|
||||||
|
public class HomeController : Controller
|
||||||
|
{
|
||||||
|
public IActionResult Index()
|
||||||
|
{
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string UrlContent()
|
||||||
|
{
|
||||||
|
return Url.Content("~/Bootstrap.min.css");
|
||||||
|
}
|
||||||
|
|
||||||
|
public string LinkByUrlAction()
|
||||||
|
{
|
||||||
|
return Url.Action("UrlContent", "Home", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string LinkByUrlRouteUrl()
|
||||||
|
{
|
||||||
|
return Url.RouteUrl("SimplePocoApi", new { id = 10 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNet.Mvc;
|
||||||
|
|
||||||
|
namespace UrlHelperWebSite.Controllers
|
||||||
|
{
|
||||||
|
[Route("api/[controller]/{id?}", Name = "SimplePocoApi")]
|
||||||
|
public class SimplePocoController
|
||||||
|
{
|
||||||
|
private readonly IUrlHelper _urlHelper;
|
||||||
|
|
||||||
|
public SimplePocoController(IUrlHelper urlHelper)
|
||||||
|
{
|
||||||
|
_urlHelper = urlHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public string GetById(int id)
|
||||||
|
{
|
||||||
|
return "value:" + id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.AspNet.Http;
|
||||||
|
using Microsoft.AspNet.Mvc;
|
||||||
|
using Microsoft.Framework.DependencyInjection;
|
||||||
|
using Microsoft.Framework.OptionsModel;
|
||||||
|
|
||||||
|
namespace UrlHelperWebSite
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Following are some of the scenarios exercised here:
|
||||||
|
/// 1. Based on configuration, generate Content urls pointing to local or a CDN server
|
||||||
|
/// 2. Based on configuration, generate lower case urls
|
||||||
|
/// </summary>
|
||||||
|
public class CustomUrlHelper : UrlHelper
|
||||||
|
{
|
||||||
|
private readonly IOptionsAccessor<AppOptions> _appOptions;
|
||||||
|
private readonly HttpContext _httpContext;
|
||||||
|
|
||||||
|
public CustomUrlHelper(IContextAccessor<ActionContext> contextAccessor, IActionSelector actionSelector,
|
||||||
|
IOptionsAccessor<AppOptions> appOptions)
|
||||||
|
: base(contextAccessor, actionSelector)
|
||||||
|
{
|
||||||
|
_appOptions = appOptions;
|
||||||
|
_httpContext = contextAccessor.Value.HttpContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Depending on config data, generates an absolute url pointing to a CDN server
|
||||||
|
/// or falls back to the default behavior
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="contentPath"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public override string Content(string contentPath)
|
||||||
|
{
|
||||||
|
if (_appOptions.Options.ServeCDNContent
|
||||||
|
&& contentPath.StartsWith("~/", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
var segment = new PathString(contentPath.Substring(1));
|
||||||
|
|
||||||
|
return ConvertToLowercaseUrl(_appOptions.Options.CDNServerBaseUrl + segment);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ConvertToLowercaseUrl(base.Content(contentPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string RouteUrl(string routeName, object values, string protocol, string host, string fragment)
|
||||||
|
{
|
||||||
|
return ConvertToLowercaseUrl(base.RouteUrl(routeName, values, protocol, host, fragment));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Action(string action, string controller, object values, string protocol, string host, string fragment)
|
||||||
|
{
|
||||||
|
return ConvertToLowercaseUrl(base.Action(action, controller, values, protocol, host, fragment));
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ConvertToLowercaseUrl(string url)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(url)
|
||||||
|
&& _appOptions.Options.GenerateLowercaseUrls)
|
||||||
|
{
|
||||||
|
return url.ToLowerInvariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNet.Builder;
|
||||||
|
using Microsoft.AspNet.Mvc;
|
||||||
|
using Microsoft.AspNet.Routing;
|
||||||
|
using Microsoft.Framework.ConfigurationModel;
|
||||||
|
using Microsoft.Framework.DependencyInjection;
|
||||||
|
|
||||||
|
namespace UrlHelperWebSite
|
||||||
|
{
|
||||||
|
public class Startup
|
||||||
|
{
|
||||||
|
public void Configure(IApplicationBuilder app)
|
||||||
|
{
|
||||||
|
var configuration = app.GetTestConfiguration();
|
||||||
|
configuration.AddJsonFile("config.json");
|
||||||
|
|
||||||
|
// Set up application services
|
||||||
|
app.UseServices(services =>
|
||||||
|
{
|
||||||
|
services.SetupOptions<AppOptions>(optionsSetup =>
|
||||||
|
{
|
||||||
|
optionsSetup.ServeCDNContent = Convert.ToBoolean(configuration.Get("ServeCDNContent"));
|
||||||
|
optionsSetup.CDNServerBaseUrl = configuration.Get("CDNServerBaseUrl");
|
||||||
|
optionsSetup.GenerateLowercaseUrls = Convert.ToBoolean(configuration.Get("GenerateLowercaseUrls"));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add MVC services to the services container
|
||||||
|
services.AddMvc(configuration);
|
||||||
|
|
||||||
|
services.AddScoped<IUrlHelper, CustomUrlHelper>();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add MVC to the request pipeline
|
||||||
|
app.UseMvc(routes =>
|
||||||
|
{
|
||||||
|
routes.MapRoute("Default", "{controller=Home}/{action=Index}");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="__ToolsVersion__" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup>
|
||||||
|
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">12.0</VisualStudioVersion>
|
||||||
|
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||||
|
<PropertyGroup Label="Globals">
|
||||||
|
<ProjectGuid>a192e504-2881-41dc-90d1-b7f1dd1134e8</ProjectGuid>
|
||||||
|
<OutputType>Web</OutputType>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="$(OutputType) == 'Console'">
|
||||||
|
<DebuggerFlavor>ConsoleDebugger</DebuggerFlavor>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="$(OutputType) == 'Web'">
|
||||||
|
<DebuggerFlavor>WebDebugger</DebuggerFlavor>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'" Label="Configuration">
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'" Label="Configuration">
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup>
|
||||||
|
<SchemaVersion>2.0</SchemaVersion>
|
||||||
|
<DevelopmentServerPort>35856</DevelopmentServerPort>
|
||||||
|
</PropertyGroup>
|
||||||
|
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||||
|
</Project>
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
@Url.Content("~/Bootstrap.min.css")
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
@Url.Content("~/Bootstrap.min.css")
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.AspNet.Mvc": "",
|
||||||
|
"Microsoft.AspNet.Server.IIS": "1.0.0-*",
|
||||||
|
"Microsoft.AspNet.Mvc.TestConfiguration": "",
|
||||||
|
"Microsoft.Framework.ConfigurationModel.Json": "1.0.0-*",
|
||||||
|
"Microsoft.Framework.OptionsModel": "1.0.0-*"
|
||||||
|
},
|
||||||
|
"frameworks": {
|
||||||
|
"aspnet50": { },
|
||||||
|
"aspnetcore50": { }
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue