It seems that my understanding of the use of partial views is not quite right.
I am trying to add a partial view which builds from a database and use the partial view within a master page.
Using the NerdDinner project (wich is great for mvc) I have added the following:
Views\Shared\dinners.ascx" :
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<NerdDinner.Models.Dinner>>" %>
<ul>
<% foreach (var dinner in Model) { %>
<li>
<%= Html.Encode(dinner.Title) %>
</li>
<% } %>
</ul>
Within "Views\Shared\Site.Master" :
<%# Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %>
...
<% Html.RenderPartial("dinners"); %>
The above will only currently function on the pages where the dinners object is currently availabe as a list (eg site/Dinners). Is there a way to do this in an elegant and mvc way or is this something that requires a clever compromise?
Why do you have it in your master? I would add another place holder to your master and then add it where it makes sense. I feel that master pages should be as agnostic to specifics. If you want it be on all dinner pages, just make nested master of the original one.
This is, IMO, one of the biggest limitations of ASP.NET MVC - managing shared data across multiple views (next to rendering partial ascx views to strings!). If you google or search on stackoverflow for something like 'managing shared view data asp.net mvc' you'll get a ton of results with various options, none of which are really perfect. The MVC team at Microsoft have acknowledged this as a problem and will hopefully include a standard solution in a future release.
Depending on how you manage data access, the easiest way may be to create a base Controller class and retrieve the data you need for the partial either inside the constructor or inside OnActionExecuting().
The option that I have chosen is to use the Html.RenderAction() helper method inside the MvcContrib project. It basically enables you to call an action method from your view and render the response. This isn't great because it requires your view to have yet more knowledge about controllers, but it gives an easy short-term solution that doesn't require hooking up any extra code on your part.
This tutorial on stephan Walther's site deal with this issue. If you use an abstract base class where the dinners object is populated and inherit from that, it will always be available, but you'll have to be aware that it's there always even when you don't need it ;).
One method I use is to create a helper method and use it in your Master Page.
public static void RenderDinners(this HtmlHelper helper)
{
helper.RenderAction<DinnersController>(c => c.Dinners());
}
<%# Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %>
...
<% Html.RenderDinners(); %>
As you can see the helper calls the Dinners Action method on the DinnersController.
public ActionResult Dinners()
{
...get dinners and put in the View
return Dinners(view);
}
partialview seems to me to be inherently flawed. It creates module coupling and breaks cohesion intentionally by definition.
I agree with Daniel, even if your control does not shows on every page, it shows on some of them, you should create your master as template only
Related
Virtually every ASP app I've written (hundreds) follows the exact same pattern. A "single page app" with a header and footer, and a dynamically updated content area that changes depending upon what going on/in the url. Something like the following (very simplified, but demonstrates the principle):
<% select case lcase(request("action") %>
<% case "home" %>
<div class='class_home'>
Home Screen HTML/Script/ASP
</div>
<% case "enroll" %>
<div class='class_enroll'>
Enroll Screen HTML/Script/ASP
</div>
<% case "checkout" %>
<div class='class_checkout'>
<!-- if it's gonna be lengthy, will often do this instead: -->
<!--
#include file=checkout.inc.asp
-->
</div>
<% end select %>
This pattern may even be nested several layers deep with additional request("subaction") subarea/subforms involved. Every form submits to itself ([form action="" method=POST]), and asp script at the top catches the form and processes it, then continues.
So, the question is, is this pattern still done inside MVC? Or do I have to duplicate the common areas over and over again in each separate page that I create?
Is this even a good idea to WANT to do this? Or is there a better way to accomplish the same goal of a "single page app"?
Thanks!
Even in classic ASP you could achieve this without all the craziness that is going on in that select statement.
In MVC, you use partials and layout pages to avoid repeating code. Here is a nice rundown http://weblogs.asp.net/scottgu/archive/2010/12/30/asp-net-mvc-3-layouts-and-sections-with-razor.aspx
This is still the same in MVC. If you are using Razor, look for the _Layout.cshtml file in /Views/Shared. If you are using the old ASP.Net engine, it will in the same location but called MasterPage.
Aditionally, there is a file called _ViewStart.cshtml. This is invoked automatically by the framework, and this is what points to the _Layout.cshtml file.
I'll add a little more to the suggestions of using _ViewStart.cshtml and _Layout.cshtml. Make sure to use strongly typed view for all your Views and have each View Model extend from a base View Model class that has all the "common" data such as the menu state, logged in status, etc.
You would just do this using ineritance:
public class MyBaseViewModel
{
public string UserName { get; set; }
//other properties
}
public class MySampleViewModel : MyBaseViewModel
{
//additional properties for this View only
}
I've been searching for a good explanation of how to access my strongly typed data from the View of my MVC app (first time touching MVC) and can't seem to find it. Here's the code in my controller:
PersonDetailsModel personDetails = personProvider.GetPersonDetails(id);
return View("Person", personDetails);
I have a view called Person.aspx which looks like this (pretty much empty):
<%# Page Title="Title" Language="C#" Inherits="System.Web.Mvc.ViewPage<Models.PersonDetailsModel>" MasterPageFile="../MvcMasterPage.Master" %>
I would've thought I could just do something like Model.property or Person.property in the view to access the data, but I don't see how I can access the instance of my model. I'm sure it's easy, but I just don't see it.
Since you have a strongly typed view, Model is the instance of your model that you passed from the controller. So you could directly access its properties:
<%# Page
Title="Title"
Language="C#"
Inherits="System.Web.Mvc.ViewPage<Models.PersonDetailsModel>"
MasterPageFile="../MvcMasterPage.Master"
%>
<div><%: Model.SomeProperty %></div>
The Model property will be of type Models.PersonDetailsModel.
And if you were using the Razor view engine the equivalent view would look like this:
#model Models.PersonDetailsModel
<div>#Model.SomeProperty</div>
I found user controls to be incredibly useful when working with ASP.NET webforms. By encapsulating the code required for displaying a control with the markup, creation of reusable components was very straightforward and very, very useful.
While MVC provides convenient separation of concerns, this seems to break encapsulation (ie, you can add a control without adding or using its supporting code, leading to runtime errors). Having to modify a controller every time I add a control to a view seems to me to integrate concerns, not separate them. I'd rather break the purist MVC ideology than give up the benefits of reusable, packaged controls.
I need to be able to include components similar to webforms user controls throughout a site, but not for the entire site, and not at a level that belongs in a master page. These components should have their own code not just markup (to interact with the business layer), and it would be great if the page controller didn't need to know about the control. Since MVC user controls don't have codebehind, I can't see a good way to do this.
Update
FINALLY, a good (and, in retrospect, obvious) way to accomplish this.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace K.ObjectModel.Controls
{
public class TestControl : ViewUserControl
{
protected override void Render(System.Web.UI.HtmlTextWriter writer)
{
writer.Write("Hello World");
base.Render(writer);
}
}
}
Create a new class which inherits ViewUserControl
Override the .Render() method as shown above.
Register the control via its associated ASCX as you would in a webForm:
<%# Register TagName="tn" TagPrefix="k" Src="~/Views/Navigation/LeftBar.ascx"%>
Use the corresponding tag in whatever view or master page that you need:
<k:tn runat="server"/>
Make sure your .ascx inherits your new control:
<%# Control Language="C#" Inherits="K.ObjectModel.Controls.TestControl" %>
Voila, you're up and running. This is tested with ASP.NET MVC 2, VS 2010 and .NET 4.0.
Your custom tag references the ascx partial view, which inherits from the TestControl class. The control then overrides the Render() method, which is called to render the view, giving you complete control over the process from tag to output.
The difference between using this approach and calling Html.RenderPartial() or `Html.RenderAction()' is adding the control to a view is done with a webforms-like tag, which is not only more comfortable for designers, but keeps them from having to be aware of controller names and methods. The name of the control class is isolated to the ASCX, also making it easier to drop these in an assembly and reuse them across separate projects.
Some may say that this violates SoC, but I believe that this approach is functionally equivalent to tying a partial view and a controller together while maintaining clean markup. It should be clear, however, that it is still up to the developer to keep only presentation-related logic in the control Business and data access logic still belong in their respective layers.
I'm a little confused here.
First of all, the .NET MVC equivalent to User Controls is Partial Views. Partial Views are a convenient way of encapsulating common View functionality in a single location. You can then call a Partial View from inside another View.
Second of all, modifying a View shouldn't mean also modifying a controller. If you are required to make a change to both just because your View changed (and not the underlying data), then there's a code issue somewhere along the line.
At first glance its easy to dismiss MVC as not having the capabilities for reusable components.
Once you get the know ASP.NET MVC you'll find there are several techniques for creating rich controls and components and encapsulating aspects of MVC follow along the same pathways as encapsulating a WebForms application.
I think what you're doing is only looking at the View aspects of MVC and not how all the underlying M and C can be encapsulated and tied together. Partial Views, Render Action/Partial are just small pieces of the underlying component capabilities of MVC. There is much more richness under the covers.
a user control is just some stuff that renders html, in mvc you have html helpers and partial views and normal views (you can render them with renderaction )
Html.Helper("someStuff")
Html.RenderPartial("viewname")
Html.RenderAction<Controller>(o => o.Action());
so basically it's just the helpers
you can actually easily substitute a call to
Html.TextBoxFor(o => o.Name);
with
Html.RenderPartial("textbox", Model.Name);
Consider the following example:
My view (CustomerDetail.ascx) binds to ICustomerDetail view-model which looks like:
interface ICustomerDetail
{
string Name { get; }
Address CurrentAddress { get; }
}
I can create a partial view Address.ascx which binds to IAddress view-model
When I am creating the CustomerDetail.ascx, I can place the Address.ascx on the same surface & bind it to the oCustomerDetail.Address field
IMO - we should be composing views from multiple such smaller partial views in MVC & this is where you will see the re-usability & the power of user controls (partial views)
Now if my controller returns ICustomerDetail, I will be able to re-use the Address.ascx without any problems
HTH.
Let's take a registration page for an e-commerce site, as an example. You prompt the user for their name, password, postal information, favorite dog breed, etc. Somewhere else in the application, you also need to collect a billing address and a shipping address. To enforce DRY, you create a user control that manages the entry of the address information.
So, to further illustrate, your address class looks something like this:
public class Address
{
public string StreetAddress { get; set; }
public string City { get; set; }
...
}
Your registration class:
public class UserReg
{
public string UserName { get; set; }
public Address MailingAddress { get; set; }
...
}
Your billing and shipping addresses may descend from the Address class:
public class BillingAddress : Address
{
...
}
public class ShippingAddress : Address
{
...
}
For the following examples, I am assuming that you have added System.Web.Mvc to the namespaces section of web.config. Based on this class hierarchy, your user control will have a control tag that refers only to the Address class:
<%# Control Language="C#" Inherits="ViewUserControl<Address>" %>
Since you've done this, you simply need to pass the appropriate model reference from the page. In the User Registration page:
<%# Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="ViewPage<UserReg>" %>
...
<% Html.RenderPartial("AddressControl", Model.MailingAddress); %>
In the billing address page:
<%# Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="ViewPage<BillingAddress>" %>
...
<% Html.RenderPartial("AddressControl", Model); %>
In the shipping address page:
<%# Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="ViewPage<ShippingAddress>" %>
...
<% Html.RenderPartial("AddressControl", Model); %>
I can pass the model directly from the billing and shipping page, because the class directly descends from Address. As long as the logic is in place to process the addresses correctly, you're not going to have to make many changes to the controller, if any.
Partial views are certainly not close to WebUserControls.
In WebUserControls, you can write the HTML, and its own event handlers and is also connected to the parent page. For e.g. I can create a Sign In Web user control and use it anywhere in the web application. As an independent control, it can handle all the verifications and signing and allows redirections.
You don't get that in Partial views.
I am wondering if it is possible to wrap up an ascx file so that it can be used in different views, without having to type it to a specific Model.
This would mean you could use one ascx file for many pages, as long as the relevant model data was passed in.
Currently I'm duplicating my ascx files for different parts of the site, but this feels very un DRY.
ps this is th kind of model I'm passing in.
public class DinnerPage {
public Dinner dinner {get; set;} //main page data
public List<Dinner> Random6Dinners {get; set;} // ascx content
}
The problem is if I want to use the ascx file that renders the 6 random dinners, but for a different main page, then I have to type it differently.
Basically, can you have an untyped ascx page (or a view)
Thanks all.
I believe what you are asking is if you could re-use many views like a control on a single view? If so, you can indeed. Use the Html.RenderPartial action in your view. This will render an existing "aspx" page on your current view:
Example:
Assuming Model.Dinners is a List/Enumrable of some sort.
Dinners.aspx
<!--html-->
<%foreach(var dinner in Model.Dinners){%>
<% Html.RenderPartial("~/DinnersFolder/Dinner.ascx", new { Dinner = dinner}); %>
<%}%>
<!--html-->
Dinner.aspx
<%
var dinner = ViewData.Eval("Dinner");
%>
<!--html-->
Something like this would work for an individual dinner:
<% Html.RenderPartial("Dinner", Model.Dinner); %> //Dinner being Dinner.ascx
This way your ("MyPartialView" / MyPartialView.aspx), will take in a type of Dinner. You can then pull out that Dinner via ViewData.Eval, and display your "Dinner" accordingly.
In your main page you can: then do something like foreach Dinner in my Dinners, render a PartialControl.
A detailed example of this:
http://highoncoding.com/Articles/638_Understanding_Partial_Views_in_ASP_NET_MVC_Application.aspx
You could aslo use RenderAction, this has the same exact idea. However, it will post to your Controllers action, which in turn returns a view:
<% Html.RenderAction(...); %>
This is a great article about the difference between each, and which you should use:
http://blogs.intesoft.net/post/2009/02/renderaction-versus-renderpartial-aspnet-mvc.aspx
Have you tried having a base Dinner model in which the 6 random dinner classes derive from and then just use the user control as such:
<%= Html.RenderPartial("DinnerControl",
Model.RandomDinnerThatDerivesFromBaseDinner); %>
And then the first line of your control should be something like:
<%# Control Language="C#"
Inherits="System.Web.Mvc.ViewUserControl<BaseDinner>" %>
I'm experiencing some craziness that I just can't figure out.
I've created the following class:
public abstract class AbstractView<T> : ViewPage<T> where T : class
which gives me some useful helpers and I'm using it in my views like this:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="Project.Web.Mvc.AbstractView<Project.Domain.Entities.Example>" %>
This all works fine. Now I've got to make a view which needs lots of complex rendering code, so I want to give my view a codebehind, so I can put all this presentational logic in it.
However, when I do:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="Project.Web.Views.Examples.View" CodeBehind="~/Views/Examples/View.aspx.cs" %>
public class View : Project.Web.Mvc.AbstractView<Project.Domain.Entities.Example>
Model is always null when I debug the view, which subsequently gives me:
"The view 'View' or its master could not be found. The following locations were searched..."
What's going on?
P.S. don't tell me codebehinds are evil. I'm writing presentation specific logic, and lots of it. It's not going inline in the aspx.
You could try using a code-alongside. By that I mean a class deriving from AbstractView<T> that is only used by this one view. It's virtually the same concept as a code-behind. Then just point at that instead of using AbstractView<T>.
Also, your Model won't be hooked up until OnLoad (could be earlier, I've never done it myself) so that could be your problem if you're trying to use it in the constructor.
Otherwise you might want to look at using a ViewModel so that instead of you passing what I assume are raw domain objects, you do the custom formatting in the ViewModel and pass that to AbstractView<T>.
Using View as your class name is probably not a good idea. Using something a more specific.
I take it you ae declaring the class in the namespace specified in the Inherits attribute?
Ok my bad, the model isnt ready in the codebehind until onload, and i was doing things in the constructor.
Strange that MVC would say "cant find the view" when it clearly can, there was just an exception thrown