203

I have a page:

<%@ Page Inherits="System.Web.Mvc.View<DTOSearchResults>" %>

And on it, the following:

<% Html.RenderPartial("TaskList", Model.Tasks); %>

Here is the DTO object:

public class DTOSearchResults
{
    public string SearchTerm { get; set; }
    public IEnumerable<Task> Tasks { get; set; }

and here is the partial:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<Task>>" %>

When Model.Tasks is not null, everything works fine. However when its null I get:

The model item passed into the dictionary is of type 'DTOSearchResults' but this dictionary requires a model item of type 'System.Collections.Generic.IEnumerable`1[Task]'.

I figured it must not know which overload to use, so I did this (see below) to be explicit, but I still get the same issue!

<% Html.RenderPartial("TaskList", (object)Model.Tasks, null); %>

I know I can work around this by checking for null, or not even passing null, but that's not the point. Why is this happening?

John Saunders
  • 160,644
  • 26
  • 247
  • 397
Andrew Bullock
  • 36,616
  • 34
  • 155
  • 231

7 Answers7

355

Andrew I think the problem you are getting is a result of the RenderPartial method using the calling (view)'s model to the partial view when the model you pass is null.. you can get around this odd behavior by doing:

<% Html.RenderPartial("TaskList", Model.Tasks, new ViewDataDictionary()); %>

Does that help?

meandmycode
  • 17,067
  • 9
  • 48
  • 42
  • If you you look through the source code/use reflector you can see why the madness happens. Ended up making my own RenderControl extension method that works sensibly. – Andrew Bullock Mar 16 '11 at 23:17
  • 3
    I get why they support null model and passing the pages Model but couldn't they have handled that by overloading. @Html.Render("donkeys") is different than @Html.Render("donkeys", couldbenull) – Phil Strong Mar 25 '11 at 17:52
  • I think even the simple behaviour of only doing model pass through if the model is castable would keep both arguments happy – meandmycode Mar 25 '11 at 18:05
  • 19
    I find this very counterintuitive so I added an "issue", vote on it if you agree: http://aspnet.codeplex.com/workitem/8872 – pbz Jun 28 '11 at 22:26
  • Instead of performing this workaround, I would recommend refactoring so that the view always gets the correct type. You can use the Null Object Pattern, for example. Or pass an Enumerable.Empty() when you find that Model.Tasks is null. – Shashi Penumarthy Aug 24 '12 at 12:49
  • Sequences I agree should be empty vs null, but null objects aren't a good solution for single entities. – meandmycode Aug 24 '12 at 18:12
  • 3
    I found that with this solution my ValidationSummary in my partial view did not work because the ViewData of the primary model was lost in the partial view. I used the answer given here http://stackoverflow.com/a/12037580/649497 to solve this. – BruceHill Jul 27 '13 at 09:20
  • 5
    You should pass along the existing ViewData: new ViewDataDictionary(ViewData) – ScottE Jul 24 '14 at 18:08
  • This no longer works in dotnet core 2, as ViewDataDictionary no longer has a constructor with 0 parameters. I have solved the problem by passing null indirectly by accessing a non existing property on the ViewData, as such: @Html.Partial("PartialView", ViewData["null"] ). My partial view checks for @Model == null and correctly doesn't render anything. I have no idea why this works. ViewData["null"] evaluates to null. So why won't it work when directly passing null? Can't believe this problem still exists in dotnet core 2. Oh well! – Jay Mar 19 '18 at 17:05
  • It probably works because making use of ViewData in the second parameter, makes it use another overload of the Partial method (name, viewdata), which takes no model. So explicitly passing a model that is null is not okay. But passing no model at all, causing the partial's model to be null, is totally okay! – Jay Mar 19 '18 at 17:37
48

@myandmycode's answer is good, but a slightly shorter one would be

<% Html.RenderPartial("TaskList", new ViewDataDictionary(Model.Tasks)); %>

This works because the ViewDataDictionary is the thing that holds the model, and it can accept a model as a constructor parameter. This basically passes an "entire" view data dictionary, which of course only contains the possibly-null model.

configurator
  • 40,828
  • 14
  • 81
  • 115
  • Passing a null value to the ViewDataDictionary constructor will throw an ArgumentNullException. – Joel McBeth Jul 26 '11 at 20:23
  • 2
    @jcmcbeth: Erm, no, it doesn't... I've used this exact code with nulls successfully. – configurator Jul 27 '11 at 12:55
  • 1
    @jcmcbeth: Are you using `new ViewDataDictionary(null)`? Because that would pick a different overload, one with a `ViewDataDictionary` parameter, which probably wouldn't accept nulls. – configurator Jul 27 '11 at 12:56
  • 1
    It would appear that using a ViewBag property causes the wrong constructor to be called. How it takes a dynamic type and assumes it's a ViewDataDictionary over an object doesn't make sense to me, but it appears to be what it is doing. You will have to cast it to an object for it to select the correct constructor. – Joel McBeth Jul 27 '11 at 15:15
  • 1
    @jcmcbeth: Calling it over a dynamic type uses the same as if you've given the actual value; if the value is `null`, that's the same as calling `new ViewDataDictionary(null)` which causes the most specific overload to be called. – configurator Jul 28 '11 at 14:17
  • 2
    if you use it like this, the dictionairy error is gone.. `Html.RenderPartial("TaskList", new ViewDataDictionary(model: Model.Tasks))` You are using the wrong constructor if its null. – Filip Cornelissen Oct 08 '15 at 12:47
29

It appears that when the property of the Model you're passing in is null MVC intentionally reverts back to the "parent" Model. Apparently the MVC engine interprets a null model value as intent to use the previous one.

Slightly more details here: ASP.NET MVC, strongly typed views, partial view parameters glitch

Community
  • 1
  • 1
Zack
  • 299
  • 3
  • 4
  • 1
    +1 for actually trying to explain the issue, and not just treating this as a weird behaviour – YavgenyP Nov 25 '12 at 14:18
  • Yep this was happening to me and the above didn't fix it, it just gave me a bit more information on my actual error. – Canvas Jul 01 '15 at 13:09
21

If you do not want to loose your previous ViewData in the partial view, you could try:

<% Html.RenderPartial("TaskList", Model.Tasks, new ViewDataDictionary(ViewData){Model = null});%>
Fran P
  • 321
  • 2
  • 3
  • 1
    This doesn't seem to answer the question. – John Saunders Aug 20 '12 at 14:21
  • 7
    +1 Actually it does work. It is basically the same idea presented here http://stackoverflow.com/a/713921/649497 but overcomes a problem with that answer and that is that the ViewData will go missing if you instantiate the ViewDataDictionary with an empty constructor. I first solved this problem with the accepted solution and then found that my ValidationSummary did not work in the partial view. This solution solved that for me. This answer needs more recognition for solving the problem and preserving ViewData in your partial view. – BruceHill Jul 27 '13 at 09:11
  • 1
    @Franc P this actually worked without losing the ViewBag values and hence passed a null model. Thanks. – Zaker Nov 30 '15 at 14:33
  • This is the right answer if you need ViewBag access in your Partials! – Daniel Lorenz Jun 20 '17 at 14:18
12

A solution would be to create a HtmlHelper like this:

public static MvcHtmlString Partial<T>(this HtmlHelper htmlHelper, string partialViewName, T model)
{
    ViewDataDictionary viewData = new ViewDataDictionary(htmlHelper.ViewData)
    {
        Model = model
    };
    return PartialExtensions.Partial(htmlHelper, partialViewName, model, viewData);
}

The Partial<T>(...) matched before the Partial(...) so convenient and no ambiguity error when compiling.

Personally I find it difficult to understand the behaviour - seems hard to imagine this as design choice?

Colin Breame
  • 1,367
  • 2
  • 15
  • 18
  • 1
    this is what I did in the end. there aren't many design choices/behaviours in asp.net mvc what make any sense. since abandoned it. helpful to others tho, so have a +1 – Andrew Bullock Nov 30 '11 at 10:06
  • Good one, however unclear for the user. Let's say I'm used to what my colleage uses in his project, I start a fresh one. Then totally forget to add this overload and voilla, the exceptions start happening in production because we didn't test it well enough. A different name is beter imho. – Jaap Apr 21 '12 at 21:18
  • Some tools will fail to hyperlink to the view with this extension method in place. – Myster Sep 15 '20 at 02:01
11

Though this has been answered, I ran across this and decided I wanted to solve this issue for my project instead of working around it with new ViewDataDictionary().

I created a set of extension methods: https://github.com/q42jaap/PartialMagic.Mvc/blob/master/PartialMagic.Mvc/PartialExtensions.cs
I also added some methods that don't call the partial if the model is null, this will save a lot of if statements.

I created them for Razor, but a couple of them should also work with aspx style views (the ones that use HelperResult probably aren't compatible).

The extension methods look like this:

@* calls the partial with Model = null *@
@Html.PartialOrNull("PartialName", null)
@* does not call the partial if the model is null *@
@Html.PartialOrDiscard("PartialName", null)

There are also methods for IEnumerable<object> models and the discard ones can also be called with a Razor lambda that allow you to wrap the partial result with some html.

Feel free to use them if you like.

Jaap
  • 3,081
  • 2
  • 29
  • 50
1

My workaround to this is:


<% Html.RenderPartial("TaskList", Model.Tasks ?? new List()); %>

h3n
  • 5,142
  • 9
  • 46
  • 76
  • This is a dirty solution. On your partial view you should be able to check for null Model, rather than checking if list has any values and if it's null. – madd Jan 05 '18 at 13:16