Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 457
Adrian Grigore Avatar asked Nov 12 '10 15:11

Adrian Grigore


3 Answers

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.

like image 102
Ivan Korytin Avatar answered Nov 15 '22 04:11

Ivan Korytin


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();
}
like image 31
Adrian Grigore Avatar answered Nov 15 '22 05:11

Adrian Grigore


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

like image 29
bmancini Avatar answered Nov 15 '22 05:11

bmancini