简体   繁体   中英

C# Class Instance Holding String Reference But Not Object Reference

Alright, so I have spend LITERALLY five or six hours working on this to no avail. I'm working in C# with ASP.NET MVC 4 and EF 5.0, I believe. Here's my problem: I have a method in my controller takes a custom class as a parameter (these names have been candy-themed obfuscated :P). The ViewError class looks like so:

public class ViewError<T> where T : class
{
    public T ModelObject { get; set; }
    public string Message { get; set; }

    public ViewError(string message, T modelObject)
    {
        Message = message;
        ModelObject = modelObject;
    }

    public ViewError()
    {
        Message = null;
        ModelObject = default(T);
    }
}

(It's a generic so I can figure out what type the ModelObject is from within the code. It's irrelevant, though; I've already tested it taking a generic 'object' and the same problem happens.)

The controller method looks like this. All it does is ask who is eating the candy, and if there's a ViewError, it displays the message. (The view has a paragraph that displays the ViewBag.Message).

public ActionResult EatCandy(ViewError<EatCandyViewModel> error)
{
    ViewBag.BrandList = new SelectList(db.Brands, "ID", "Name");
    // If there's a notification, pass it to the view, along with the model
    if(error.Message != null)
    {
        ViewBag.Message = error.Message;
        return View(error.ModelObject);
    }
    return View();
}

On the post:

[HttpPost]
public ActionResult EatCandy(EatCandyViewModel viewModel)
{
    if(ModelState.IsValid)
    {
        CandyEater eater = (CandyEater)viewModel;
        db.CandyEaters.Add(eater);
        db.SaveDatabase(); // Custom convenience wrapper for SaveChanges()
        return RedirectToAction("ChooseCandyToEat", eater);
    }
    return View(viewModel);
}

Pretty standard stuff. Now, in the ChooseCandyToEat method, it brings up a list of candy available to eat of a specific brand. If that brand doesn't have any available candy, I want it to send an error back to the EatCandy method (via ViewError object) telling that eater that they have no candy to eat, and send the model back so that the eater doesn't have to type in their info again, merely select a different brand of candy.

public ActionResult ChooseCandyToEat(CandyEater eater)
{
    // Get the list of candy associated with the brand.
    IEnumerable<Candy> candyList = db.Candies.Where(b => b.Brand == eater.DesiredBrand)
                                             .Where(e => !e.Eaten);
    // If the brand has no candy, return an error message to the view
    if(candyList.Count() == 0)
    {
        EatCandyViewModel viewModel = (EatCandyViewModel)eater;
        ViewError<EatCandyViewModel> viewError = new ViewError<EatCandyViewModel>("Oh noes! That brand has no candy to eat!", viewModel.Clone()); // This is a deep clone, but even if it wasn't, it would still be weird. Keep reading.
        return RedirectToAction("EatCandy", viewError);
    }
    return View(candyList);
}

According to my understanding, this should all work. Now here's the weird part - I can confirm, through debug messages and the Watch window (using Visual Studio) that the ViewError is created properly and holds a deep clone of the viewModel in its ModelObject (I have to convert it back because the EatCandy method expects an EatCandyViewModel as a parameter). However, when I step forward and the ViewError is passed to EatCandy, the ModelObject within it is null! I originally thought this was because it only passed a reference of viewModel into the object and it was getting garbage collected, which is why I added the Clone() method. The string, which is also a reference type, is getting through okay, though. Why isn't the object? Does anyone know?

If you need more info, just ask. And disregard the ridiculousness of this database - I obfuscated it intentionally, not just to be silly.

This won't work. RedirectToAction results in a 302 to the browser which just is not capable of carrying complicated models.

There are some alternatives, you could use the TempData to store the model, do the redirect and retrieve the data. You could probably even call the EatCandy action directly instead of redirecting, however the auto matching the view won't work and you'd have to force the "EatCandy" view name at the end of the EatCandy action.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM