Rendering partial views with a model and htmlhelper - asp.net-mvc

ASP .NET MVC 1
I'd like to show a partial view base on a model, and I'd like that to have a fairly short command for that. So I saw a way of using both a HtmlHelper and a controller (And I'd use another controller for that, not the controller currently used).
But somehow it still gives an error, though I think the method starts to look as it should.
So what am I doing wrong? (If I call the method directly in the ASPX-page, it succeeds. But it should be possible to use a HtmlHelper for that).
public static void RenderPartialView(this HtmlHelper html, string action, string controller, object model)
{
var context = html.ViewContext;
RouteData rd = new RouteData(context.RouteData.Route, context.RouteData.RouteHandler);
rd.Values.Add("controller", controller);
rd.Values.Add("action", action);
rd.Values.Add("model", model);
IHttpHandler handler = new MvcHandler(new RequestContext(context.HttpContext, rd));
handler.ProcessRequest(System.Web.HttpContext.Current);
}
Part in the ASCX-page:
<% Html.RenderPartialView("Show", "Intro", Model.Intro); %>
Error given:
'System.Web.Mvc.HtmlHelper' does not contain a definition for 'RenderPartialView' and no extension method 'RenderPartialView' accepting a first argument of type 'System.Web.Mvc.HtmlHelper' could be found (are you missing a using directive or an assembly reference?)

Why don't you use Html.RenderPartial ? It's the correct way to render a partial view. No need to make another request.
<% Html.RenderPartial("Show", Model.Intro); %>
Your call does not succeed beacause when you use an extension method in a "non static" way (i.e, as if the method belongs to an instance), you must omit the first parameter. The correct call would be
<% Html.RenderPartialView("Show", "Intro", Model.Intro); %>
Hope it helps
Cédric

Add
<add namespace="Namespace-Of-RenderPartialView-Class"/>
to your web.config file.

With extension methods, you don't need to include the first argument (the "this HtmlHelper html"). That is handled by the compiler when using extension methods. It is inferred based on the object you're calling the method on.

Related

ASP.NET MVC Generic Partial View Pattern

I have a partial view that I want to be generic. According to this question, partial views cannot be generic. So I instead made an HtmlHelper extension that handles the pieces for which I want type-safety, then hands off the rest to a real partial view.
Usually my helper is called on page load, which works fine, but sometimes I want to add a row or something through AJAX. When this happens, the controller cannot use my "partial view" since it does not have access to the HtmlHelper.
Apart from having a partial view with a model of type object, is there anything I can do?
I'm using Razor, if that is important.
A simplified version of what I'm doing:
public static MvcHtmlString DoStuff<T>(this HtmlHelper html, IEnumerable<T> data,
Func<T, ViewModelType> StronglyTypedFn, string PartialName)
{
// the pre- and post-processing for the partial view is complex enough I'd like
// to encapsulate it. But I want the encapsulation to include the safety
// benefits that generics give.
var mappedData = data.Select(StronglyTypedFn);
string htmlData = "";
foreach(var model in mappedData){
htmlData += html.Partial(PartialName, model);
}
htmlData += "some boilerplate footer html";
return htmlData;
}
I realize that in this example I have so few lines of code outside the partial view that it seems pointless to have a helper, but in my real example it is more complex.
Now, in an ajax call I want to return Html.DoStuff(). But I can't, because this requires access to the HtmlHelper, and the helper isn't available inside a controller.
You could just have a simple action method that calls the partial for one model instance
public PartialViewResult Single(string partialName) {
return PartialView(partialName);
}
You could use a View with a Dynamic type instead of object.
But... It seems as if there's some misunderstanding here because the Controller shouldn't try to render the view at all. Could you post the Controller code?
The better option is, IMO, returning a JsonResult for your ajax request and adding the row/rows on client side using JS.

Detect Route Values in a View

Is it possible to detect a route value in a view?
Such as /pages/create/1 and I want to check to see if 1 is there?
Basically, I want to render a different partial view based on this value though I'm fairly sure this is probably not the best way to go about what I'm trying to achieve.
On a side note, instead of doing the above is it possible for me to be able to change what partial views are rendered in a view based on a value from within my controller?
ViewContext.RouteData.Values["whatever"]
You can inspect a RouteData object through ViewPage.ViewContext.RouteData. Then check for values using something like
string areaname = routeData.Values["area"] as string;
string controllername = routeData.Values["controller"] as string;
string actionname = routeData.Values["action"] as string;
string id = routeData.Values["id"] as string;
If you find that you want to inspect these values in the controller instead, you can access them using ControllerBase.ControllerContext.RouteData. Something similar applies for action filters etc.
Other answers are correct, but thought i'd address your last sentence:
On a side note, instead of doing the above is it possible for me to be able to change what partial views are rendered in a view based on a value from within my controller?
Well partial view's are rendered in the View itself (unless calling from JavaScript and binding directly to DOM) with the following code:
<%: Html.RenderPartial("SomePartial") %>
So to prevent "code soup" (if statements) in your view, you use a HTML helper which calls through to RenderPartial after inspecting the ViewContext:
public static string RenderCustomPartial(this HtmlHelper helper, RouteData rd)
{
string partialName;
if (rd.Values["SomeParam"] == 1)
partialName = "PartialOneName";
else
partialName = "PartialTwoName";
return helper.RenderPartial(partialName);
}
And then in the View:
<%: Html.RenderCustomPartial(ViewContext.RouteData) %>
You could make some mods to the above - like access the route data directly in the extension, pass through the model to bind in the partial, etc - but you get the idea.
Alternatively you could do the above IF statement in your controller, and stuff the partial view name in the ViewData, then use that in the regular RenderPartial call in your View.
Whatever floats your boat. :)

ASP.NET MVC: Custom Html Helpers in Razor

I am having difficulty with Html Helpers when used with Razor. Said helpers worked fine in MVC 2 with the web form view engine. But not in razor. The error I get at runtime is:
Compiler Error Message: CS1502: The best overloaded method match for 'System.Web.WebPages.WebPageExecutingBase.Write(System.Web.WebPages.HelperResult)' has some invalid arguments
Source Error:
Line 1: #using Wingspan.Web.Mvc;
Line 2: #Html.IncrementalMenu(MenuBlock.Site)
Expanding the Show Detailed Compiler Output reveals:
d:\...\Views\Shared\MenuTop.cshtml(2,1): error CS1502: The best overloaded method match for 'System.Web.WebPages.WebPageExecutingBase.Write(System.Web.WebPages.HelperResult)' has some invalid arguments
d:\...\Views\Shared\MenuTop.cshtml(2,7): error CS1503: Argument 1: cannot convert from 'void' to 'System.Web.WebPages.HelperResult'
That indicates to me that razor doesn't like my helper, IncrementalMenu, returning void (which works fine in MVC 2 web form engine views).
I get no errors at Compile time, although the line of code (#Html.IncrementalMenu(...)) is red underlined with the following message:
Cannot implicitly convert type 'void' to 'object'
IncrementalMenu is in the Wingspan.Web.Mvc namespace. It's signature is as follows:
public static void IncrementalMenu(this HtmlHelper html, MenuBlock menuBlock)
{
// Uses an HtmlTextWriter to render a menu from the sitemap
}
I'm blowed if I know what is wrong...
PS:
The MenuBlock parameter is just an enum that identifies how the menu should render. Don't fixate on this as that is fine.
You can call your helper like this:
#{ Html.IncrementalMenu(MenuBlock.Site); }
WebForms syntax
<% Html.IncrementalMenu(MenuBlock.Site); %>
You just call your method, and the return value (if there is any) is ignored.
Code like this expects a return value, and writes the return value to the html stream:
#Html.YourHelper()
Webforms syntax:
<%: Html.YourHelper() %>
The same, if result value != IHtmlString:
<%= Server.HtmlEncode(Html.YourHelper()) %>
Addendum:
You can get the same, or similar, error with #Html.RenderPartial. In this case it is due to the fact that RenderPartial renders directly to the Response, so is not a string and needs to be coded inside a "Razor code block":
#{
Html.RenderPartial(...);
}
I suspect that is one of the reasons that Microsoft have included in ASP.NET MVC the new Html.Partial. As Html.Partial does return a string, it is OK to write:
#Html.Partial
Which looks a lot better. Given that one of Razor's declared objectives is to be easy on the eye, this is quite likely true.
It also kind of makes me, at least, feel more comfortable. I know what returning a string is, I do it all the time. But "returning to the Response" requires a few more brain cycles every time I think it.
And it fits with the old adage that finally Microsoft get their products right in version 3. EG, Access 97.
Which is a depressing simile. Cos they screwed things up in version 4, ie, Access 2000...
Your HTML helper should return MvcHtmlString which represents the html in order to work properly with Razor (and other view engines that are not the WebFormsViewEngine)
public static MvcHtmlString Label(this HtmlHelper html, string expression)
{
return MvcHtmlString.Create("<label>" + expression + "</label>");
}

strongly typed class in my ViewUserControl not working?

I am getting an error in my viewuser control:
Could not load type 'System.Web.Mvc.ViewUserControl'
My viewpage passes the MyViewUserControllerUserList class in the RenderPartial call.
So I am doing:
action creates its strongly typed view data, which has a property which is a strongly typed class that my userlist.ascx expects.
userlist.ascx:
Inherits="System.Web.Mvc.ViewUserControl<MyViewUserControllerUserList>"
Am I doing this correctly?
Update
Just to make sure, my code for my strongly typed partial user control is:
public class MyViewUserControllerUserList: ViewUserControl
{
}
The syntax you have looks correct, however you need to make sure the namespace is incuded as well.
Inherits="System.Web.Mvc.ViewUserControl<My.Class.Namespace.ModelClass>"
Then you would pass it in like this
<% Html.RenderPartial("PartialName", InstanceOfModelClass); %>
Then access it inside the partial using the Model property.

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

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?
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 ) %>
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" ); %>
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.
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.

Resources