Skip to content
This repository was archived by the owner on Dec 14, 2018. It is now read-only.
This repository was archived by the owner on Dec 14, 2018. It is now read-only.

ExpressionTextCache is case insensitive: TextboxFor(x=>x.name) and TextboxFor(x=>x.Name) produce the same html #6349

Closed
@IGionny

Description

@IGionny

I think that there is a bug with ExpressionTextCache 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 id="name" name="name" ...all in lowercase (like the first 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.Product

PRODUCT<br />

@Html.EditorFor(x => x.Name)

== [ProductLower] ==

@{
    ViewData["Title"] = "Home Page";
}
@model Example_Models.Models.ProductLower


LOWER<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:

//https://github.com/aspnet/Mvc/blob/2cabd589ac6a2fcd88b3c23aa50541536d2c8b71/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ExpressionHelperTest.cs

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 TIME
new FixedExpressionTextCache())
       {
       }
   }

In startup.cs:

 public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
            services.AddTransient(typeof(IHtmlHelper<>), typeof(FixedHtmlHelper<>));

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions