How to load _Layout.cshtml only once? - asp.net-mvc

I have a pretty standard MVC solution with _Layout.cshtml, Index.cshtml, etc. I'm trying to load some required bundles in the Layout only one time, so that when I change views, I only get what I need from the server, not what I already have (bundles, layout html, etc).
Following this post: MVC 3: How to render a view without its layout page when loaded via ajax?
I now have an updated _ViewStart. It doesn't seem to be working though:
With the following JS viewmodel being loaded as part of the bundle in _Layout:
var subjectservice = new baseservice(); // baseservice is an empty function
subjectservice.subjects = {};
subjectservice.getSubjects = function () {
alert('Hit');
subjectservice.subjects = 'data';
}
subjectservice.getSubjects();
The intended behavior is that:
I get Subjects when I initially load the application (with _Layout), no matter what page I'm on
I don't call getSubjects again when navigating - only when I specifically call it from other viewmodels
However, no matter what page I navigate to, I always get the alert message, even if Layout has already loaded. Viewing the network panel in Chrome Debugger shows all of my bundled js files are getting reloaded with every page load.
An example of a view that I'm trying to load without re-loading bundles and _Layout:
public ActionResult Index()
{
return View();
}
How can I load Layout and its bundles only once?

What I believe you're referring to is the ability to use Ajax and Pushstate.
Luckily there's a great OSS lib for this.
https://github.com/defunkt/jquery-pjax
Do the following to your _ViewStart.cshtml
#{
Layout = Request.Headers["X-PJAX"] != null ?
"~/Views/Shared/_PjaxLayout.cshtml" :
"~/Views/Shared/_Layout.cshtml"; // uses the _Layout.cshtml for unsupported browsers
}
More info here
http://chrisseroka.wordpress.com/2012/04/24/getting-starteg-with-pjax-and-asp-net-mvc/

Related

Casual layout patern in mvc with razor

I can't find the reel intentions behind MVC-Razor layouts through internet.
In shared folder, there is :
_Layout.cshtml
_LoginPartial.cshtml
Should i use the _Layout for pages that dosen't require to be logged in, and use _LoginPartial for pages that require to be logged in ? Or am i completely lost ?
To make it simple :
If i create a new view that can only be reached when logged in, should it be beginning with
Layout = "~/Views/Shared/_Layout.cshtml";
or
Layout = "~/Views/Shared/_LoginPartial.cshtml";
?
Edit :
Checking tutorials and explanation from everyone (thanks all)
_Layout.cshtml is exactly like a master page in WEB FORM,
So i should always use :
Layout = "~/Views/Shared/_Layout.cshtml";
at the begining of a page i want to have formated like others.
The Login partial can be applied after authentification to alter the layout (disconnect button instead of connect, ect.)
The file _Layout.cshtml represent the layout of each page in the application. While partial view is a custom, reusable component that you can use in each page you need it. For example, we can create a partial view for customer and call it many time in page
<table class="table table-condensed">
#foreach (var student in Model.Students)
{
#Html.Partial("_StudentForm ", student)
}
</table>
So _Layout is meant to be used for all pages and _LoginPartial.cshtml can be used inside the page you need to have a login form in. Check this article about partial view
Tips and Tricks about Razor Partial Views
Layout = "~/Views/Shared/_Layout.cshtml";
in your view start file (_ViewStart.cshtml), may times its the ONLY thing in that file.
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
the _Layout.cshtml and _Viewstart.cshtml combo is similar to a master page in web applications but these will not have controller actions associated with them. if you set the layout setting in the _ViewStart file you don't need to set it in your actual views, they will inherit it from the viewstart file.
If you look inside the layout file you will see this line somewhere
#RenderBody()
That is where your individual views HTML will end up when your specific view is called.
The Login partial is just a quick start to demonstrate a view that can change display based on if the user is logged in or not.
You can use the same _Layout.cshtml but your controller ActionMethod should change to Authenticate. Use the below link for more info.
Authenticate User in MVC
Its more of a naming convention for layouts.
views will inherit it from the viewstart file. If you look inside the layout file you will see the renderbody method.
#RenderBody()
This is where the HTML code is read and shown in the browser.
The same goes for the _loginPartial.cshtml its just there for looks and to show you what Mvc is capable of.
Visual Studio creates the layout _Layout.cshtml when all but
the Empty project template is used. This layout is applied to all views by default through the /Views/_ViewStart.cshtml file.
If you do not want the default layout applied to views, you can change the settings in _ViewStart.cshtml (or delete the file entirely) to specify another layout in the view, like this:
#{
Layout = "~/Views/Shared/MyLayout.cshtml";
}
Or you can disable any layout for a given view, like this:
#{
Layout = null;
}
Hope this helps.

Using an #section inside a ChildAction to add additional scripts to a page

I've got a MVC View which is made up of a main view and some additional content added via #Html.Action.
In the additional content (which is a ChildOnlyAction) I want to be able to add some JS to the page, but I want to add it to the #RenderSection("Scripts") block which I've defined in the layout.
Can my Child Action's View use this:
#section Scripts {
//Add scripts
}
So far I haven't been able to get it to work, so if it doesn't what would be an alternative approach?
Sections do not work in partial views. You could use a conjunction of extension methods that I illustrated here: https://stackoverflow.com/a/9663249/29407
So in your _Layout.cshtml you will have at some location:
#Html.RegisteredScripts()
and then in your partial:
#{Html.RegisterScript("~/scripts/foo.js");}
I was willing today to create a global dialog that would open under some conditions, I needed the script to be at bottom of the page. As others have already mention, a #section inside a child action is not possible.
I had the same problem as you did, the solultion to use custom helpers and js files should work, but I don't like, because usually I operate the javascript with razor and files make the requests longer to load.
the solution at https://stackoverflow.com/a/9663249/29407 is valid if you like that, for me no thanks.
I came with a new solution that is clean, the problem if you analyze it is that we have one controller and a view with 2 parts that have to be injected at different position in the final result.
After my analysis I realize that we have 2 views but one controller that has to control them once per request, below is how I did it, I moved the javascript to a new view with same name endig with script.
XDialogController.cs
XDialog.cshtml
XDialogScript.cshtml
Then before returning the ActionResult from the child action method, one sets the model or values for the other view inside the TempData object.
for example:
[ChildActionOnly]
public ActionResult Popup()
{
// pass variable or model if you need it to script view.
TempData[TempDataKeys.ScriptXDialogModel] = new ModelScriptX();
// pass variable or model to regular view.
return PartialView("XDialog", new ModelX());
}
Inside your ...Script.cshtml file you can read the variable or model as you need.
for example:
#if((TempData[TempDataKeys.DisplayXDialog] as bool?) == true)
{
<script type="text/javascript">
...jquery functions ....
</script>
}
Remember that TempData can only be read only once, one can keep the value inside a variable inside the view.
To invoke my dialog in the layout page I do the following:
<body>
#RenderBody()
#Html.Action("Popup", "XDialog")
#Scripts.Render("~/Scripts/core")
#RenderSection("ExtraScripts", required: false)
#Html.Partial("XDialogScript")
</body>
I hope that can help anybody.

adding attributes in HTML tag of specific views only?

I am working on a MVC application with razor view engine.
HTML, Head and Body tags are placed in shared view _Layout.cshtml and other views are using this as layout.
While supporting application I am in need to add a custom attribute in some of pages (not in all pages). If I add attribute in layout it will appear in all pages. Can you pleased guide me how adding an attribute can be managed only in desired pages.
Below is my html tag with attribute:
<html ng-app="business_rules">
You can create a Layout where you need all these attributes and just refer this layout for desired pages using
#{
Layout = "Path/To/Layout.cshtml";
}
on the top of those pages.
For rest of the pages, you will use the different layout without those attributes.
You can define the Layout form the controller too. It can be done as below:
public ActionResult Index()
{
MyMOdel model = new MyMOdel ();
//TO DO:
return View("Index", "_AdminLayout", model);
}
I would use jquery. Just add on pages where you need this attribute following code:
<script>
$(document).ready(function(){
$('html').attr("ng-app", "business_rules")
});
</script>
I came across the exact same problem right now and the best solution I came up with was this below (I don't really want to add an entirely separate layout just for an attribute on the body tag). You've got a couple of suggestions on the asp net forums as well
I added an item to the view bag, a (probably overly) generic one called BodyAttributes:
ViewBag.BodyAttributes = "ng-app=\"moduleName\"";
Then I render this in pure htlm on the body tag
<body #Html.Raw(ViewBag.BodyAttributes)>
This keeps your layout non page specific and the angular doesn't need to be bootstrapped on every view. Plus, whether or not the angular is bootstrapped is now controlled from within the view itself where it belongs.

Retrieving and caching HTML from website using ASP.NET MVC 3

I want a partial view that display some stuff from a website that is not under my control.
The data on the website is only available through HTML, and thus I can only retrieve it by querying the web site and parsing the HTML. (The website holds a list of 50 elements, and I only want the top 10.)
Now, the data from the website is not changing very frequently, so I imagine that I can retrieve the HTML on an hourly basis, and displaying a cached version on my web site.
How can I accomplish this in ASP.NET MVC 3?
Ignoring the MVC3 requirement for now, you should look to using WebClient to grab the html from the website. You can do something like:
var client = new WebClient();
var html = Encoding.UTF8.GetString(client.DownloadData("http://www.somedomain.com"));
If you need to tailor your request, I'd recommend looking at HttpWebRequest, HttpWebResponse. Now that you can grab the html, you need to consider your caching mechanism, possibly in the ASP.NET runtime?
public ActionResult GetHtml()
{
if (HttpRuntime.Cache["html"] == null)
GetHtmlInternal();
return Content((string)HttpRuntime.Cache["html"], "text/html");
}
private void GetHtmlInternal()
{
var html = // get html here.
HttpRuntime.Cache.Insert("html", html, null, DateTime.Now.AddMinutes(60), Cache.NoSlidingExpiration);
}
The first solution that comes to mind is to create an action in a controller that makes an Http request to the remote web page and parses the html you want to return to your own page and then set output caching on your action.
Edit:
What controller to put the action in would depend on the structure of your web site and whether the partial view would be visible on all views or just a specific view. If the partial is visible in all views I'd either place it in the Home controller or create a "General" controller (if I anticipated more actions would go in such a controller).
If you want to manipulate the result I would probably make a model and partial view for the list. If you want to take a part of the returned html and output it as it is I would use the same method as in the answer by Matthew Abbott:
return Content(yourHtmlString);
The end would look something like this:
[OutputCache(Duration = 3600)]
public ActionResult RemoteList()
{
var client = new WebClient();
var html = Encoding.UTF8.GetString(client.DownloadData("http://www.somedomain.com"));
// Do your manipulation here...
return Content(html);
}
(Some of the above code was borrowed from the post by Matthew Abbott.)
You could just add OutputCache attribute on your action and set OutputCache.Duration Property to 3600 seconds (1 hour)

.NET MVC: How to implement different page appearance per user?

I am running out of ideas here. Maybe you can advice me what pattern or method(s) to use.
User should be able to log in and change the appearance only for his/her profile.
The difference (AFAIK) with personalization is that personalized layout are seen only for the editor (him-/herself).
The difference between skinning, I guess, is that Skins are predefined but users should be able to change the settings themselves.
I need to be able to display the customized layout to everyone who visit author`s page.
The good solution would be to keep the layout info in a DB table. Also it should be cached I guess to take load off the DB and used in CSS.
Thanks
Edit:
OK I have done some research now. Came up with this kind of idea.
In a View get a userId (Guid type) from a DB and set it to the ViewData:
ViewData["userId"] = profile.userId;
That View uses the following MasterPage called 'Profile.Master' and links to the dynamic CSS file:
<link href="<%= Url.Action("Style", "Profile",
ViewData["userId"]) %>" rel="stylesheet" type="text/css" />
</head>
In the ProfileController get the CSS data from DB and return it to the dynamic CSS View:
public ActionResult Style(Guid userId)
{
var styles = (from s in Db.UserStyleSet.OfType<UserStyle>()
where s.aspnet_Users.UserId == userId
select s);
return View("Style", styles);
}
The problem is that the UserId is never passed to the dynamic CSS link:
The parameters dictionary contains a null entry for parameter 'userId' of non-nullable type 'System.Guid' for method 'System.Web.Mvc.ActionResult Style(System.Guid)' in 'Project.Controllers.ProfileController'.
Any advice is welcome, thank you.
Very neat layout customization features you can find in Kona project developed by Rob Conery. When you run source code which you can find here, you will see layout management UI which allows you to change the position of each component on the screen.
The approach used there is as follows:
When page is rendered our customized view engine check which master page should present (this way we are able to switch themes based on current settings)
public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache) {
ViewEngineResult result = null;
var request = controllerContext.RequestContext;
if (controllerContext.Controller.GetType().BaseType == typeof(KonaController)) {
var orchardController = controllerContext.Controller as KonaController;
string template = orchardController.ThemeName;
View engine uses master page and renders view which was defined by specific controller action resolved using route tables. For instance, we typed main url of the site which pointed to Home Controller, Index method. This method returned Index.aspx view which was rendered by View engine.
While view engine is rendering the Index.aspx page it launches helper methods like
<%this.RenderWidgets("sidebar1"); %>.
This method is truely responsible for rendering specific widdgets per each div in the aspx page. This way, if your user changes the layout of the widgets they will be correctly presented on the screen.
public static void RenderWidgets(this ViewPage pg, Kona.Infrastructure.Page page, bool useEditor, string zone) {
if (page != null) {
foreach (IWidget widget in page.Widgets.Where(x => x.Zone.Equals(zone, StringComparison.InvariantCultureIgnoreCase))) {
string viewName = useEditor ? widget.EditorName : widget.ViewName;
if (widget.ViewName != null) {
if (widget.IsTyped) {
var typedWidget = widget as Widget<IList<Product>>;
pg.Html.RenderPartial(viewName, typedWidget);
} else {
pg.Html.RenderPartial(viewName, widget);
}
} else if (!string.IsNullOrEmpty(widget.Title)) {
pg.Html.RenderPartial("TitleAndText", widget);
} else {
pg.Html.RenderPartial("TextOnly", widget);
}
}
}
}
How user is able to change the layout? Kona has very neat javascript which is used together with Ajax and user simply drag&drop widgets from one panel to another to reorder the layout.
You could use a CMS framework. See this question for suggestions
You could dynamically build a CSS file and save the css name in the user's db entry.
How much customisation do you need? Storing an entire css in the database 1 style at a time seems a little overkill, are you sure your users really need / want that level of customisation?
Wouldn't it be simpler to present a list of themes, allow the user to select the one they want and then store that information with the user profile so that when you retrieve the profile details you also retrieve the theme. This information can then be used to select the appropriate master as well as passed to the view to render the correct stylesheet(s).
If you really want to allow extreme customisation down to the individual style level, I would use a default css and then when the user customises their layout, copy the default and alter as necessary, creating a custom css for the user. Each time the user updates their profile layout, simply update the css file with the changes. To get around css caching, record an incrementing version number for each change and append that to the end of the url for the css e.g. <link rel="stylesheet" href="user001.css?v=2>.

Resources