8

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?

Adrian Grigore
  • 33,034
  • 36
  • 130
  • 210

3 Answers3

15

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.

Ivan Korytin
  • 1,832
  • 1
  • 27
  • 41
2

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();
}
gdoron
  • 147,333
  • 58
  • 291
  • 367
Adrian Grigore
  • 33,034
  • 36
  • 130
  • 210
  • 2
    This isn't a good idea, because you're adding code to facilitate tests into production code. It also gets executed on every request, and it's completely unnecessary. – Jeff Putz Jul 16 '12 at 21:05
  • 3
    Why not just use the "ValueProvider = ..." line in the setup of your controlller in your unit tests. – JoelFan Nov 28 '12 at 21:06
1

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

Community
  • 1
  • 1
bmancini
  • 2,028
  • 15
  • 21
  • I guesst almost as much, but which value exactly do I need to mock? And how can I do this? This seems to be a pretty common scenario... – Adrian Grigore Nov 12 '10 at 15:55
  • A common scenario for brand new code isn't that common. ;) Why don't you include your current mocks and then we could point out likely additions. Are you mocking the header? – John Farrell Nov 12 '10 at 16:05
  • Yeah doesn't look like the MVC3 source is available yet... that could make things bit more challenging. I'll load up Reflector and see what I can find. – bmancini Nov 12 '10 at 16:16
  • If you're unit testing a method which calls UpdateModel, you should set the Controller.ValueProvider property manually before running the test. I'd recommend setting it to an instance of SimpleValueProvider, which is just a fancy NameValueCollection. – Levi Nov 12 '10 at 18:05
  • I had to add both a ValueProvider and a ControllerContext – Breandán Dalton Feb 16 '16 at 14:30