How do I encapsulate calls to ViewBag or ViewData? - asp.net-mvc

I want to be able to able to register script blocks in the ViewData or ViewBag and then unload them on my layout page at the correct position.
I tried writing a #function {} in my _Layout but this cannot be called from my Views.
So how do I write a class that allows me to do something similar to
#Something.registerscript("myscript.js")
And then on the Layout page
#Something.RenderScripts()
I saw an implementation using the singleton pattern here...
Add CSS or JavaScript files to layout head from views or partial views
But im afraid that will cause problems as this should be dynamic not static!

Is this for a generic solution or do you just want to include scripts files from within your views? For the latter, you can always create a section called "Head" or whatever which is created within the <head>-element.
For example:
_layout.cshtml
<html>
<head>
#RenderSection("Head", false)
</head>
...
</html>
View.cshtml
#section Head
{
<script type="text/javascript">...</script>
}
I wouldn't propose this as an answer, since there is the possiblity that you do want to actually do more than this (as far as I know, this doesn't work with EditorTemplates etc.). But in case you were thinking to complicated, this works very easy.

Why would you use ViewBag for this ? If you want to create something that has request-wide scope, use HttpContext.Current.Items - in your case implement one storing helper method, and one "render everything stored" method.

Viewbag or it's close relative viewdata I'd say are the best collections to use for this, as your context appears to be within the view. Where would you register the script & where would you render it?
You could, I think, write helper extensions to wrap up the register & render side of things. I believe helper methods can access viewdata. You'd get the added bonus of the abstraction away from the actual store you used, as you'd only reference it from these helper methods.

Related

controller logic for gsp templates

I'm working on organizing my gsp's into templates. The problem is that some of them require a fair amount of controller type logic (fetching data based on the current request, massaging it before display, etc). Currently it seems like I have two options:
Do the controller type work in the template itself using taglibs and scriptlet. This seems to be a poor separation of View and Controller. Plus it gets quite ugly quite quickly.
Do the controller type work in every controller that ultimately uses that template. This is not very DRY.
Neither of these seem quite MVC to me. What I'm looking for is a way to call a gsp template that has it's own controller. Is there such a thing in grails?
thanks,
The proper way to reuse your template (as you have defined here) is to actually include the call to the owning controller from your other GSP. Such as:
<g:include controller="myFancyControllerName" action="actionThatRendersTemplate" />
In doing so you are ensuring that the logic that builds the model used by the template is kept within the controller you are calling and you don't have to repeat it anywhere else.
You can put a fair amount of logic into taglibs, or call services from a gsp. You don't have to put all your logic in a controller or call a controller from a GSP.

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.

ASP.NET MVC 3 Razor Partial View - jQuery Included In Main Layout

A partial view that I'm using requires certain jQuery libraries to be included, and I'm currently trying to think of the best way to nicely add them.
My current setup is as follows:
_Layout.cshtml:
...
#if (ViewBag.jQuery)
{
<script src="#Url.Content("~/Scripts/jquery-1.5.1.min.js")"
type="text/javascript"></script>
}
...
Index.cshtml:
...
#{ Html.RenderPartial("PartialView",
new MVCModel.Models.MyModel(),
new ViewDataDictionary { { "ViewBag", ViewBag } }); }
...
PartialView.cshtml:
...
#{
if (ViewBag.ViewBag != null)
{
var vb = (dynamic)ViewBag.ViewBag;
vb.jQuery = true;
}
}
...
So what I'm trying to do is "turn on" the library in the partial view, and have that boolean propagate up to the master layout. This makes it so that I can enable the library as much as I want wherever I want (many partial views, or one view used multiple times), but it will only get included once. On top of that, I get control over the ordering of the includes, so I can make sure a files dependencies are included first.
I'm just wondering if there is a nicer way of pulling this off.
Right now my two biggest issues are:
The ViewBag is not strongly typed, so intellisense won't tell me which libraries are available to me.
Passing the ViewBag to the partial view, just so I can re-use it.
Here is a simple way to accomplish your objective.
There is a function in MVC Razor views called RenderSection. It has a syntax like
#RenderSection ("occasionalScripts", false)
(occasionalScripts is the name of the section and false means the section is optional and may not appear in every page.)
You would want to include this in your _Layout.cshtml which is Views\Shared. This will allow your main script template to display a section with your script definitions if you have it defined for a particular view.
Generally, you want to put this at the bottom of your page just before the closing </body> tag. (This is a best practice for performance.) I would list all of my other scripts (the ones which load on every page) just above it. The reason is because the load order of jQuery scripts is very important.
In each of your views where you have special scripts, add the following:
#section occasionalScripts {
... put script references here
}
If you have a view which requires no special scripts, then you don't need to include this section there. The false on the #RenderSection tag will accommodate any view where the #section tag is missing.
On each page where this functionality is implemented, you can select different scripts.
Optionally, you could have multiple sections defined for different categories of files.
I usually have a different approach, and consider that ALL libraries used on the web site should be referenced on every page. It does make the first load a bit slower, but then the loading of all the following pages is faster, as it uses the browser-cache.
To improve it even better, you can make all your libraries available in only one file.
I used SquishIt for that at some point. I integrates rather well in ASP.NET MVC.

Can a controller influence the _layout.cshtml file?

I'm stuck! I'm under the impression that the _layout.cshtml file is used for MasterPage-like content. Everything there is rendered on every page. Naturally, I want to write the code for rendering my sidebar menu in that file.
I want to dynamically display a list of Categories from my DB, but I'm having a problem with passing the actual model of categories to Layout.cshtml since it seems no controller actually touches it.
Any suggestions?
Otherwise please tell me how to approach this problem. I've been wracking my brain for the past three days and still no elegant solution.
I need to:
Dynamically fetch a list of Categories from the DB.
Display this list of Categories on every single view. (Hence the use of _layout.cshtml)
Elegantly handle each different categories click.
I'm at my wits end. :P How would you solve this?
_layout.cshtml
#if(isSectionDefined("Categories"))
{
<div id="sidebar">
#RenderSection("Categories", required: false )
</div>
}
index.cshtml
#section Categories {
<ul>
<li>Category One</li>
<li>Category Two</li>
<li>Category Three</li>
</ul>
}
see this : http://weblogs.asp.net/scottgu/archive/2010/12/30/asp-net-mvc-3-layouts-and-sections-with-razor.aspx
Any viewmodel that you pass to your view is automatically available within your master page. If you do not use RenderAction/Action which is the best approach, then you must create the necessary master page data in every action and add it to viewdata - either by having a common base class for your strongly typed viewmodel that contains all master page data or by using the viewdata dictionary.
I would strongly recommend that you go down the html.action approach though. In this way, you have a totally separate controller action for dealing with your list of categories. This action can retrieve the neccesary category data and return the categorylist usercontrol as a partialview and you will not have to worry about polluting all your other actions with this data.
As I see it, ViewData (and its relatives like ViewBag, Model, etc.) is meant for the specific current view. Your _Layout.cshtml is not specific to the current view; and it would be awkward if EVERY controller would have to pass the categories data in addition to whatever else data it needs to pass for the view.
Instead, what I do, is provide a static method in one of my helper classes that retrieves the categories from the DB. I also do some caching there, so that I do not have to hit the DB on every single request. The _Layout.cshtml then simply calls this static method. Simple and elegant.
If you wish, you can bring this out to a partial view, make it a helper method, whatever.
One note of caution though - my custom error view also uses the same _Layout.cshtml, and if the DB goes down, you get an exception trying to display the exception. ASP.NET MVC is smart enough to detect this and abort processing, but you're left with a nondescript default error page. What I did was to place try...catch statements around these dangerous calls, which quietly ignore the exception if the current page is the error view.
I've achieved something similar by having my ViewModels implement an Interface which has members that contain the menu data. In my action method I set that data. Then in my view I check to see if my view-model implements that inteface, pull the menu data out and render the menu (in a partial view actually)

Razor: #Html.Partial() vs #RenderPage()

What is the appropriate way of rendering a child template?
And what's the difference? Both seem to work for me.
And why does #Html.RenderPartial() no longer work?
Html.Partial("MyView")
Renders the "MyView" view to an MvcHtmlString. It follows the standard rules for view lookup (i.e. check current directory, then check the Shared directory).
Html.RenderPartial("MyView")
Does the same as Html.Partial(), except that it writes its output directly to the response stream. This is more efficient, because the view content is not buffered in memory. However, because the method does not return any output, #Html.RenderPartial("MyView") won't work. You have to wrap the call in a code block instead: #{Html.RenderPartial("MyView");}.
RenderPage("MyView.cshtml")
Renders the specified view (identified by path and file name rather than by view name) directly to the response stream, like Html.RenderPartial(). You can supply any model you like to the view by including it as a second parameter
RenderPage("MyView.cshtml", MyModel)
I prefer
#RenderPage("_LayoutHeader.cshtml")
Over
#{ Html.RenderPartial("_LayoutHeader"); }
Only because the syntax is easier and it is more readable. Other than that there doesn't seem to be any differences functionality wise.
EDIT: One advantage of RenderPartial is you don't have to specify the entire path or file extension it will search the common places automatically.
The RenderPartial method doesn’t return HTML markup like most other helper methods. Instead, it writes
content directly to the response stream, which is why we must call it like a complete line of C#, using a semicolon.
This is slightly more efficient than buffering the rendered HTML from the partial view, since it will be written to the
response stream anyway. If you prefer a more consistent syntax, you can use the Html.Partial method, which
does exactly the same as the RenderPartial method, but returns an HTML fragment and can be used as
#Html.Partial("Product", p).
We can also pass model using partial views. #Html.Partial("MyView","MyModel");
#RenderPages()
The above does not work in ASP.NET MVC. It only works in WebPages.
#Html.Partial("_Footer")
You will need to use the above in ASP.NET MVC.
For ASP.NET Core 7. In the Shared folder make partial file then user this following code
<partial name="_NavBar" />

Resources