ASP.NET MVC output cache with dynamic fragment - asp.net

How could I cache an entire page except a bit at the top which says something along the lines of "Welcome back, Matt! | Log Out" if the user is logged in and so-on?
I'm using ASP.NET MVC 2.

What you are trying to achieve is called donut-caching or cache substitution. As of ASP.NET MVC 2 there is no built in helper to support this scenario. As much as I know it was a planned feature in MVC v.1 but it was dropped somewhere in the way to the release. For more info check this links http://haacked.com/archive/2008/11/05/donut-caching-in-asp.net-mvc.aspx, Is Donut Caching working properly with ASP.NET MVC?.
VaryByParam option that is mentioned by Oleg here is not a good idea in your case. If you have VaryByParam a different version of the page will be put in the cache for every different value of the parameter (in your case for every user-name).
Personally I would think of caching the data, not the whole output of the page.

Probably helps
<%# OutputCache Duration="15" VaryByParam="*" %>
or with some other value for the VaryByParam. See http://msdn.microsoft.com/en-us/library/hdxfb6cy.aspx, http://blog.maartenballiauw.be/post/2008/06/Creating-an-ASPNET-MVC-OutputCache-ActionFilterAttribute.aspx and http://blogs.microsoft.co.il/blogs/gilf/archive/2010/07/18/asp-net-output-cache-provider.aspx.
Moreover, if you have the start page which is not user depended, it is possible to replace the start page with a very static welcome page with the empty field (hidden div) instead of "Welcome back, Matt! | Log Out". After that an ajax request for filling of the user name can be started at the client side. This kind of the welcome page page can be very good cached.

Not Supported != Not Possible
http://blog.maartenballiauw.be/post/2008/07/01/Extending-ASPNET-MVC-OutputCache-ActionFilterAttribute-Adding-substitution.aspx
http://www.klopfenstein.net/lorenz.aspx/output-donut-caching-attribute-asp-net-mvc-partial-requests
http://haacked.com/archive/2009/05/12/donut-hole-caching.aspx

Here you have a workaround solution:
*Add the OuptutCache attribute to the Controller that manages the whole view as usually:
[OutputCache(Duration = 3600, VaryByParam = "*")]
public ActionResult Index(FormCollection formCollection)
{
//Controller code
}
*For the part that you don't want to do caching, load it using jquery + an ajax request (with its own controller and without the OutputCache attribute):
<div id="loginContainer"></div>
$(document).ready(function() {
$.post('controller/action', postdata, function(data) {
if (data.success == true) {
//Populate the container with the obtained data
}
});
});
The view will be retrieved from the Output Cache and, once it is loaded, a request to obtain the login info will be performed. Hopefully, it will be a very quick request and the user will not notice the delay.

Get this via nuget:
http://mvcdonutcaching.codeplex.com/
Add an action for LogOnPArtial, so you can change it from Html.Partial to Html.Action in the _Layout.cshtml
#Html.Action("_LogOnPartial","Account",true)
The true is a exclude parameter that says, exclude this from caching. The action will be called even if the page it is in is cached. This is the "hole" in the donut that is not cached.
On your page, such as About.cshtml that you want cached, apply DonutOutputCache attribute. This allows the new framework to inspect the page as it's caching it, and add flags where you've excluded actions.
The nice thing is the _LogOnPartial is not cached and will refresh for different users while the rest of the page is cached and the About() action will not be run. You could even configure caching on the _LogOnPartial action you created using the DonutOutputCache attribute, but a more frequent or less frequent interval, or vary by some other param. This allows you to compose pages of partials, and the cache refreshing logic is independently configured for each partial.
IMO this tool is exactly how I imagined caching in MVC should have been implemented.

Related

Distinguishing controller actions using authorization filters for caching

I'm trying to come up with a way to cache the actions in a controller only for anonymous users using the [OutputCache(Duration = 3600] annotation. E.g.:
[OutputCache(Duration = 3600)]
[ActionName("Test")]
public async Task<ViewResult> Test() {...}
In my research I came across a very clever way to use ActionMethodSelectionAttribute for exactly this, but as soon as the anonymous action is cached it applies for both the anonymous and authorized version.
I need this caching for the front page and for information pages. In all cases the top menu will be different for authorized users. The problem is that the menu is a partial added in the _Layout and so cached together with the rest of the action. E.g:
<body>
<div class="contain-to-grid oversizedContainer">
#Html.Action("Menu", "Framework")
</div>
#RenderBody()
The second problem is that on the information pages some of what is displayed will depend on if the user is autherized (checked using razor #if !Request.IsAuthenticated) and data in the view model (#Model).
I'd prefer something that would not require me to rewrite all my views.
PS! I'm aware of VaryByParam, but the actions do not take any parameters and are linked to directly.

Load ASP.Net User Controls via jQuery AJAX

I have mini modules(think iGoogle) that are currently loaded via the page calling LoadUserControl method and loading that control into PlaceHolders. I need to switch that implementation to loading the controls through a jQuery AJAX request. The problem currently lies in the fact that when I perform an AJAX Get, I can load the modules by appending them to the content but none of the functionality that would otherwise be working on a normal loaded control is there. For example, when I select a different option on the a DDL the page refreshes and nothing changes. I suspect because it is because the methods aren't being tied in when I perform a load through AJAX. Additionally when I use this method my flash content is not being loaded.
Am I doing something wrong here, or is there a better solution ?
$.ajax({
url: '/modules/UserModules.aspx?CID=12345',
type: "GET",
dataType: "html",
success: function(data) {
$('#column1').append($(data).find('div#lm li'));
$('#column2').append($(data).find('div#cm li'));
$('#column3').append($(data).find('div#rm li'));
alert('Load was performed.');
}
});
When you post back to the server, the server doesn't know about your user control because it was added to the page dynamically. Actually, you render the UC HTML, then add part of the rendered HTML to your page.
I would recommend steering clear of posting back to the server and using jQuery to retrieve any data using a Page Method or web method when you make a selection using your DDL.
I'm not sure I see the utility in using a UC for this. There are a number of ways to do this sort of thing. Like Jamie said, you can have a plain .aspx page that uses web methods. An asmx page. You can also not use web methods and just do as you would with php or classic ASP by writing your string results directly and processing them (shouldn't be your first choice though!).
But the most important thing you can take from this is debugging. Put a break point in your UC code to track server side. Use Firebug: open it, choose console and watch to see if your GET requests are actually doing what they need to. These days, you rarely need an alert to debug.

MVC: capture route url's and pass them to javascript function

Short:Is there a way to have a route-definition that will pass the "CONTROLLER/ACTION" string value to a JavaScript function in stead of actually going straight for the controller action?
More:I have a masterpage which contains the navigation of the site. Now, all the pages need this navigation wrapped around it at all times, but because I didn't want the navigation to constantly load with each pagecall, I changed all my pages to partialviews.
These partial views are loaded via the JQuery.Load() method whenever a menu item is clicked in the navigation.
This all worked very good, up till now because I noticed it's also a requirement of the website to be able to link directly to page X, rather then default.aspx.
So, as an example:The main page is my "default.aspx" page, this utilizes my master page with the navigation around it. And each call to a new page uses a javascript function that loads that particular partial view inside a div that is known in my masterpage. So, the url never changes away from "default.aspx", but my content changes seemlesly.
The problem is, those url's also need to be available when typed directly into the address bar. But, they're partial views, so loading them directly from the address bar makes them display without any masterpages around them. Therefore my question if it might be possible to capture the route typed into the address bar and pass that on to my JavaScript function that will load that route in the content div.
(I hope I explained it ok enough, if not, feel free to ask more information)
You are 100% correct to not want to hard code your URLs in your javascript code as it demolishes one of the primary tenants of MVC to do so. I'm one of those "separation of concerns" guys who will not write a single line of javascript outside of a dedicated .js file so I cannot dynamically specify the URL the way tuanvt has. What I do is use MVCs Url.Action method to emit my service URLs into hidden inputs on the master page (or the specific page if it is not used in multiple places). Then my script file simply pulls the value out of that hidden input and uses it just fine.
ASP.NET MVC View Source
<input id="serviceUrl" type="hidden" value="<%= Url.Action("Action", "Controller") %>" />
JS Source
$.getJSON($("#serviceUrl").val(), function(data) {
//write your JS success code here to parse the data
});
First challenge, as you are using AJAX to load the partial pages you need client accessible URLs for the javascript to call. Second challenge, you need URLs that will load the HomeController and pass the 'page' portion of the URL into the javascript.
For the first aspect I'd create some abstracted routes, i.e. "/ajaxaccess/{controller}/{action}/{id}" for the partial pages. That would be the first registered route. A second route would accept any controller/action reference and always get processed by the HomeController Index action.
In the /Home/Index action you grab the requested URL and slice it up, take the /{controller}/{action}/... section and pass that into your default.aspx using TempData. In your page check for the existence of the TempData and if it exists use the value therein to trigger your AJAX page load for the partial page (don't forget that you'll need to prepend '/ajaxaccess' (or whatever you choose) to the URL before it's passed to your page.
I'm not going to provide code here as I think the information you'll gain from working through this yourself will be invaluable to you moving forward.
You could use hash anchor (#) on your url and read it with javascript.
var url = document.location.toString();
if (url.match('#')) {
anchor = url.split('#');
// do whatever with anchor[1] ..
}
You can do something like this, put this in your javascript code on the view:
var szUrl=<%= ViewContext.RouteData.Route.ToString()%>;
Then the current route will be stored on the variable szUrl.

AJAX+ASP.NET

As far i learned ....AJAX is used for partial page refresh (overcoming the flickering effect in the web page) ....Is there any other features in AJAX....
Technically Yes. Ajax is used for "partial page" refresh there by eliminating the complete page refresh. There are 2 main advantages
Data transfer : Data transfer (To and from the server) is less compared to the entire page refresh
Better User experience : Since the user will not be seeing a blank page it gives the user an illusion of interacting with the site.
What can be done using AJAX is an ever ending list.
Ex: Gmail uses AJAX for it email. If you are using gmail and compare it with other email providers you will know the difference.
Facebook has rich AJAX features in its site.
SO uses AJAX for comments
I think What AJAX cannot do will be easier to mention. For example
AFAIK web browsers cannot maintain the view state of the AJAX enabled website.
Some AJAX enabled websites do not render properly in mobile browsers.
Anythign more?
Your question is a bit confusing, but you can do Ajax with ASP.NET. You can do partial page refreshes with Ajax among other things using the UpdatePanel in ASP.NET. You may also want to look at jQuery for a simpler more lightweight Ajax solution.
I think you're very mistaken. If AJAX had been created only to solve the problem of partial page refresh/page flickering, it would not have revolutionized the web in the way it has.
The single biggest advantage offerred by AJAX is Client-to-Server Communication that is initiated based on some action on the client. This instantly gives us the ability to make the web much more responsive and user friendly without users having to wait for page reloads and postbacks.
I would suggest that you spend some time researching the subject. Read up on the Wiki article on AJAX.
As far as ASP.NET is concerned, AJAX integrates very well into it. Mature AJAX frameworks such as ASP.NET AJAX and Anthem.NET obfuscate much of the internal details of the XmlHttpRequest.
Ajax has let me add some great new features to my web applications with the free Ajax toolkit. See the link
Ajax Examples
They do not come with out their issues but once you learn how to use them they can really add to the the users experience in you site.
ASP.NET uses as you know UpdatePanel for partial page refresh using AJAX.
A less known feature is something .NET calls web methods. This is really AJAX calls that is not connected to the GUI part of the page. You can declare (server-side) a method as a WebMethod, and in the client side, you can call that using JavaScript.
Example:
This example shows how to get the value of a session variable. Note that the method must be Shared - which means that it does not know of any member values on the page object.
As for all ASP.NET AJAX functionality, you need to have a ScriptManager element on the page. To enable page methods, you also need to add EnablePageMethods="true" to the ScriptManager like this:
<asp:ScriptManager ID="ScriptManager1" runat="server" EnablePageMethods="true" />
Server side code (VB):
<Services.WebMethod()> Public Shared Function GetPreviewImages() As String
Dim lPreviewImages As String = HttpContext.Current.Session("mPreviewImages")
If lPreviewImages IsNot Nothing Then
Return lPreviewImages
Else
Return ""
End If
End Function
Client side code:
//Declare the return methods:
function GetPreviewImages_Success(result, userContext, methodName) {
alert(result);
}
function GetPreviewImages_Failed(error, userContext, methodName) {
var errorMessage = 'Error in map server method ' + methodName ;
if(error !== null) errorMessage += '\n' + error.get_message();
alert(errorMessage);
}
// Call the page method:
PageMethods.GetPreviewImages(GetPreviewImages_Success, GetPreviewImages_Failed);
See also example in C#, which also includes how parameters work in the web method.

url rewriting + Asp.Net Login Form = Death

on our site we do url rewriting to generate massive amounts of database generated pages. on every page, there is a Login control for users. like this:
Internal aspx page: /DB.aspx?id=123
User visible url: /ABC/123.aspx, /ABC/456.aspx ... (url rewritten)
unfortunately, the tag on each page has an action attribute of "DB.aspx?id=123". when the user clicks the button the browser is posting to /ABC/DB.aspx?id=123 which of course does not exist.
solutions i tried:
1. change the action attribute by subclassing HtmlForm. this destroys the all other forms on the site.
2. remove the action attribute (so that the browser is always posting to the same url). this works on the rewritten pages but on "/" (the default.aspx in the root dir) i get a message that the verb post is not allowed on "/" (iis 6 and i have no control over mappings)
anybody?
Check this really nice blog post from scott gu, http://weblogs.asp.net/scottgu/archive/2007/02/26/tip-trick-url-rewriting-with-asp-net.aspx.
"Specifically, you can take advantage of the new ASP.NET 2.0 Control Adapter extensibility architecture to customize the rendering of the control, and override its "action" attribute value with a value you provide. This doesn't require you to change any code in your .aspx pages"
Check the section: "Handling ASP.NET PostBacks with URL Rewriting", I have used the adapter he posted successfully.
Ps. be aware there are some issues on asp.net when using url rewrite when using cookieless session, and the rewritten url is deeper than the original page, just like the one you have. (/abc/apage vs. /db?). The issue is right into the source code of the framework, there are workarounds but that's a whole subject (with tradeoffs :( ... you might want to have them at the same level).
Semantics maybe, but does the action attribute = "DB.aspx?id=123" or "/DB.aspx?id=123"? Assuming your URL rewriting allows pass-through to physical pages, this might be your issue.
I never did it, but I saw the code using Reflector and I guess you can fix it this way:
On the page:
this.Form.Action = null;
or:
this.Form.SetAttribute("action", null);
If that doesn't work, just set the path you want:
this.Form.SetAttribute("action", "ABC/123.aspx");
If you upgrade to ASP.NET 3.5 SP1, the action property is now properly recognized and can be set from codebehind.

Resources