Object reference error

Feb 20, 2012 at 3:34 PM

Matt,

As you suggested I am posting some code here. Thanks for taking the time. Sorry I cannot roll this up into a project to send to you - this is because of the fact that I'm using OrchardCMS and the code is housed inside a module (plus the fact that I've rearranged selector and model code to take into account how my module in OrchardCMS is set up).

Bear in mind that my controller code is completely different. I've tried to remedy that but to no avail.

I have placed most of the MVCGrabBag "Helper" folder code into my modules own "Helper" folder, but have also imported Selector.cs and SelectorAttribute.cs code from "Selectors" folder into my "Helper" folder.

I changed your DisplayMode.cs and DisplayModeOptions.cs code found in the "Models" folder to fit my needs, and placed them in one file in my "ViewModels" folder (the name of my file is BankruptcyData.cs). I also changed your DisplayModeSelectorAttribute.cs code found in the "Selectors" folder to fit my needs and placed it in the same BankruptcyData.cs file.

Here is my controller:

namespace ClientSpace.Inquiry.Bankruptcy
{
[OrchardFeature("ClientSpace.Inquiry.Bankruptcy")]
    [ValidateOnlyIncomingValues, Themed]
    public class BankruptcyController : Controller
    {
        #region IBankruptcyMailer
        private IBankruptcyMailer _bankruptcyMailer = new BankruptcyMailer();
        public IBankruptcyMailer BankruptcyMailer
        {
            get { return _bankruptcyMailer; }
            set { _bankruptcyMailer = value; }
        }
        #endregion

        private BankruptcyData bankruptcyData;

        #region SERIALIZATION
        protected override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            var serialized = Request.Form["bankruptcyData"];
            if (serialized != null) //Form was posted containing serialized data
            {
                bankruptcyData = (BankruptcyData)new MvcSerializer().Deserialize(serialized, SerializationMode.Signed);
                TryUpdateModel(bankruptcyData);
            }
            else
                bankruptcyData = (BankruptcyData)TempData["bankruptcyData"] ?? new BankruptcyData();
        }

        protected override void OnResultExecuted(ResultExecutedContext filterContext)
        {
            if (filterContext.Result is RedirectToRouteResult)
                TempData["bankruptcyData"] = bankruptcyData;
        }
        #endregion
      

        //
        // EMail Quote
        # region EMailQuote
        //
        // STEP 1:
        // Solutions/Quote/EMail/BasicDetails
        public ActionResult EMailQuoteBasicDetails(string nextButton)
        {
            if ((nextButton != null) && ModelState.IsValid)
                return RedirectToAction("EMailQuoteConfirm");
            return View("EMailQuote/BasicDetails", bankruptcyData);
        }
        //Test
        //
        //NEED TO ADD MORE STEPS
        //
        //
        // STEP 2:
        // Solutions/Quote/EMail/Confirm
        public ActionResult EMailQuoteConfirm(string backButton, string nextButton)
        {
            //***THIS IS THE VIEW I AM TRYING TO POST TO***
            if (backButton != null)
                return RedirectToAction("EMailQuoteBasicDetails");
            else if (nextButton != null)
                return RedirectToAction("EMailQuoteSubmitted");
            else
                return View("EMailQuote/Confirm", bankruptcyData);
        }
        //
        // STEP 3:
        // Solutions/Quote/EMail/Submitted
        public ActionResult EMailQuoteSubmitted()
        {
            // Todo: Save bankruptcyData to database; render a "Submitted" view
            BankruptcyMailer.EMailQuote(bankruptcyData).Send();
            return View("EMailQuote/Submitted", bankruptcyData);
        }
}

I have only post the relevant part. I am using MVCMailer as well inside the OrchardCMS module. That code is not really relevant until I "Submit" (you can see BankruptcyMailer in step 3 (this was just put there for context). Step 1 would contain the checkbox. This works, and I have a drop down and check box list on that step.

It even works posting to step 2 (the "Confirm" step I am trying to post to). As I mentioned in another reply post here if I do @Model.RealEstateDropDown (which is what I named this drop down - see model code below), then it will post the selected value. This is what is throwing the error if I hit the "Back" button on the wizard (if I hit back on the browser it works fine to show what I selected). As I mentioned on your blog, if I comment that "else" statement in the Selector.cshtml code, then I can hit "Back" on the browser, its just that the selected value is not retained.

Here is my model:

namespace ClientSpace.Inquiry.ViewModels
{
    [OrchardFeature("ClientSpace.Inquiry.Bankruptcy")]
    [Serializable]
    public class BankruptcyData
    {
        public enum RealEstate
        {
            IDoNotOwnAnyRealEstate,
            IOwnOneProperty,
            IOwnTwoProperties,
            IOwnThreeOrMoreProperties,
        }

        //
        //
        //[Required(ErrorMessage = "Please tell us whether or not you own any real estate")]
        [Required]
        [Display(Name = "Do you own any real estate property (e.g., a house, co-op apartment, condominimum, etc.")]
        [RealEstateSelector(BulkSelectionThreshold = 0)]
        public RealEstate? RealEstateDropDown { get; set; }
        
        //
        //
        //[Required(ErrorMessage = "Please select at least one")]
        [Required]
        [Display(Name = "Why are you thinking about filing for bankruptcy (select all that apply)?")]
        [RealEstateSelector]
        public List<RealEstate> RealEstateCheckBoxes { get; set; }
        
        //
        //
        public class RealEstateSelectorAttribute : SelectorAttribute
        {
            public override IEnumerable<SelectListItem> GetItems()
            {
                return Selector.GetItemsFromEnum<RealEstate>();
            }
        }
    }
}

Again, I am just posting the relevant parts. Again, my views work but my code is different than what you had because of the wizard.

Here is the "Basic Details" step view code:

                @using (Html.BeginFormAntiForgeryPost())
                    { 
                        @Html.Hidden("bankruptcyData", new MvcSerializer().Serialize(Model, SerializationMode.Signed))
                        <div class="wizard-container">
                            <div class="wizard-step">
                                <div class="wizard-header">
                                    <h3>
                                        Step 1: Basic Details
                                    </h3>
                                    <p>
                                        Please enter your details below.
                                    </p>
                                </div>
                                <div class="wizard-content">
                                    <div class="form_table">
                                        <table>
                                            <tr>
                                                <td width="40%">
                                                    @Html.LabelFor(m => m.RealEstateDropDown)
                                                </td>
                                                <td width="60%" class="select_cell">
                                                    
                                                    @Html.FullFieldEditor(m => m.RealEstateDropDown)
                                                </td>
                                            </tr>
                                            <tr>
                                                <td width="40%">
                                                    @Html.LabelFor(m => m.RealEstateCheckBoxes)
                                                </td>
                                                <td width="60%" class="select_cell">
                                                   @Html.FullFieldEditor(m => m.RealEstateCheckBoxes)
                                                </td>
                                            </tr>
                                        </table>
                                    </div>
                                </div>
                            </div>
                            <div class="wizard-navigation">
                                <input type="submit" name="nextButton" value="Next" />
                            </div>
                        </div>
                    }

Not sure if I left anything out. Sorry for the long post and a big THANKS for trying to help out.

Feb 20, 2012 at 4:02 PM

And just for context, this is the code that is throwing the error whenever I hit "Back" on the wizard

    // Loop through the items and make sure they are Selected if the value has been posted
    if(Model != null)
    {
        foreach (var item in selectorModel.Items)
        {
            if (supportsMany)
            {
                var modelStateValue = GetModelStateValue<string[]>(Html, fieldName) ?? ((IEnumerable)Model).OfType<object>().Select(m => m.ToString());
                item.Selected = modelStateValue.Contains(item.Value);
            }
            else
            {
                //If below code is taken out then "Object reference" error is not thrown, however, the posted values for a drop down list (or any single item input?) will not be posted back.
                var modelStateValue = GetModelStateValue<string>(Html, fieldName);
                item.Selected = modelStateValue.Equals(item.Value, StringComparison.OrdinalIgnoreCase);
            }
        }
    }

Coordinator
Feb 20, 2012 at 11:52 PM

Hey rem just curious, was this fixed as per your Edit on the other message saying you got it working?

Feb 21, 2012 at 1:43 AM

No, the other post just had to do with passing the values to another page, i.e., how to display the results.

Mar 2, 2012 at 6:33 PM

I ran into this same problem.

The problem happens when using the selector on a value that has been set, but not from a form post. For example, you set the value of an Enum in the get method of a controller.  At that point, the if(Model != null) check returns true, however the following line will return null (since there is no ModelState for the value) 

              var modelStateValue = GetModelStateValue<string>(Html, fieldName);

You should be able to recreate the error by setting the DropDown property in the Index() method of SelectorController.

var model = new DisplayModeOptions() { DropDown = DisplayMode.HomePage };

 

If you don't have ModelState for the item, you have to get the value directly from the model. As a quick fix, I changed the else block to the following:

 

            else
            {
                var modelStateValue = GetModelStateValue<string>(Html, fieldName);
                if(modelStateValue != null)
                {
                    item.Selected = modelStateValue.Equals(item.Value, StringComparison.OrdinalIgnoreCase);
                }
                else
                {
                    Type modelType = Model.GetType();
                    if (modelType.IsEnum)
                    {
                        item.Selected = item.Value == Model.ToString();
                    }
                }
            }

 

 

Mar 3, 2012 at 3:06 PM
Edited Mar 3, 2012 at 3:07 PM

@ryan_h,

That works great. Thanks for the fix. 

Just to note: I was not setting any values in my controller. I suspected it was my use of Serializer (part of MVC Futures 3). However, your underlying logic is correct. Since the serialization was creating a hidden input to pass data from previous controllers in a wizard it acted like a value being set but not from the current controllers post method.

Glad to see other people using this project. It's really nice to have one set of tools to do this stuff rather than a hodge podge of different code for these inputs.