- Notifications
You must be signed in to change notification settings - Fork2.1k
ExpressionTextCache is case insensitive: TextboxFor(x=>x.name) and TextboxFor(x=>x.Name) produce the same html #6349
Description
I think that there is a bug withExpressionTextCache used to manage expressions into Html.EditorFor...IF i visti a page with Html.EditorFor(x=>x.name) AND after a page with Html.EditorFor(x=>x.Name) (in 2 different model). ..the result will be the same: <input name="name" ...all in lowercase (like thefirst lamda expression resolved).
I repro the problem via a test website with 2 model: public class Product { public string Name { get; set; } }public class ProductLower { public string name { get; set; } }
A controller with 2 actions:public IActionResult Product() { var model = new Product(); return View(model); }
public IActionResult ProductLower() { var model = new ProductLower(); return View(model); }
and 2 views:
== [Product] ==
@{ ViewData["Title"] = "Home Page";}@model Example_Models.Models.ProductPRODUCT<br />@Html.EditorFor(x => x.Name)== [ProductLower] ==
@{ ViewData["Title"] = "Home Page";}@model Example_Models.Models.ProductLowerLOWER<br />@Html.EditorFor(x => x.name)If you visit ProductLower page and AFTER the Product page, the output of the @Html.EditorFor will be the same: <input id='name' name='name'...
I also take the tests for the ExpressionTextCache and put the 'wrong-case' to proof this:
edit the NonEquivalentExpressions adding
(Expression<Func<TestLowerModel, string>>) (model => model.name),(Expression<Func<TestModel, string>>) (model => model.Name)full code:
//https://github.com/aspnet/Mvc/blob/2cabd589ac6a2fcd88b3c23aa50541536d2c8b71/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ExpressionHelperTest.cs public class ExpressionTextCacheTests { private readonly ExpressionTextCache _expressionTextCache = new ExpressionTextCache(); public static TheoryData<Expression, Expression> NonEquivalentExpressions { get { var value = "test"; var key = "TestModel"; var Model = "Test"; var myModel = new TestModel(); return new TheoryData<Expression, Expression> { //ADDED CASE { (Expression<Func<TestLowerModel, string>>) (model => model.name), (Expression<Func<TestModel, string>>) (model => model.Name) }, { (Expression<Func<TestModel, Category>>) (model => model.SelectedCategory), (Expression<Func<TestModel, CategoryName>>) (model => model.SelectedCategory.CategoryName) }, { (Expression<Func<TestModel, string>>) (model => model.Model), (Expression<Func<TestModel, string>>) (model => model.Name) }, { (Expression<Func<TestModel, CategoryName>>) (model => model.SelectedCategory.CategoryName), (Expression<Func<TestModel, string>>) (model => value) }, { (Expression<Func<TestModel, string>>) (testModel => testModel.SelectedCategory.CategoryName .MainCategory), (Expression<Func<TestModel, string>>) (testModel => value) }, { (Expression<Func<IList<TestModel>, Category>>) (model => model[2].SelectedCategory), (Expression<Func<TestModel, string>>) (model => model.SelectedCategory.CategoryName.MainCategory) }, { (Expression<Func<TestModel, int>>) (testModel => testModel.SelectedCategory.CategoryId), (Expression<Func<TestModel, Category>>) (model => model.SelectedCategory) }, { (Expression<Func<IDictionary<string, TestModel>, string>>) (model => model[key].SelectedCategory .CategoryName.MainCategory), (Expression<Func<TestModel, Category>>) (model => model.SelectedCategory) }, { (Expression<Func<TestModel, string>>) (m => Model), (Expression<Func<TestModel, string>>) (m => m.Model) }, { (Expression<Func<TestModel, TestModel>>) (m => m), (Expression<Func<TestModel, string>>) (m => m.Model) }, { (Expression<Func<TestModel, string>>) (m => myModel.Name), (Expression<Func<TestModel, string>>) (m => m.Name) }, { (Expression<Func<TestModel, string>>) (m => key), (Expression<Func<TestModel, string>>) (m => value) } }; } } [Theory] [MemberData(nameof(NonEquivalentExpressions))] public void GetExpressionText_CheckNonEquivalentExpressions(LambdaExpression expression1, LambdaExpression expression2) { // Act - 1 var text1 = ExpressionHelper.GetExpressionText(expression1, _expressionTextCache); // Act - 2 var text2 = ExpressionHelper.GetExpressionText(expression2, _expressionTextCache); // Assert Assert.NotEqual(text1, text2, StringComparer.Ordinal); Assert.NotSame(text1, text2); } private class TestLowerModel { public string name { get; set; } } private class TestModel { public string Name { get; set; } public string Model { get; set; } public Category SelectedCategory { get; set; } public IList<Category> PreferredCategories { get; set; } } private class Category { public int CategoryId { get; set; } public CategoryName CategoryName { get; set; } } private class CategoryName { public string MainCategory { get; set; } public string SubCategory { get; set; } } }I found a workaround:
public class FixedHtmlHelper<TModel> : HtmlHelper<TModel> { public FixedHtmlHelper(IHtmlGenerator htmlGenerator, ICompositeViewEngine viewEngine, IModelMetadataProvider metadataProvider, IViewBufferScope bufferScope, HtmlEncoder htmlEncoder, UrlEncoder urlEncoder, ExpressionTextCache expressionTextCache) : base(htmlGenerator, viewEngine, metadataProvider, bufferScope, htmlEncoder, urlEncoder,//NEW INSTANCE EACH TIMEnew FixedExpressionTextCache()) { } }In startup.cs:
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddTransient(typeof(IHtmlHelper<>), typeof(FixedHtmlHelper<>));