0

I have a page where some values are posted to my controller as part of a view model. The controller attempts to process a list within the controller and will spit back any values it couldn't process.

Now first time the page loads everything is good, however when the user posts any tokens that are incorrect then Razor or ASP.NET messes with the data going out.

So if the user sends in:

Token1 = ABC
Token2 = DEF
Token3 = GHI

And token 1 and 3 fail then the model that is passed back is:

Token1 = ABC
Token3 = GHI

But what gets rendered is:

Token1 = ABC
Token3 = DEF

I have verified that I am passing the correct data back as next to the textbox I have told it to just output the keycode (ie: "GHI" from above) and it does as text render the correct value, but in the textbox next to it, referencing the same variable (Model.Tokens[tokenCounter].KeyCode) it gets rendered incorrectly.

Code in the controller:

 if (failedTokens.Count == 0)
 {
     // Forward to success page
     ... 
 }
 else
 {
     ModelState.AddModelError("", "Tokens couldn't be added");
     bulkTokenCreateModel.Tokens = failedTokens;
     return View(..., bulkTokenCreateModel);
 }

Code in the view:

@for (var tokenCounter = 0; tokenCounter < Model.Tokens.Count; tokenCounter++)
{
    ...
    <td>
        <div class="form-group">
            @Model.Tokens[tokenCounter].KeyCode
            @Html.TextBoxFor(model => Model.Tokens[tokenCounter].KeyCode, new { @class = "form-control" })
        </div>
    </td>
    ...
}

I can only put this down to ASP.NET or Razor messing with model? Or am I missing something stupid? Any thoughts, or anything to check would be very helpful

EDIT:

Have been asked to add classes:

public class BulkTokenCreateModel
{
    public string Notes { get; set; }
    public int Quantity { get; set; }
    public List<Token> Tokens { get; set; }
}

public class Token
{
    public int FobId { get; set; }
    public DateTime DateAdded { get; set; }
    public string KeyCode { get; set; }
    ...
}

The token class is not a strictly ViewModel only class (while the BulkTokenCreateModel class is), but the KeyCode property in question is only a simple string property. I have removed the other parts of the class for fear of giving too much away about the inner workings of the software. If need be I can post more, but nothing in that class interferes with the KeyCode property.

EDIT2:

Was asked for the full code in the action of the controller so here goes:

    public ActionResult ScanTokensComplete(BulkTokenCreateModel bulkTokenCreateModel)
    {
        var businessLayer = BusinessLayerManager.Current;
        var failedTokens = new List<Token>();

        foreach (var token in bulkTokenCreateModel.Tokens)
        {
            try
            {
                token.DateAdded = DateTime.Now;
                token.Enabled = true;

                var addedToken = businessLayer.TokenAdd(token);
            }
            catch (FaultException<ArgumentNullFault> detail)
            {
                failedTokens.Add(token);
                ModelState.AddModelError("KeyCode", detail.Detail.Message + $" : Token : { token.KeyCode }");
            }
            catch (Exception ex)
            {
                failedTokens.Add(token);
                ModelState.AddModelError("KeyCode", ex.Message + $" : Token : { token.KeyCode }");
            }
        }

        if (failedTokens.Count == 0)
        {
            SearchCache.UpdateCache(typeof(Token), User.BrowsingClientId);
            return RedirectToAction(...);
        }
        else
        {
            ModelState.AddModelError("", "Tokens couldn't be added");
            bulkTokenCreateModel.Tokens = failedTokens;
            return View(..., bulkTokenCreateModel);
        }
    }
Oliver
  • 162
  • 2
  • 10
  • Any reason why in this line: `@Html.TextBoxFor(model => Model.Tokens[tokenCounter].KeyCode, new { @class = "form-control" })` the `M` in `Model.Tokens` is capitalized when you're using lambda expression? What happens if you change that line to `@Html.TextBoxFor(model => model.Tokens[tokenCounter].KeyCode, new { @class = "form-control" })`? – Grizzly Feb 22 '17 at 17:06
  • Hi, thank you for your response, I have tried that just now and it still is displaying a different value in the text box to what it should be. – Oliver Feb 22 '17 at 17:15
  • Sorry the question at the beginning of your comment didn't register first time I read it. There's no particular reason for the capitalised M, and going from my test just now, it doesn't seem to make a difference. Good spot though, as my eyes glossed over that. – Oliver Feb 22 '17 at 17:18
  • I am going to post an answer.. it might not be right, but it will help visually instead of trying to do this via comments. – Grizzly Feb 22 '17 at 17:19
  • Can you please post the code for your Token Class - or at least the properties – James S Feb 22 '17 at 17:44
  • Hi James, have posted the relevant properties, it's only a simple class without any methods. – Oliver Feb 22 '17 at 17:54
  • can you add the code for your action that how are you getting failedTokens – Usman Feb 22 '17 at 18:05
  • Added code for the action in the controller now – Oliver Feb 22 '17 at 18:16
  • 1
    i have tried it and yes you are right its populating the wrong value in textbox – Usman Feb 22 '17 at 18:38
  • Whew, I'm not going crazy then, so at least you can replicate the issue. – Oliver Feb 22 '17 at 18:40

1 Answers1

1

it happens because of HTML helpers they first look at the ModelState when binding their values and then in the model.You have to use ModelState.Clear(); to clear the values from ModelState if you are changing the values of model in controller.so your code will be like

 public ActionResult ScanTokensComplete(BulkTokenCreateModel bulkTokenCreateModel)
    {
        ModelState.Clear();
        var businessLayer = BusinessLayerManager.Current;
        var failedTokens = new List<Token>();

        foreach (var token in bulkTokenCreateModel.Tokens)
        {
            try
            {
                token.DateAdded = DateTime.Now;
                token.Enabled = true;

                var addedToken = businessLayer.TokenAdd(token);
            }
            catch (FaultException<ArgumentNullFault> detail)
            {
                failedTokens.Add(token);
                ModelState.AddModelError(token.KeyCode, detail.Detail.Message + $" : Token : { token.KeyCode }");
            }
            catch (Exception ex)
            {
                failedTokens.Add(token);
                ModelState.AddModelError(token.KeyCode, ex.Message + $" : Token : { token.KeyCode }");
            }
        }

        if (failedTokens.Count == 0)
        {
            SearchCache.UpdateCache(typeof(Token), User.BrowsingClientId);
            return RedirectToAction(...);
        }
        else
        {
            ModelState.AddModelError("", "Tokens couldn't be added");
            bulkTokenCreateModel.Tokens = failedTokens;
            return View(..., bulkTokenCreateModel);
        }
    }

and your view will be

 <div class="form-group">
            @Html.ValidationMessage(Model.Tokens[tokenCounter].KeyCode)
            @Model.Tokens[tokenCounter].KeyCode
            @Html.TextBoxFor(model => Model.Tokens[tokenCounter].KeyCode, new { @class = "form-control" })
        </div>

in addition i have added token.KeyCode instead of "KeyCode" because it will specify exactly which token is causing the error

Usman
  • 4,615
  • 2
  • 17
  • 33
  • Hi, thank you for your answer, I've had to leave the office for the day now, but that looks like that could be the issue. Will test as soon as I can, latest tomorrow afternoon, and award you with the sweet karma if so. Thanks again – Oliver Feb 22 '17 at 18:50
  • you are doing one more mistake in validation let me update my answer – Usman Feb 22 '17 at 18:55