5

Caution, before You read the rest

This question is not about POST method, redisplaying view with submited form or binding input values to controller method parameters. It's purely about rendering the View using html helper (HiddenFor or Hidden - both returns the same).


I created a simple hidden field using HiddenFor helper

@Html.HiddenFor(m => m.ProductCode)

and my problem is that value for this hidden field is rendered as null:

<input id="productCode" name="productCode" type="hidden" value/>

Even if I set it when instantiating a model and of course it's confirmed with debugging (it always has a value).
So instead it should look like this:

<input id="productCode" name="productCode" type="hidden" value="8888888"/>

Because I know there are some questions like this one (actually all of them refer to changing form values during form POST) I included list of things I tried already. My code is right below this section which I belive to be essential.


So far I tried:

  • ModelState.Clear() everywhere possible - cause as we know the value from ModelState is first place where it looks for the value. NO EFFECT which I expected, cause my ModelState is empty (my case is not about changing value during POST in controller as in many questions like that), so it should take value from my view model.
  • Not using HiddenFor helper, but pure html instead. WORKS, but its just workaround, not an answer to the problem.
  • Duplicating line with helper in view as follows:
@Html.HiddenFor(m => m.ProductCode)
@Html.HiddenFor(m => m.ProductCode)

PARTIALLY WORKS Produces first input as value/> and second as value="8888888"/> which indicates that there is probably something that hides initial property value. Anyway, I found nothing in ViewData at any point, nor in query string. Obviously I can't accept it this way, no explonation needed I guess.

  • Changing name of the property. Originally it was ProductCode. I changed it to productCode, ProdCode, ProductCodeasd, etc. and all of these WORKS. Again, it looks like there is something that hides/updates the value, but again - 100% sure there is no JS or anything else doing it. So still no answer found - just workaround again.
  • Explicitly setting the value for HiddenFor: @Html.HiddenFor(x => x.ProductCode, new {Value = @Model.ProductCode}). NO EFFECT, renders the same way.
  • Using @Html.Hidden instead of @Html.HiddenFor. NO EFFECT, with name set as ProductCode it renders the same way.

One more thing I found interesting. Reading html with Display page source in Chrome [ctrl+U] shows that value is valid value="8888888"/>, but in DevTools it's still value/> and of course submitting the form passes null to Controller method.


Model

public class Product
    {
        public string Description { get; set; }
        public int Quantity { get; set; }
        public string ProductCode { get; set; }
        public string ImageUrl { get; set; }

        public Product(string desc, string productCode, string imgUrl)
        {
            Description = desc;
            ProductCode = productCode;
            ImageUrl = imgUrl;
        }
    }

View

@model Product

@using (Html.BeginForm("UpdateCart", "Cart"))
{
    <div class="row pad10">

        <div class="col-sm-6 text-center">
            <img src="@Model.ImageUrl" width="300" height="300" />
        </div>
        <div class="col-sm-6 text-justify">
            <p>@Model.Description</p>
            <div class="row padding-top-2">
                <div class="col-sm-6">
                    <label>@CommonResources.Quantity: </label>
                </div>
                <div class="col-sm-4">
                    @Html.TextBoxFor(m => m.Quantity, new
                    {
                        @class = "form-control",
                        @data_val_required = CommonResources.FieldRequired,
                        @data_val_number = CommonResources.ValidationNumber
                    })
                    @Html.ValidationMessageFor(model => model.Quantity, "", new { @class = "text-danger" })
                    @Html.HiddenFor(m => m.ProductCode)
                </div>
            </div>
        </div>
    </div>
    <div class="text-center col-xs-12 padTop20 padBottom20">
        <input type="submit" value="Submit" class="whtBtn pad" />
    </div>
}

Controller

The view is returned from controller with RedirectToAction as follows:
ValidateAndProceed -> ResolveNextStep (here redirection occurs) -> ShowProduct

        public ActionResult ValidateAndProceed()
        {
            var order = Session.Current.Order;
            var lang = LangService.GetSelectedLanguage();
            var invoice = Session.Current.CurrentInvoice;
            var localCurrency = Session.Current.LocalCurrencyInfo;

            List<CheckoutValidationFieldError> errors = new List<CheckoutValidationFieldError>();

            errors = ValidationService.ValidateAddress(order);
            if (errors.Count > 0)
            {
                return RedirectToAction("InvalidAddress", "Address", new { serializedErrors = JsonConvert.SerializeObject(errors) });
            }

            return ResolveNextStep(order, invoice);
        }

        public ActionResult ResolveNextStep(IOrder order, IInvoice invoice)
        {
            if (OrderService.ShowProductView(order, invoice))
            {
                return RedirectToAction("ShowProduct");
            }
            return RedirectToAction("Summary");
        }

        public ActionResult ShowProduct()
        {
            Product model = ProductService.GetProduct(Session.Current.CurrentInvoice);
            return View("~/Views/Product.cshtml", model );
        }

Finally, what can cause such a weird behavior? I've already ran out of options. Maybe anyone had problem like mine before, would appreciate any clue on this case.

Przemysław
  • 206
  • 1
  • 7
  • Have you tried explicitly setting the value for `HiddenFor`: `@Html.HiddenFor(x => x.ProductCode, new {Value = @Model.ProductCode})` and then see what value do you get? – Rahul Sharma Jan 27 '20 at 13:04
  • @RahulSharma Yes, sorry I forgot, tried already and no effect. Question updated. – Przemysław Jan 27 '20 at 13:20
  • Check out this link: https://learn.microsoft.com/en-us/archive/blogs/simonince/asp-net-mvcs-html-helpers-render-the-wrong-value – Rahul Sharma Jan 27 '20 at 13:23
  • Checked it before, but as I said before its not a problem with manipulating data during POST (btw my ModelState is empty, yet I used ModelState.Clear() just to be double sure of that). What's more I don't have a problem with binding values to controller method parameters. My problem is in setting input's value with my model's property value, so its purely problem with rendering view, not passing form input values to controller on submit. – Przemysław Jan 27 '20 at 13:46
  • Okay, well `HiddenFor` always reads from `ModelState` not the model itself so you would have to look at the `ModelState` before you send it to your `View`. Have you tried explicitly removing this key before binding it: `ModelState.Remove("ProductCode"); Product model = ProductService.GetProduct(Session.Current.CurrentInvoice);` – Rahul Sharma Jan 27 '20 at 13:58
  • @RahulSharma Yes I did and no effect. I belive `ModelState.Clear()` removes this key anyway. But I also did at as You mentioned. – Przemysław Jan 27 '20 at 14:06
  • 1
    Why is your constructor called Donation? Have you tried changing it to the Product? – salli Jan 27 '20 at 14:25
  • Overlooked - my bad, it's Product in my code. It would throw an error before rendering the View, so still that's not it, but thanks for pointing it out. Question updated. – Przemysław Jan 27 '20 at 14:32
  • @Przemysław, can you give us examples of ProductCode? Only other thing I would suggest to try would be to change `return ResolveNextStep(order, invoice);` to ` return RedirectToAction("ResolveNextStep", new { order = order, invoice = invoice } );`. When I was stepping through your scenario I noticed that your code would go back to the ValidateAndProceed action before rendering ShowProduct() when you used `ResolveNextStep(order, invoice)`. It really shouldn't matter, but i just thought it was odd. I was able to get the ProductCode value both ways. – salli Jan 27 '20 at 17:11
  • As presented in question, code example is _8888888_, another could be _342716_, _992635_ etc. Tried your suggestion even if it's not the area where this problem occures (when debugging view I can see the right value in view model) and it doesn't solve the problem, doesn't work. It won't go back to ValidateAndProceed(), cause it moves on to ResolveNextStep() and continue execution (ValidateAndProceed returns redirection, so it ends there). – Przemysław Jan 28 '20 at 09:26
  • @Przemysław To clarify, the value of `ProductCode` comes from this line: `Product model = ProductService.GetProduct(Session.Current.CurrentInvoice);`. Basically what I want to ask is that are your sending the `ProductCode` value during redirection? – Rahul Sharma Jan 28 '20 at 09:34
  • I'm getting the model (with ProductCode, ImageUrl etc.) and passing it to the view in ShowProduct(). So, it's after redirection. – Przemysław Jan 28 '20 at 09:55
  • @Przemysław Okay. Did you clear the model state before you hit this line: `Product model = ProductService.GetProduct(Session.Current.CurrentInvoice);` ? I am assuming this line would assign: `model.ProductCode` to some value which you are not able to see on your `View` – Rahul Sharma Jan 28 '20 at 11:17
  • @RahulSharma Yes exactly, I used Clear() at that point (even if I saw in immediate window that ModelState is empty - no keys in dictionary etc.). And yes again - this is the line where ProductCode is assigned. I can see the proper ProductCode value when debugging the View (breakpoint at some point of the View), but then I see no value in browser DevTools inspector. That's why I think the problem is with rendering the view alone, not anything else before. Looks like something is hiding the right value, no idea what could it be. – Przemysław Jan 28 '20 at 11:35
  • 1
    @Przemysław Can you try this way: Decorate your field in your model: `[HiddenInput(DisplayValue = false)] public string ProductCode { get; set; }` and then render on your View like: `@Html.EditorFor(x => x.ProductCode)` – Rahul Sharma Jan 28 '20 at 11:54
  • @RahulSharma Renders exactly the same way. _ProductCode_ seems to be cursed property name. – Przemysław Jan 28 '20 at 12:39
  • @Przemysław Not quite where all `ProductCode` exists in your code but this is pretty weird. – Rahul Sharma Jan 28 '20 at 12:42
  • @RahulSharma Yes, this is weird and still no answers. – Przemysław Jan 28 '20 at 14:58
  • 3
    If view source shows the value, could it be cleared via javascript? – Jeremy Lakeman Jan 30 '20 at 06:03
  • It's basically seeking for debug without having the [MCVE], just a guess game. – Reza Aghaei Jan 30 '20 at 07:04
  • 2
    So what strikes me as telling here is that in the raw HTML source, the value is set. That's the actual rendered value of the view, not what DevTools tells you. The discrepancy very likely means there is some change in the value that is occurring in JavaScript after the browser finishes loading the page. If you try disabling JavaScript in the DevTools and reload the page, does the value show the correct value then in DevTools? – Andy Mudrak Jan 31 '20 at 02:14
  • 1
    @AndyMudrak with JavaScript disabled the view won't render, that's bacuse of app architecture, but finally JavaScript was a good direction which I mentioned in the answer. – Przemysław Jan 31 '20 at 11:21
  • @Przemysław Phew! My faith in .NET has been restored again after reading your answer. `Javascript` is always the culprit – Rahul Sharma Jan 31 '20 at 11:50
  • @RahulSharma belive me, mine as well :D Yes, `Javascript` is so often responsible for actions like this, but this time it was like one or two levels up, so hard to find.. – Przemysław Jan 31 '20 at 12:43

1 Answers1

2

I debugged the whole process of rendering the view (got into .Net sources) checking every possible place that could make it fail and found nothing.

After @AndyMudrak and @Jeremy Lakeman comments I decided to try again to find JavaScript responsible for that behavior, but deeper than I did before. What I found was a really silly script where element Id is being concatenated from three strings what I didn't expect, cause it's really badly implemented. So finally - JavaScript is doing it and there is no bad behavior from framework etc.

Actually I am a bit disappointed (even if it's good to know this easy answer) cause it looked much more complicated than it really was and it took me hours to find out how simple it is :|

Thanks for comments, sorry for final simplicity.

Przemysław
  • 206
  • 1
  • 7