Fix #448: Support app-wide defaults for HTML helpers

This commit is contained in:
Kirthi Krishnamraju 2015-04-28 14:58:19 -07:00
parent ac230a78c1
commit caa8ea44fb
47 changed files with 488 additions and 71 deletions

View File

@ -8,6 +8,7 @@ using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.Internal;
using Microsoft.Framework.Logging;
using Microsoft.Net.Http.Headers;
using Microsoft.Framework.OptionsModel;
namespace Microsoft.AspNet.Mvc
{
@ -59,6 +60,8 @@ namespace Microsoft.AspNet.Mvc
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<PartialViewResult>>();
var options = context.HttpContext.RequestServices.GetRequiredService<IOptions<MvcOptions>>();
var viewName = ViewName ?? context.ActionDescriptor.Name;
var viewEngineResult = viewEngine.FindPartialView(context, viewName);
if (!viewEngineResult.Success)
@ -80,7 +83,13 @@ namespace Microsoft.AspNet.Mvc
using (view as IDisposable)
{
await ViewExecutor.ExecuteAsync(view, context, ViewData, TempData, ContentType);
await ViewExecutor.ExecuteAsync(
view,
context,
ViewData,
TempData,
options.Options.HtmlHelperOptions,
ContentType);
}
}
}

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
@ -8,6 +8,7 @@ using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.Framework.Internal;
using Microsoft.Net.Http.Headers;
using Microsoft.Framework.OptionsModel;
namespace Microsoft.AspNet.Mvc
{
@ -34,6 +35,7 @@ namespace Microsoft.AspNet.Mvc
[NotNull] ActionContext actionContext,
[NotNull] ViewDataDictionary viewData,
[NotNull] ITempDataDictionary tempData,
[NotNull] HtmlHelperOptions htmlHelperOptions,
MediaTypeHeaderValue contentType)
{
var response = actionContext.HttpContext.Response;
@ -66,9 +68,15 @@ namespace Microsoft.AspNet.Mvc
using (var writer = new HttpResponseStreamWriter(response.Body, encoding))
{
var viewContext = new ViewContext(actionContext, view, viewData, tempData, writer);
await view.RenderAsync(viewContext);
}
var viewContext = new ViewContext(
actionContext,
view,
viewData,
tempData,
writer,
htmlHelperOptions);
await view.RenderAsync(viewContext); }
}
}
}

View File

@ -8,6 +8,7 @@ using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.Internal;
using Microsoft.Framework.Logging;
using Microsoft.Net.Http.Headers;
using Microsoft.Framework.OptionsModel;
namespace Microsoft.AspNet.Mvc
{
@ -59,6 +60,8 @@ namespace Microsoft.AspNet.Mvc
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<ViewResult>>();
var options = context.HttpContext.RequestServices.GetRequiredService<IOptions<MvcOptions>>();
var viewName = ViewName ?? context.ActionDescriptor.Name;
var viewEngineResult = viewEngine.FindView(context, viewName);
if(!viewEngineResult.Success)
@ -80,7 +83,13 @@ namespace Microsoft.AspNet.Mvc
using (view as IDisposable)
{
await ViewExecutor.ExecuteAsync(view, context, ViewData, TempData, ContentType);
await ViewExecutor.ExecuteAsync(
view,
context,
ViewData,
TempData,
options.Options.HtmlHelperOptions,
ContentType);
}
}
}

View File

@ -9,6 +9,8 @@ using Microsoft.AspNet.Mvc.Core.Internal;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.ModelBinding.Metadata;
using Microsoft.AspNet.Mvc.ModelBinding.Validation;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.Framework.Internal;
using Newtonsoft.Json;
namespace Microsoft.AspNet.Mvc
@ -36,6 +38,7 @@ namespace Microsoft.AspNet.Mvc
ModelValidatorProviders = new List<IModelValidatorProvider>();
ClientModelValidatorProviders = new List<IClientModelValidatorProvider>();
CacheProfiles = new Dictionary<string, CacheProfile>(StringComparer.OrdinalIgnoreCase);
HtmlHelperOptions = new HtmlHelperOptions();
SerializerSettings = SerializerSettingsProvider.CreateSerializerSettings();
}
@ -171,5 +174,10 @@ namespace Microsoft.AspNet.Mvc
/// </ul>
/// </remarks>
public IList<IMetadataDetailsProvider> ModelMetadataDetailsProviders { get; }
/// <summary>
/// Gets or sets programmatic configuration for the HTML helpers and <see cref="ViewContext"/>.
/// </summary>
public HtmlHelperOptions HtmlHelperOptions { get; [param: NotNull] set; }
}
}

View File

@ -55,11 +55,11 @@ namespace Microsoft.AspNet.Mvc.Rendering
_htmlEncoder = htmlEncoder;
// Underscores are fine characters in id's.
IdAttributeDotReplacement = "_";
IdAttributeDotReplacement = optionsAccessor.Options.HtmlHelperOptions.IdAttributeDotReplacement;
}
/// <inheritdoc />
public string IdAttributeDotReplacement { get; set; }
public string IdAttributeDotReplacement { get; }
/// <inheritdoc />
public string Encode(string value)

View File

@ -73,10 +73,6 @@ namespace Microsoft.AspNet.Mvc.Rendering
{
return _htmlGenerator.IdAttributeDotReplacement;
}
set
{
_htmlGenerator.IdAttributeDotReplacement = value;
}
}
/// <inheritdoc />
@ -494,7 +490,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
var view = viewEngineResult.View;
using (view as IDisposable)
{
var viewContext = new ViewContext(ViewContext, view, newViewData, TempData, writer);
var viewContext = new ViewContext(ViewContext, view, newViewData, writer);
await viewEngineResult.View.RenderAsync(viewContext);
}
}

View File

@ -13,7 +13,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
/// </summary>
public interface IHtmlGenerator
{
string IdAttributeDotReplacement { get; set; }
string IdAttributeDotReplacement { get; }
string Encode(string value);

View File

@ -0,0 +1,44 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNet.Mvc.Rendering
{
/// <summary>
/// Provides programmatic configuration for the HTML helpers and <see cref="ViewContext"/>.
/// </summary>
public class HtmlHelperOptions
{
/// <summary>
/// Gets or sets the <see cref="Html5DateRenderingMode.Html5DateRenderingMode"/> value.
/// </summary>
/// <remarks>
/// Set this property to <see cref="Html5DateRenderingMode.Rfc3339"/> to have templated helpers such as
/// <see cref="IHtmlHelper.Editor"/> and <see cref="IHtmlHelper{TModel}.EditorFor"/> render date and time
/// values as RFC 3339 compliant strings. By default these helpers render dates and times using the current
/// culture.
/// </remarks>
public Html5DateRenderingMode Html5DateRenderingMode { get; set; }
/// <summary>
/// Gets or sets the <see cref="string"/> that replaces periods in the ID attribute of an element.
/// </summary>
public string IdAttributeDotReplacement { get; set; } = "_";
/// <summary>
/// Gets or sets a value that indicates whether client-side validation is enabled.
/// </summary>
public bool ClientValidationEnabled { get; set; } = true;
/// <summary>
/// Gets or sets the element name used to wrap a top-level message generated by
/// <see cref="IHtmlHelper.ValidationMessage"/> and other overloads.
/// </summary>
public string ValidationMessageElement { get; set; } = "span";
/// <summary>
/// Gets or sets the element name used to wrap a top-level message generated by
/// <see cref="IHtmlHelper.ValidationSummary"/> and other overloads.
/// </summary>
public string ValidationSummaryMessageElement { get; set; } = "span";
}
}

View File

@ -24,9 +24,9 @@ namespace Microsoft.AspNet.Mvc.Rendering
Html5DateRenderingMode Html5DateRenderingMode { get; set; }
/// <summary>
/// Gets or sets the character that replaces periods in the ID attribute of an element.
/// Gets the <see cref="string"/> that replaces periods in the ID attribute of an element.
/// </summary>
string IdAttributeDotReplacement { get; set; }
string IdAttributeDotReplacement { get; }
/// <summary>
/// Gets the metadata provider. Intended for use in <see cref="IHtmlHelper"/> extension methods.

View File

@ -96,7 +96,6 @@ namespace Microsoft.AspNet.Mvc
context.ViewContext,
view,
ViewData ?? context.ViewContext.ViewData,
TempData ?? context.ViewContext.TempData,
context.Writer);
using (view as IDisposable)

View File

@ -43,7 +43,8 @@ namespace Microsoft.AspNet.Mvc
[NotNull] IView view,
[NotNull] ViewDataDictionary viewData,
[NotNull] ITempDataDictionary tempData,
[NotNull] TextWriter writer)
[NotNull] TextWriter writer,
[NotNull] HtmlHelperOptions htmlHelperOptions)
: base(actionContext)
{
View = view;
@ -52,9 +53,10 @@ namespace Microsoft.AspNet.Mvc
Writer = writer;
_formContext = _defaultFormContext;
ClientValidationEnabled = true;
ValidationSummaryMessageElement = "span";
ValidationMessageElement = "span";
ClientValidationEnabled = htmlHelperOptions.ClientValidationEnabled;
Html5DateRenderingMode = htmlHelperOptions.Html5DateRenderingMode;
ValidationSummaryMessageElement = htmlHelperOptions.ValidationSummaryMessageElement;
ValidationMessageElement = htmlHelperOptions.ValidationMessageElement;
}
/// <summary>

View File

@ -12,6 +12,7 @@ using Microsoft.Framework.Logging;
using Microsoft.Net.Http.Headers;
using Moq;
using Xunit;
using Microsoft.Framework.OptionsModel;
namespace Microsoft.AspNet.Mvc
{
@ -167,6 +168,13 @@ namespace Microsoft.AspNet.Mvc
.Returns(viewEngine.Object);
serviceProvider.Setup(p => p.GetService(typeof(ILogger<PartialViewResult>)))
.Returns(new Mock<ILogger<PartialViewResult>>().Object);
serviceProvider.Setup(s => s.GetService(typeof(IOptions<MvcOptions>)))
.Returns(() => {
var optionsAccessor = new Mock<IOptions<MvcOptions>>();
optionsAccessor.SetupGet(o => o.Options)
.Returns(new MvcOptions());
return optionsAccessor.Object;
});
context.HttpContext.RequestServices = serviceProvider.Object;
var viewResult = new PartialViewResult
@ -187,6 +195,14 @@ namespace Microsoft.AspNet.Mvc
serviceProvider.Setup(s => s.GetService(typeof(ILogger<PartialViewResult>)))
.Returns(new Mock<ILogger<PartialViewResult>>().Object);
serviceProvider.Setup(s => s.GetService(typeof(IOptions<MvcOptions>)))
.Returns(() => {
var optionsAccessor = new Mock<IOptions<MvcOptions>>();
optionsAccessor.SetupGet(o => o.Options)
.Returns(new MvcOptions());
return optionsAccessor.Object;
});
var httpContext = new DefaultHttpContext();
httpContext.RequestServices = serviceProvider.Object;

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
@ -75,7 +75,13 @@ namespace Microsoft.AspNet.Mvc
var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider());
// Act
await ViewExecutor.ExecuteAsync(view.Object, actionContext, viewData, null, contentType);
await ViewExecutor.ExecuteAsync(
view.Object,
actionContext,
viewData,
null,
new HtmlHelperOptions(),
contentType);
// Assert
Assert.Equal(expectedContentType, context.Response.ContentType);
@ -106,7 +112,13 @@ namespace Microsoft.AspNet.Mvc
// Act
await Record.ExceptionAsync(
() => ViewExecutor.ExecuteAsync(view.Object, actionContext, viewData, null, contentType: null));
() => ViewExecutor.ExecuteAsync(
view.Object,
actionContext,
viewData,
null,
new HtmlHelperOptions(),
contentType: null));
// Assert
Assert.Equal(expectedLength, memoryStream.Length);

View File

@ -9,6 +9,7 @@ using Microsoft.AspNet.Http.Internal;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Routing;
using Microsoft.Framework.Logging;
using Microsoft.Framework.OptionsModel;
using Microsoft.Net.Http.Headers;
using Moq;
using Xunit;
@ -179,6 +180,13 @@ namespace Microsoft.AspNet.Mvc
.Returns(viewEngine.Object);
serviceProvider.Setup(p => p.GetService(typeof(ILogger<ViewResult>)))
.Returns(new Mock<ILogger<ViewResult>>().Object);
serviceProvider.Setup(s => s.GetService(typeof(IOptions<MvcOptions>)))
.Returns(() => {
var optionsAccessor = new Mock<IOptions<MvcOptions>>();
optionsAccessor.SetupGet(o => o.Options)
.Returns(new MvcOptions());
return optionsAccessor.Object;
});
context.HttpContext.RequestServices = serviceProvider.Object;
var viewResult = new ViewResult
@ -199,6 +207,13 @@ namespace Microsoft.AspNet.Mvc
serviceProvider.Setup(s => s.GetService(typeof(ILogger<ViewResult>)))
.Returns(new Mock<ILogger<ViewResult>>().Object);
var optionsAccessor = new Mock<IOptions<MvcOptions>>();
optionsAccessor.SetupGet(o => o.Options)
.Returns(new MvcOptions());
serviceProvider.Setup(s => s.GetService(typeof(IOptions<MvcOptions>)))
.Returns(optionsAccessor.Object);
var httpContext = new DefaultHttpContext();
httpContext.RequestServices = serviceProvider.Object;

View File

@ -872,7 +872,6 @@ Environment.NewLine;
public string IdAttributeDotReplacement
{
get { return _innerHelper.IdAttributeDotReplacement; }
set { _innerHelper.IdAttributeDotReplacement = value; }
}
public IModelMetadataProvider MetadataProvider

View File

@ -612,7 +612,8 @@ namespace Microsoft.AspNet.Mvc.Rendering
Mock.Of<IView>(),
viewData,
Mock.Of<ITempDataDictionary>(),
TextWriter.Null);
TextWriter.Null,
new HtmlHelperOptions());
}
public enum RegularEnum

View File

@ -72,7 +72,8 @@ namespace Microsoft.AspNet.Mvc.Rendering
CreateViewEngine(),
metadataProvider,
innerHelperWrapper: null,
htmlGenerator: htmlGenerator);
htmlGenerator: htmlGenerator,
idAttributeDotReplacement: null);
}
public static HtmlHelper<TModel> GetHtmlHelper<TModel>(ViewDataDictionary<TModel> viewData)
@ -83,7 +84,22 @@ namespace Microsoft.AspNet.Mvc.Rendering
CreateViewEngine(),
TestModelMetadataProvider.CreateDefaultProvider(),
innerHelperWrapper: null,
htmlGenerator: null);
htmlGenerator: null,
idAttributeDotReplacement: null);
}
public static HtmlHelper<TModel> GetHtmlHelper<TModel>(
ViewDataDictionary<TModel> viewData,
string idAttributeDotReplacement)
{
return GetHtmlHelper(
viewData,
CreateUrlHelper(),
CreateViewEngine(),
TestModelMetadataProvider.CreateDefaultProvider(),
innerHelperWrapper: null,
htmlGenerator: null,
idAttributeDotReplacement: idAttributeDotReplacement);
}
public static HtmlHelper<TModel> GetHtmlHelper<TModel>(TModel model)
@ -91,6 +107,22 @@ namespace Microsoft.AspNet.Mvc.Rendering
return GetHtmlHelper(model, CreateViewEngine());
}
public static HtmlHelper<TModel> GetHtmlHelper<TModel>(TModel model, string idAttributeDotReplacement)
{
var provider = TestModelMetadataProvider.CreateDefaultProvider();
var viewData = new ViewDataDictionary<TModel>(provider);
viewData.Model = model;
return GetHtmlHelper(
viewData,
CreateUrlHelper(),
CreateViewEngine(),
provider,
innerHelperWrapper: null,
htmlGenerator: null,
idAttributeDotReplacement: idAttributeDotReplacement);
}
public static HtmlHelper<IEnumerable<TModel>> GetHtmlHelperForEnumerable<TModel>(TModel model)
{
return GetHtmlHelper<IEnumerable<TModel>>(new TModel[] { model });
@ -117,7 +149,9 @@ namespace Microsoft.AspNet.Mvc.Rendering
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, CreateUrlHelper(), viewEngine, TestModelMetadataProvider.CreateDefaultProvider());
}
@ -154,7 +188,14 @@ namespace Microsoft.AspNet.Mvc.Rendering
var viewData = new ViewDataDictionary<TModel>(provider);
viewData.Model = model;
return GetHtmlHelper(viewData, urlHelper, viewEngine, provider, innerHelperWrapper, htmlGenerator: null);
return GetHtmlHelper(
viewData,
urlHelper,
viewEngine,
provider,
innerHelperWrapper,
htmlGenerator: null,
idAttributeDotReplacement: null);
}
private static HtmlHelper<TModel> GetHtmlHelper<TModel>(
@ -163,12 +204,17 @@ namespace Microsoft.AspNet.Mvc.Rendering
ICompositeViewEngine viewEngine,
IModelMetadataProvider provider,
Func<IHtmlHelper, IHtmlHelper> innerHelperWrapper,
IHtmlGenerator htmlGenerator)
IHtmlGenerator htmlGenerator,
string idAttributeDotReplacement)
{
var httpContext = new DefaultHttpContext();
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
var options = new MvcOptions();
if (!string.IsNullOrEmpty(idAttributeDotReplacement))
{
options.HtmlHelperOptions.IdAttributeDotReplacement = idAttributeDotReplacement;
}
options.ClientModelValidatorProviders.Add(new DataAnnotationsClientModelValidatorProvider());
var optionsAccessor = new Mock<IOptions<MvcOptions>>();
optionsAccessor
@ -205,6 +251,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
new CommonTestEncoder(),
new UrlEncoder(),
new JavaScriptStringEncoder());
if (innerHelperWrapper != null)
{
innerHelper = innerHelperWrapper(innerHelper);
@ -220,7 +267,15 @@ namespace Microsoft.AspNet.Mvc.Rendering
new CommonTestEncoder(),
new UrlEncoder(),
new JavaScriptStringEncoder());
var viewContext = new ViewContext(actionContext, Mock.Of<IView>(), viewData, null, new StringWriter());
var viewContext = new ViewContext(
actionContext,
Mock.Of<IView>(),
viewData,
null,
new StringWriter(),
options.HtmlHelperOptions);
htmlHelper.Contextualize(viewContext);
return htmlHelper;

View File

@ -168,8 +168,9 @@ namespace Microsoft.AspNet.Mvc.Rendering
@"Property3=""HtmlEncode[[Property3Value]]"" type=""HtmlEncode[[checkbox]]"" value=""HtmlEncode[[true]]"" /><input " +
@"name=""HtmlEncode[[MyPrefix.Property1]]"" type=""HtmlEncode[[hidden]]"" value=""HtmlEncode[[false]]"" />";
var dictionary = new Dictionary<string, object> { { "Property3", "Property3Value" } };
var helper = DefaultTemplatesUtilities.GetHtmlHelper();
helper.IdAttributeDotReplacement = "!!!";
var helper = DefaultTemplatesUtilities.GetHtmlHelper<DefaultTemplatesUtilities.ObjectTemplateModel>(
model: null,
idAttributeDotReplacement: "!!!");
helper.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "MyPrefix";
// Act

View File

@ -241,9 +241,10 @@ namespace Microsoft.AspNet.Mvc.Rendering
// Arrange
var expected = @"<input id=""HtmlEncode[[MyPrefix$Property1]]"" name=""HtmlEncode[[MyPrefix.Property1]]"" type=""HtmlEncode[[hidden]]"" " +
@"value=""HtmlEncode[[modelstate-with-prefix]]"" />";
var helper = DefaultTemplatesUtilities.GetHtmlHelper(GetViewDataWithModelStateAndModelAndViewDataValues());
var helper = DefaultTemplatesUtilities.GetHtmlHelper(
GetViewDataWithModelStateAndModelAndViewDataValues(),
idAttributeDotReplacement: "$");
helper.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "MyPrefix";
helper.IdAttributeDotReplacement = "$";
helper.ViewData.ModelState.Clear();
helper.ViewData.ModelState.Add("Property1", GetModelState("modelstate-without-prefix"));
helper.ViewData.ModelState.Add("MyPrefix.Property1", GetModelState("modelstate-with-prefix"));
@ -262,9 +263,10 @@ namespace Microsoft.AspNet.Mvc.Rendering
// Arrange
var expected = @"<input id=""HtmlEncode[[MyPrefix$Property1]]"" name=""HtmlEncode[[MyPrefix.Property1]]"" type=""HtmlEncode[[hidden]]"" " +
@"value=""HtmlEncode[[vdd-with-prefix]]"" />";
var helper = DefaultTemplatesUtilities.GetHtmlHelper(GetViewDataWithModelStateAndModelAndViewDataValues());
var helper = DefaultTemplatesUtilities.GetHtmlHelper(
GetViewDataWithModelStateAndModelAndViewDataValues(),
idAttributeDotReplacement: "$");
helper.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "MyPrefix";
helper.IdAttributeDotReplacement = "$";
helper.ViewData.ModelState.Clear();
helper.ViewData.Clear();
helper.ViewData.Add("Property1", "vdd-without-prefix");
@ -403,8 +405,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
viewData["Property3[height]"] = "Prop3Value";
viewData["Property4.Property5"] = "Prop5Value";
viewData["Property4.Property6[0]"] = "Prop6Value";
var helper = DefaultTemplatesUtilities.GetHtmlHelper(viewData);
helper.IdAttributeDotReplacement = "$$";
var helper = DefaultTemplatesUtilities.GetHtmlHelper(viewData, idAttributeDotReplacement: "$$");
var attributes = new Dictionary<string, object> { { "data-test", "val" } };
// Act
@ -530,10 +531,11 @@ namespace Microsoft.AspNet.Mvc.Rendering
// Arrange
var expected = @"<input id=""HtmlEncode[[MyPrefix$Property1]]"" name=""HtmlEncode[[MyPrefix.Property1]]"" type=""HtmlEncode[[hidden]]"" " +
@"value=""HtmlEncode[[modelstate-with-prefix]]"" />";
var helper = DefaultTemplatesUtilities.GetHtmlHelper(GetViewDataWithModelStateAndModelAndViewDataValues());
var helper = DefaultTemplatesUtilities.GetHtmlHelper(
GetViewDataWithModelStateAndModelAndViewDataValues(),
"$");
helper.ViewData.Model.Property1 = "propValue";
helper.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "MyPrefix";
helper.IdAttributeDotReplacement = "$";
helper.ViewData.ModelState.Clear();
helper.ViewData.ModelState.Add("Property1", GetModelState("modelstate-without-prefix"));
helper.ViewData.ModelState.Add("MyPrefix.Property1", GetModelState("modelstate-with-prefix"));

View File

@ -93,9 +93,10 @@ namespace Microsoft.AspNet.Mvc.Rendering
// Arrange
var expected = @"<input id=""HtmlEncode[[MyPrefix$Property1]]"" name=""HtmlEncode[[MyPrefix.Property1]]"" type=""HtmlEncode[[password]]"" " +
@"value=""HtmlEncode[[explicit-value]]"" />";
var helper = DefaultTemplatesUtilities.GetHtmlHelper(GetViewDataWithModelStateAndModelAndViewDataValues());
var helper = DefaultTemplatesUtilities.GetHtmlHelper(
GetViewDataWithModelStateAndModelAndViewDataValues(),
idAttributeDotReplacement: "$");
helper.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix = "MyPrefix";
helper.IdAttributeDotReplacement = "$";
// Act
var result = helper.Password("Property1", "explicit-value", htmlAttributes: null);
@ -196,8 +197,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
{
// Arrange
var viewData = GetViewDataWithModelStateAndModelAndViewDataValues();
var helper = DefaultTemplatesUtilities.GetHtmlHelper(viewData);
helper.IdAttributeDotReplacement = "$$";
var helper = DefaultTemplatesUtilities.GetHtmlHelper(viewData, idAttributeDotReplacement: "$$");
var attributes = new Dictionary<string, object> { { "data-test", "val" } };
// Act

View File

@ -12,7 +12,13 @@ namespace Microsoft.AspNet.Mvc.Rendering
public void SettingViewData_AlsoUpdatesViewBag()
{
// Arrange (eventually passing null to these consturctors will throw)
var context = new ViewContext(new ActionContext(null, null, null), view: null, viewData: null, tempData: null, writer: null);
var context = new ViewContext(
new ActionContext(null, null, null),
view: null,
viewData: null,
tempData: null,
writer: null,
htmlHelperOptions: new HtmlHelperOptions());
var originalViewData = context.ViewData = new ViewDataDictionary(metadataProvider: new EmptyModelMetadataProvider());
var replacementViewData = new ViewDataDictionary(metadataProvider: new EmptyModelMetadataProvider());

View File

@ -54,7 +54,13 @@ namespace Microsoft.AspNet.Mvc
{
var actionContext = new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor());
var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider());
var viewContext = new ViewContext(actionContext, view, viewData, null, TextWriter.Null);
var viewContext = new ViewContext(
actionContext,
view,
viewData, null,
TextWriter.Null,
new HtmlHelperOptions());
var writer = new StreamWriter(stream) { AutoFlush = true };
var viewComponentDescriptor = new ViewComponentDescriptor()

View File

@ -110,7 +110,8 @@ namespace Microsoft.AspNet.Mvc.ViewComponents
Mock.Of<IView>(),
new ViewDataDictionary(new EmptyModelMetadataProvider()),
null,
TextWriter.Null);
TextWriter.Null,
new HtmlHelperOptions());
}
private class TestViewComponent : ViewComponent

View File

@ -92,7 +92,14 @@ namespace Microsoft.AspNet.Mvc
{
var actionContext = new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor());
var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider());
var viewContext = new ViewContext(actionContext, view, viewData, null, TextWriter.Null);
var viewContext = new ViewContext(
actionContext,
view,
viewData,
null,
TextWriter.Null,
new HtmlHelperOptions());
var writer = new StreamWriter(stream) { AutoFlush = true };
var viewComponentDescriptor = new ViewComponentDescriptor()

View File

@ -300,14 +300,25 @@ namespace Microsoft.AspNet.Mvc
private static ViewComponentContext GetViewComponentContext(IView view, ViewDataDictionary viewData)
{
var actionContext = new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor());
var viewContext = new ViewContext(actionContext, view, viewData, null, TextWriter.Null);
var viewContext = new ViewContext(
actionContext,
view,
viewData,
null,
TextWriter.Null,
new HtmlHelperOptions());
var viewComponentDescriptor = new ViewComponentDescriptor()
{
Type = typeof(object),
};
var viewComponentContext = new ViewComponentContext(viewComponentDescriptor, new object[0], viewContext, TextWriter.Null);
var viewComponentContext = new ViewComponentContext(
viewComponentDescriptor,
new object[0],
viewContext,
TextWriter.Null);
return viewComponentContext;
}
}

View File

@ -0,0 +1,87 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading.Tasks;
using Microsoft.AspNet.Builder;
using Microsoft.Framework.DependencyInjection;
using RazorWebSite;
using Xunit;
namespace Microsoft.AspNet.Mvc.FunctionalTests
{
public class HtmlHelperOptionsTest
{
private const string SiteName = nameof(RazorWebSite);
private readonly Action<IApplicationBuilder> _app = new Startup().Configure;
private readonly Action<IServiceCollection> _configureServices = new Startup().ConfigureServices;
[Fact]
public async Task AppWideDefaultsInViewAndPartialView()
{
// Arrange
var expected =
@"<div class=""validation-summary-errors""><validationSummaryElement>MySummary</validationSummaryElement>
<ul><li style=""display:none""></li>
</ul></div>
<validationMessageElement class=""field-validation-error"">An error occurred.</validationMessageElement>
<input id=""Prefix!Property1"" name=""Prefix.Property1"" type=""text"" value="""" />
<div class=""editor-label""><label for=""MyDate"">MyDate</label></div>
<div class=""editor-field""><input class=""text-box single-line"" id=""MyDate"" name=""MyDate"" type=""datetime"" value=""2000-01-02T03:04:05.060&#x2B;00:00"" /> </div>
<div class=""validation-summary-errors""><validationSummaryElement>MySummary</validationSummaryElement>
<ul><li style=""display:none""></li>
</ul></div>
<validationMessageElement class=""field-validation-error"">An error occurred.</validationMessageElement>
<input id=""Prefix!Property1"" name=""Prefix.Property1"" type=""text"" value="""" />
<div class=""editor-label""><label for=""MyDate"">MyDate</label></div>
<div class=""editor-field""><input class=""text-box single-line"" id=""MyDate"" name=""MyDate"" type=""datetime"" value=""2000-01-02T03:04:05.060&#x2B;00:00"" /> </div>
False";
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
var client = server.CreateClient();
// Act
var body = await client.GetStringAsync("http://localhost/HtmlHelperOptions/HtmlHelperOptionsDefaultsInView");
// Assert
Assert.Equal(expected, body.Trim());
}
[Fact]
public async Task OverrideAppWideDefaultsInViewAndPartialView()
{
// Arrange
var expected =
@"<div class=""validation-summary-errors""><ValidationSummaryInView>MySummary</ValidationSummaryInView>
<ul><li style=""display:none""></li>
</ul></div>
<ValidationInView class=""field-validation-error"" data-valmsg-for=""Error"" data-valmsg-replace=""true"">An error occurred.</ValidationInView>
<input id=""Prefix!Property1"" name=""Prefix.Property1"" type=""text"" value="""" />
<div class=""editor-label""><label for=""MyDate"">MyDate</label></div>
<div class=""editor-field""><input class=""text-box single-line"" id=""MyDate"" name=""MyDate"" type=""datetime"" value=""02/01/2000 03:04:05 &#x2B;00:00"" /> <ValidationInView class=""field-validation-valid"" data-valmsg-for=""MyDate"" data-valmsg-replace=""true""></ValidationInView></div>
True
<div class=""validation-summary-errors""><ValidationSummaryInPartialView>MySummary</ValidationSummaryInPartialView>
<ul><li style=""display:none""></li>
</ul></div>
<ValidationInPartialView class=""field-validation-error"" data-valmsg-for=""Error"" data-valmsg-replace=""true"">An error occurred.</ValidationInPartialView>
<input id=""Prefix!Property1"" name=""Prefix.Property1"" type=""text"" value="""" />
<div class=""editor-label""><label for=""MyDate"">MyDate</label></div>
<div class=""editor-field""><input class=""text-box single-line"" id=""MyDate"" name=""MyDate"" type=""datetime"" value=""02/01/2000 03:04:05 &#x2B;00:00"" /> <ValidationInPartialView class=""field-validation-valid"" data-valmsg-for=""MyDate"" data-valmsg-replace=""true""></ValidationInPartialView></div>
True";
var server = TestHelper.CreateServer(_app, SiteName, _configureServices);
var client = server.CreateClient();
// Act
var body = await client.GetStringAsync("http://localhost/HtmlHelperOptions/OverrideAppWideDefaultsInView");
// Assert
Assert.Equal(expected, body.Trim());
}
}
}

View File

@ -144,7 +144,8 @@ namespace Microsoft.AspNet.Mvc.Razor
Mock.Of<IView>(),
viewData,
Mock.Of<ITempDataDictionary>(),
TextWriter.Null);
TextWriter.Null,
new HtmlHelperOptions());
return viewContext;
}

View File

@ -44,7 +44,8 @@ namespace Microsoft.AspNet.Mvc.Razor
Mock.Of<IView>(),
new ViewDataDictionary(new EmptyModelMetadataProvider()),
Mock.Of<ITempDataDictionary>(),
TextWriter.Null);
TextWriter.Null,
new HtmlHelperOptions());
// Act
activator.Activate(instance, viewContext);
@ -75,7 +76,8 @@ namespace Microsoft.AspNet.Mvc.Razor
Mock.Of<IView>(),
new ViewDataDictionary(new EmptyModelMetadataProvider()),
Mock.Of<ITempDataDictionary>(),
TextWriter.Null);
TextWriter.Null,
new HtmlHelperOptions());
// Act and Assert
var ex = Assert.Throws<InvalidOperationException>(() => activator.Activate(instance, viewContext));
@ -117,7 +119,8 @@ namespace Microsoft.AspNet.Mvc.Razor
Mock.Of<IView>(),
viewData,
Mock.Of<ITempDataDictionary>(),
TextWriter.Null);
TextWriter.Null,
new HtmlHelperOptions());
// Act
activator.Activate(instance, viewContext);
@ -155,7 +158,8 @@ namespace Microsoft.AspNet.Mvc.Razor
Mock.Of<IView>(),
viewData,
Mock.Of<ITempDataDictionary>(),
TextWriter.Null);
TextWriter.Null,
new HtmlHelperOptions());
// Act
activator.Activate(instance, viewContext);
@ -190,7 +194,8 @@ namespace Microsoft.AspNet.Mvc.Razor
Mock.Of<IView>(),
viewData,
Mock.Of<ITempDataDictionary>(),
TextWriter.Null);
TextWriter.Null,
new HtmlHelperOptions());
// Act
activator.Activate(instance, viewContext);

View File

@ -126,7 +126,8 @@ namespace Microsoft.AspNet.Mvc.Razor
view: Mock.Of<IView>(),
viewData: viewData,
tempData: Mock.Of<ITempDataDictionary>(),
writer: new StringWriter());
writer: new StringWriter(),
htmlHelperOptions: new HtmlHelperOptions());
}
private class TestRazorPage : RazorPage<RazorPageCreateModelExpressionModel>

View File

@ -124,7 +124,8 @@ namespace Microsoft.AspNet.Mvc.Razor
Mock.Of<IView>(),
viewData,
Mock.Of<ITempDataDictionary>(),
TextWriter.Null);
TextWriter.Null,
new HtmlHelperOptions());
return new TestRazorPage
{

View File

@ -1298,7 +1298,8 @@ namespace Microsoft.AspNet.Mvc.Razor
Mock.Of<IView>(),
null,
Mock.Of<ITempDataDictionary>(),
writer);
writer,
new HtmlHelperOptions());
}
private static Action<TextWriter> CreateBodyAction(string value)

View File

@ -1339,7 +1339,8 @@ namespace Microsoft.AspNet.Mvc.Razor
view,
new ViewDataDictionary(new EmptyModelMetadataProvider()),
Mock.Of<ITempDataDictionary>(),
new StringWriter());
new StringWriter(),
new Rendering.HtmlHelperOptions());
}
private static IViewStartProvider CreateViewStartProvider(params IRazorPage[] viewStartPages)

View File

@ -820,7 +820,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
Mock.Of<IView>(),
new ViewDataDictionary(new EmptyModelMetadataProvider()),
Mock.Of<ITempDataDictionary>(),
TextWriter.Null);
TextWriter.Null,
new HtmlHelperOptions());
}
private static TagHelperContext GetTagHelperContext(string id = "testid",

View File

@ -438,7 +438,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
Mock.Of<IView>(),
new ViewDataDictionary(new TestModelMetadataProvider()),
Mock.Of<ITempDataDictionary>(),
TextWriter.Null);
TextWriter.Null,
new HtmlHelperOptions());
}
}
}

View File

@ -206,7 +206,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
Mock.Of<IView>(),
viewData,
Mock.Of<ITempDataDictionary>(),
TextWriter.Null);
TextWriter.Null,
new HtmlHelperOptions());
return viewContext;
}

View File

@ -697,7 +697,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
Mock.Of<IView>(),
viewData,
Mock.Of<ITempDataDictionary>(),
TextWriter.Null);
TextWriter.Null,
new HtmlHelperOptions());
return viewContext;
}

View File

@ -792,7 +792,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
Mock.Of<IView>(),
viewData,
Mock.Of<ITempDataDictionary>(),
TextWriter.Null);
TextWriter.Null,
new HtmlHelperOptions());
return viewContext;
}

View File

@ -63,7 +63,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
Mock.Of<IView>(),
viewData,
Mock.Of<ITempDataDictionary>(),
TextWriter.Null);
TextWriter.Null,
new HtmlHelperOptions());
return viewContext;
}

View File

@ -305,7 +305,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
new ViewDataDictionary(
new EmptyModelMetadataProvider()),
Mock.Of<ITempDataDictionary>(),
TextWriter.Null);
TextWriter.Null,
new HtmlHelperOptions());
}
}
}

View File

@ -308,7 +308,8 @@ Parameter name: value",
new ViewDataDictionary(
new EmptyModelMetadataProvider()),
Mock.Of<ITempDataDictionary>(),
TextWriter.Null);
TextWriter.Null,
new HtmlHelperOptions());
}
private class Model

View File

@ -24,7 +24,13 @@ namespace ModelBindingWebSite.Controllers
{
_activated = true;
var viewData = new ViewDataDictionary<Person>(ViewData);
var context = new ViewContext(ActionContext, new TestView(), viewData, null, TextWriter.Null);
var context = new ViewContext(
ActionContext,
new TestView(),
viewData, null,
TextWriter.Null,
new HtmlHelperOptions());
((ICanHasViewContext)PersonHelper).Contextualize(context);
}

View File

@ -0,0 +1,54 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNet.Mvc;
namespace RazorWebSite.Controllers
{
public class HtmlHelperOptionsController : Controller
{
public IActionResult HtmlHelperOptionsDefaultsInView()
{
var model = new DateModel
{
MyDate = new DateTimeOffset(
year: 2000,
month: 1,
day: 2,
hour: 3,
minute: 4,
second: 5,
millisecond: 60,
offset: TimeSpan.FromHours(0))
};
ModelState.AddModelError("Error", "An error occurred.");
return View(model);
}
public IActionResult OverrideAppWideDefaultsInView()
{
var model = new DateModel
{
MyDate = new DateTimeOffset(
year: 2000,
month: 1,
day: 2,
hour: 3,
minute: 4,
second: 5,
millisecond: 60,
offset: TimeSpan.FromHours(0))
};
ModelState.AddModelError("Error", "An error occurred.");
return View(model);
}
}
public class DateModel
{
public DateTimeOffset MyDate { get; set; }
}
}

View File

@ -25,6 +25,14 @@ namespace RazorWebSite
options.ViewLocationExpanders.Add(expander);
options.ViewLocationExpanders.Add(new CustomPartialDirectoryViewLocationExpander());
});
services.ConfigureMvc(options =>
{
options.HtmlHelperOptions.ClientValidationEnabled = false;
options.HtmlHelperOptions.Html5DateRenderingMode = Microsoft.AspNet.Mvc.Rendering.Html5DateRenderingMode.Rfc3339;
options.HtmlHelperOptions.IdAttributeDotReplacement = "!";
options.HtmlHelperOptions.ValidationMessageElement = "validationMessageElement";
options.HtmlHelperOptions.ValidationSummaryMessageElement = "validationSummaryElement";
});
}
public void Configure(IApplicationBuilder app)

View File

@ -0,0 +1,7 @@
@model RazorWebSite.Controllers.DateModel
@Html.ValidationSummary(true, "MySummary")
@Html.ValidationMessage("Error")
@Html.TextBox("Prefix.Property1")
@Html.EditorForModel()
@Html.ViewContext.ClientValidationEnabled

View File

@ -0,0 +1,7 @@
@model RazorWebSite.Controllers.DateModel
@Html.ValidationSummary(true, "MySummary")
@Html.ValidationMessage("Error")
@Html.TextBox("Prefix.Property1")
@Html.EditorForModel()
@await Html.PartialAsync("HtmlHelperOptionsDefaultsInPartialView", Model)

View File

@ -0,0 +1,10 @@
@model RazorWebSite.Controllers.DateModel
@{
ViewContext.ValidationMessageElement = "ValidationInPartialView";
ViewContext.ValidationSummaryMessageElement = "ValidationSummaryInPartialView";
}
@Html.ValidationSummary(true, "MySummary")
@Html.ValidationMessage("Error")
@Html.TextBox("Prefix.Property1")
@Html.EditorForModel()
@Html.ViewContext.ClientValidationEnabled

View File

@ -0,0 +1,13 @@
@model RazorWebSite.Controllers.DateModel
@{
ViewContext.ValidationMessageElement = "ValidationInView";
ViewContext.ValidationSummaryMessageElement = "ValidationSummaryInView";
ViewContext.Html5DateRenderingMode = Html5DateRenderingMode.CurrentCulture;
ViewContext.ClientValidationEnabled = true;
}
@Html.ValidationSummary(true, "MySummary")
@Html.ValidationMessage("Error")
@Html.TextBox("Prefix.Property1")
@Html.EditorForModel()
@Html.ViewContext.ClientValidationEnabled
@await Html.PartialAsync("OverrideAppWideDefaultsInPartialView")