ASP.NET MVC, strongly typed views, partial view parameters glitch
Asked Answered
W

4

22

If i got view which inherits from:

System.Web.Mvc.ViewPage<Foo>

Where Foo has a property Bar with a type string
And view wants to render strongly typed partial view which inherits from:

System.Web.Mvc.ViewUserControl<string>  

like this:

Html.RenderPartial("_Bar", Model.Bar);%>

Then why it will throw this:

The model item passed into the dictionary is of type 'Foo'
but this dictionary requires a model item of type 'System.String'.

when bar is not initialized?

More specific: why it passes Foo, where it should pass null?

Woebegone answered 26/6, 2009 at 13:7 Comment(3)
What is your code for generating the partial view?Impound
I have the same problem, but with my own type, not System.String, so there's no "default" to use instead. Perhaps I should make this a non-strongly typed partial view instead, and check the type passed?Tortuous
@Dave, i didn't have a problem - i knew it works like that. In your case - just check for null before you render anything (creating RenderPartialIfNotNull htmlhelper method might be handy).Woebegone
H
35

As @Dennis points out, if the model value is null, it will use the existing model from the view. The reason for this is to support the ability to call a partial view using a signature that contains only the partial view name and have it reuse the existing model. Internally, all of the RenderPartial helpers defer to a single RenderPartialInternal method. The way you get that method to reuse the existing model is to pass in a null value for the model (which the signature that takes only a view name does). When you pass a null value to the signature containing both a view name and a model object, you are essentially replicating the behavior of the method that takes only the view name.

This should fix your issue:

<% Html.RenderPartial( "_Bar", Model.Bar ?? string.Empty ) %>
Heterogenetic answered 26/6, 2009 at 13:36 Comment(6)
Works specifically because Foo is of type string. Obviously, that's what the question asked about. =) But for future reference (how I found this page), if Foo is a complex type (in my case), you basically wrap the entire Html.RenderPartial call within a check to see if Foo is non-null. Just an FYI!Blackhead
@Blackhead -- or just use the null coalesing operator with the second operand a suitable default for the model type, e.g., new Foo()Heterogenetic
This does work for strings but when you use other, complex type you'll need to specify a new ViewDataDictionary() { Model = Model.Bar } as the third parameter to RenderPartial.Tacet
...Which does NOT work if you also need to preserve dictionary content other than the model. I see no obvious solution for that case. Anyone else?Granite
@KjellRilbe many times I create a separate ViewDataDictionary for the view if I need to add additional properties. It wouldn't take much effort to write a helper that takes a collection of properties to propagate from the current ViewData into such a dictionary to which you could then add additional items and/or modify the model.Heterogenetic
@tvanfosson: I wrote a simple foreach loop that copies all KeyValuePairs from this.ViewData to the newly created ViewDataDictionary containing the correct Model type. Works...Granite
S
9

Look at ASP.NET MVC source (HtmlHelper.cs -> RenderPartialInternal method -> line 258):

...

if (model == null) {
    if (viewData == null) {
        newViewData = new ViewDataDictionary(ViewData);
    }

...

this is exactly your case. ASP.NET MVC uses the ViewData from your ViewContext

UPDATED:

Try this instead:

<% Html.RenderPartial("_Bar", Model.Bar ?? "Default" ); %>
Spavined answered 26/6, 2009 at 13:35 Comment(3)
+1 for "Look at ASP.NET MVC source" aspnet.codeplex.com/sourcecontrol/changeset/view/… Taking a look at RenderPartialExtensions.cs will also help. aspnet.codeplex.com/sourcecontrol/changeset/view/…Mongolian
Sorry, eu-ge-ne, but i can't accept your answer either. See comment at Dennis post for details. :)Woebegone
For ASP.NET MVC source: github.com/aspnet/AspNetWebStack/blob/…Unilobed
M
4

If you pass null as the model to RenderPartial, then it will look at the original model, which is why the error says foo.

You'll need to make sure that bar is initialized to be an empty string instead of null.

Edit: @Arnis, look at the source code. It doesn't lie. You are passing null to the overload of RenderPartial. You are not passing Foo. Internally, the system uses the Model from your page's ViewContext (which is Foo) when you pass a null Bar to RenderPartial.

Mongolian answered 26/6, 2009 at 13:21 Comment(2)
This answer seems wrong to me. Question was: "why it passes Foo, where it should pass null?" and not "Does it passes Foo instead of null?".Woebegone
@Dennis sure it does not lie, but it does not answer to question WHY it needs that. tvanfosson named the reason. Thanks anyway. :)Woebegone
T
4

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 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.

Tacet answered 21/4, 2012 at 11:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.