Adrian Grigore
Adrian Grigore

Reputation: 33318

TryUpdateModel throws NullReferenceException in ASP.NET MVC 3 unit test

Since I upgraded from MVC 2 to MVC 3 RC, using TryUpdateModel causes a NullReferenceException. This problem only occurs when running my action method as part of a unit test. Running it on the actual server works as expected.

Here's a stack trace of the exception:

System.NullReferenceException: Object reference not set to an instance of an object. at System.Web.Mvc.JsonValueProviderFactory.GetValueProvider(ControllerContext controllerContext) at System.Web.Mvc.ValueProviderFactoryCollection.<>c_DisplayClassc.b_7(ValueProviderFactory factory) at System.Linq.Enumerable.WhereSelectEnumerableIterator2.MoveNext() at System.Linq.Enumerable.WhereSelectEnumerableIterator2.MoveNext() at System.Collections.Generic.List1..ctor(IEnumerable1 collection) at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source) at System.Web.Mvc.ValueProviderFactoryCollection.GetValueProvider(ControllerContext controllerContext) at System.Web.Mvc.Controller.TryUpdateModel[TModel](TModel model, String prefix)
... my own code from here on....

In case it matters, my controller has the following signature:

[AcceptVerbs(HttpVerbs.Post)]
public virtual ActionResult Edit(int id, FormCollection collection)
{
}

My guess is that this has to do with the new way DI works in MVC3, but I can't figure out what I'm doing wrong. Perhaps there is something in terms of DI setup that is required in MVC 3, but wasn't required in MVC 2?

Upvotes: 8

Views: 2423

Answers (3)

Adrian Grigore
Adrian Grigore

Reputation: 33318

In case someone else has the same problem and finds this post:

I solved the problem generically based on Ivan Kortym's answer (thanks!), with the following piece of code in my controller base class constructor:

if (Request!=null && Request.Form != null && Request.Form.HasKeys() && ValueProvider == null)
{
    ValueProvider = new FormCollection(Request.Form).ToValueProvider();
}

Upvotes: 2

Ivan Korytin
Ivan Korytin

Reputation: 1842

You should add this code:

 FormCollection formValues = new FormCollection() 
        {
            { "Test", "test" },
            { "FirstName", "TestName" } 
        };
        rootController.ValueProvider = formValues.ToValueProvider();

I have the same problem and this code helps me.

Upvotes: 15

bmancini
bmancini

Reputation: 2028

It's probably a change in implementation of System.Web.Mvc.JsonValueProviderFactory.GetValueProvider that is hitting a value in ControllerContext that is null.

You may need to mock an additional value in ControllerContext.

At least that's where I'd look first.

EDIT

Yeah, looks like it's doing a null check on controllerContext.

public override IValueProvider GetValueProvider(ControllerContext controllerContext)
{
    if (controllerContext == null)
    {
        throw new ArgumentNullException("controllerContext");
    }
    object deserializedObject = GetDeserializedObject(controllerContext);
    if (deserializedObject == null)
    {
        return null;
    }
    Dictionary<string, object> backingStore = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
    AddToBackingStore(backingStore, string.Empty, deserializedObject);
    return new DictionaryValueProvider<object>(backingStore, CultureInfo.CurrentCulture);
}

From the stacktrace we can see that TryUpdateModel[TModel](TModel model, String prefix). Using reflector, it is accessing the ControllerBase ValueProvider property. This in turn calls ValueProviderFactoryCollection.GetValueProvider(ControllerContext controllerContext) with the current Controllers ControllerContext property.

You should just be able to create a new ControllerContext instance and set the controller's property accordingly...

[TestMethod]
public void EditTest
{
    var controller = new Controller();         
    var controllerContext = new ControllerContext();

    controller.ControllerContext = controllerContext;

    controller.Edit(...);       
}

Some additional mocking may be required to get it to fully function though. Some info on how to fully mock ControllerContext: Mocking Asp.net-mvc Controller Context

Upvotes: 1

Related Questions