I'm trying to get into MVC and currently reading the wrox professional ASP.NET MVC book.
I kind of get it so far. Instead of each URL going to a page it goes to a controller. The controller action then gets the data and decides what view to use.
I also understand that if I have a url like/product.aspx?id=100 then the controller would get the product details and merge them with the "show product" view.
Now here's the bit I don't get...
If my product page has other stuff on it, like a login box, a "top 10 products" section, list of categories etc. which may or not be used on other pages too then how would I include them and keep their code separate?
In the classic aspx model it would be simple. If my top 10 products appeared on every page then I would put it in a master page, but more likely I would make it into a user control if it was going to be used on some pages not others.
From what I understand of MVC so far, my products controller would have to get the top 10 products and so would any other controller that was producing a page with the top 10 products on it.
Confused. Please help.
From what I understand of MVC so far,
my products controller would have to
get the top 10 products and so would
any other controller that was
producing a page with the top 10
products on it.
Not necessarily. You can use Master pages with MVC, as well as Partials to compartmentalize re-usable view content.
This is a good article on using Partials.
http://jeffreypalermo.com/blog/asp-net-mvc-and-the-templated-partial-view-death-to-ascx/
Also, in your Top 10 products example, you could have that rendered by a child action:
[ChildActionOnly]
public ActionResult GetTopTenProducts()
{
var products = db.GetTopProducts(10);
return View(products);
}
You would then have a partial view (.ascx) called "GetTopProducts.ascx" that would be rendered when you call the GetTopTenProducts() action. Then in your Master page, or anywhere you wanted that Top 10 list to show up, you would call it like this:
<% Html.RenderAction("GetTopTenProducts") %>
You're close.
You're just forgetting that your controller is a class that can inherit from a base class.
You can create a base controller class that handles retrieving the top ten products, and then have any controllers that need that functionality inherit from the base class.
You then create a Master Page that uses a Partial View to render the top ten products passed to the View (from the base controller).
...and voila! No code repetition.
Please don't forget that in ASP.NET MVC you still can use MasterPage. With specific to ASP.NET MVC 2 you can display those 10 products using RenderAction which can render Action from any Controller you select.
In addition to that, you should get to know PartialView as well.
You can use master pages and in MVC partial views are synonymous with user controls. The difference being master page does not have a corresponding controller. You an solve with one of two ways.
When you use a partial view you can
pass a model in.
<%= Html.RenderPartial("PartialView",Model) %>
You can just do the work in the partial view, utilizing the code brackets <% %>.
Related
I wonder if I could get some ideas on how best to approach this.
I have a Razor Pages application. I have several areas, and each area has its own layout page and menus.
This is pretty straight forward, but I have a couple of pages I need to share with two or more areas. These shared pages will be passed an argument that indicates which area it's being used by, and I would want the layout page (menu) for that page to be the one for that area. (Ideally, the URL would also reflect the current area.)
I'm not sure if this is practical. I'd like to keep things as simple as possible. But all I come up with is some fairly complex routing stuff.
Two options I can think of:
Put the shared page in the root Pages folder and use AddPageRoute to add multiple routes to the shared page including a parameter for the area name:
options.Conventions.AddPageRoute("/SharedPage", "{areaName}/alias1");
options.Conventions.AddPageRoute("/SharedPage", "{areaName}/alias2");
You can set the layout based on the value of RouteData.Values["areaName"]. Don't use area as the parameter name. It's a reserved word as far as routing is concerned:
#{
if(RouteData.Values["areaName"] == "foo")
{
Layout = "_FooLayout";
}
if(RouteData.Values["areaName"] == "bar")
{
Layout = "_BarLayout";
}
}
Add pages to the areas that act as placeholders to generate the routes, and then extract the body of the shared page to a partial that can be placed in the top level Pages/Shared folder so that it is accessible to all other pages in the application. If you want to centralise some of the processing for the shared page, create a class that inherits from PageModel and put your processing logic there, then have the actual PageModel classes inherit from that.
You can make this by using partial views.
So, create one patial view and place it not in areas (in Common for example or other folders).
In areas folder create master view that render only your Partial view.
TL; DR Are "partial Pages" possible in ASP.NET Core 2.0? If so, why am I getting the error described below?
I've been messing with the new Razor Page feature of ASP.NET 2.0 and having some trouble with partials. The documentation linked above refers to "partials" in several places, but only to the extent of saying mystical things like "The layouts, templates, and partials you're using with MVC controllers and conventional Razor views just work." Do they mean partial Views, like what we're used to, or some new kind of partial Page also? They show that files with the conventional Partial suffix in the name can be placed in the Pages/ folder, but the files that they mention (e.g., _ValidationScriptsPartial.cshtml) don't have the #page declaration. If "partial Pages" are indeed a new thing, then I would expect the following minimal scenario to work:
A Page file named Derp.cshtml:
#page
#namespace MyApp.Pages
#model DerpModel
<p>Member 1: #Model.Member1</p>
<p>Member 2: #Model.Member2</p>
A class file named Derp.cshtml.cs in the same directory (nested under the first in VS 2017's Solution Explorer), containing the following class:
namespace MyApp.Pages {
public class DerpModel : PageModel {
public void OnGet() {
Member1 = "Derp Member1";
Member2 = "Derp Member2";
}
public string Member1 { get; set; }
public string Member2 { get; set; }
}
}
A second Page file named DerpWrapper.cshtml in the same directory, with the following code:
#page "{id}"
#namespace MyApp.Pages
#model DerpModel
<p>Outer Member 1: #Model.Member1</p>
<p>Outer Member 2: #Model.Member2</p>
#Html.Partial("Derp", Model)
And of course a _ViewImports file that declares #namespace MyApp.Pages
Note that both Razor Pages have the same #model type, and the second Page basically wraps up the first one with a call to Html.Partial() and passes its Model. This should all be pretty standard stuff, but upon navigating to http://localhost:xxx/DerpWrapper, I get a 500 response due to a NullReferenceException with the following stack trace.
MyApp.Pages.Derp_Page.get_Model()
MyApp.Pages.Derp_Page+<ExecuteAsync>d__0.MoveNext() in Derp.cshtml
+
<p>Inner Member1: #Model.Member1</p>
...
MyApp.Pages.DerpWrapper_Page+<ExecuteAsync>d__0.MoveNext() in DerpWrapper.cshtml
+
#Html.Partial("Derp", Model)
...
Why does the "wrapper" Page's Model equal null? Is there no way to define a Razor Page and display it from another Page or View with Html.Partial()?
Razor’s partial views are basically .cshtml views that are transcluded into another view, that is a razor view or a razor page. They are rendered within that view and as such inherit some of the view’s properties, e.g. its ViewData. They however do not have a controller, a page model or some other kind of “code behind” that gives you a way to run code for the partial.
As such, the primary use for partials is to extract common components that do not contain logic on their own into separate and reusable files.
The other kind of reusable components that exist in Razor are view components. View components are similar to partials but they do have an actual class that backs them and that allows you to add your own logic to the view component.
At the moment, view components follow the MVC layout for views, meaning the view component is actually a class and you return a view from its Invoke method which causes the view engine to locate that .cshtml in the Views folder. David Fowler confirmed to me though, that they are working on a Razor page like experience for view components too, so in a later version of ASP.NET Core, you will eventually be able to write a .cshtml view component and add some code-behind to it (just like Razor pages).
Coming back to your question though, at the moment there are two kinds of entry points for an MVC route: An MVC controller, possibly returning a view, and a Razor page. Neither of these can invoke one or the other inside of them though. If you are rendering a view, you can load partials or view components but not other “full” views or Razor pages. And if you’re rending a Razor page, you also can load partials or view components, but still no other “full” views or Razor pages.
So what you want to do is simply not possible because a Razor page is supposed to be an entry point to the route, and while invoking any view as a partial may work (in some situations), you will certainly neither get the controller for that view nor the page model running for it. If you want to invoke something that has complex logic, you should use view components instead.
Answers on Stack overflow remain for ages so it's important that we ensure that they are improved.
To add onto Poke's Answer or rather to correct it. Partial views separate common sections between pages. These sections can include logic. But that logic should not involve any field(variables, or objects) contained or defined within any specific page. They can apply logic though dependency injection... by injecting them into the partial view.
I have a mvc site where the master page uses RenderPartial to return different views based on user roles. One of the partial view is inherited on a "SettingsModel" type. This results in a conflict with the underlying page which takes a different model. When it tries to render the page, it says "I'm expecting "settingsModel" but the incoming model is for the page. How can I resolve this conflict?
you can use RenderAction method to render different partial pages having their own view models
this article explains difference b/w rederpartial and renderAction with pros and cons of each
Let's assume I have 2 Controllers, TopicsController and PostsController.
For each controller, I have a couple of views (Index & Details).
The Topic (Index) view inherits System.Web.Mvc.ViewPage<IEnumerable<MessageBoard.Models.Topic>>
The Topic (Details) view inherits System.Web.Mvc.ViewPage<MessageBoard.Models.TopicFormViewModel>
I'm using a TopicFormViewModel because I'm sending additional data along with the Model.
The Post (Details) view just inherits System.Web.Mvc.ViewPage<MessageBoard.Models.Post>
Now, I've created a partial view (CreatePost.ascx) which is (obviously :p) used to create a new Post. I want to be able to re-use this control on all of the views you see above.
Update
I've tried rendering the partial view using <% Html.RenderPartial("New"); %> from my Topics/Index.aspx View, but that results in an exception
The model item passed into the dictionary is of type 'System.Data.Linq.Table`1[MessageBoard.Models.Topic]', but this dictionary requires a model item of type 'MessageBoard.Models.Post'.
Now the problem is that my partial view (CreatePost.ascx) accepts a System.Web.Mvc.ViewUserControl<MessageBoard.Models.Post> and I'm not sure how to pass that from all my views above.
I'm also unsure know how to submit the .ascx values to a certain URL (i.e. /Topics/1/CreatePost), how do I tell the submit button to post to that URL?
Thanks in advance,
Marko
Ciao Marko,
Now the problem is that my partial
view (CreatePost.ascx) accepts a
System.Web.Mvc.ViewUserControl
and I'm not sure how to pass that from
all my views above.
I am not sure I understand what do you mean by "how to pass that from all my views above" but I am sure that you dont have to pass an instance of Post from your views. What is going on is that from your views you will invoke a controller action that creates the Post model object and then bind it to the CreatePost.ascx partial.
I'm also unsure know how to submit the
.ascx values to a certain URL (i.e.
/Topics/1/CreatePost), how do I tell
the submit button to post to that URL?
You have two options:
Inside your CreatePost.ascx partial you are probably using a form.
<% using (Html.BeginForm("action", "controller", FormMethod.Post, new {} )) { %>
If you use in the way I am showing you can change the first and the second params respectively to the names of the Action and the Controller that would habndle your submit.
The second option is using jQuery. Simply set an ID for your form and then
$("#myForm").submit(function(event) {
//post an ajax request to the server
});
Hope this helps!
P.S. To be able to reuse your CreatePost.ascx partial place it inside the shared view folder (where your master page is).
In regards to reusing a partial view which is not in the same view folder, use the following and pass in the model required, alternatively you can define a custom route for it.
<% html.RenderPartial("~/Views/<ControllerName>/<PartialViewName>.ascx", <model>);
#Marko
Another way would be to have in the PostController an Action like the following:
[HttpGet]
public ActionResult CreatePost( int topicId ) {
PostModel pm = _manager.CreateDefaultPost();
pm.TopicID = id;
return PartialView( "CreatePost", pm );
}
Then wherever you want to create a Post you can simply call this action that returns the strong-typed view for your new post.
Even if you have a supplementary http call over the network IMO this solution has the advantage to keep the initialization code of a new Post centralized to a single place.
From the View "point-of-view" the call to the action can be done when the user press the "New Post" button and then inject the received markup inside a modal dialog or in a place of your pleasure in the current page.
Hope it helps!
I've developed my own custom users and roles objects using ActiveRecord that do NOT extend the default Asp.Net providers and therefore I can't get the user from the HttpContext. I can create a custom htmlhelper to render menus but should my views render the menu or the master page?
If it's the master page how can I pass to the custom htmlhelper things like current user since some menu items depend on the user roles.
Also, how can I detect what controller is being viewed inside my master pages?
1) If your menu functionality is supposed to exist on multiple pages, then it makes sense to put it in the master page. If not, then the normal view.
2) A popular choice is to make all of your ViewModels inherit from a base view class, and then your Master page uses that. Example:
System.Web.Mvc.ViewMasterPage<ViewBase>
System.Web.Mvc.ViewPage<MyViewModel>
public class MyViewModel : ViewBase { }
3) You can pull out the specific controller from the route data. However, if you need specific functionality for certain controllers, I would just suggest using a different master page for those views than trying to make all of your views use the same master page.
In general, all ASP.NET controls (whether WebForms or MVC) should control their own state.
In the case of handling navigation, I'd say create a .ASCX (partial view) and place it on your master page. Let the partial view control how it is displayed based on the HttpContext.