Output Caching using BOTH varybyparam and varybycustom - asp.net

I'm trying to do something which should be very simple...I have a site with a dropdown from which the user selects a group. Thereafter, the user navigates through the site using querystring arguments from menus. So I want the caching to be dependent on the querystring - this seems to work. I also want the cache to be dependent on the group that they selected.
But when the querystring is empty, neither cache element seems to work - the page is just whatever the version was for the last selected group. My cache directive looks like this:
<%# OutputCache Duration="300" VaryByCustom="currentAtomId" VaryByParam="documentId;folderId;sectionId;renderMode;typeId" %>
My varyByCustom code looks like this:
public override string GetVaryByCustomString(HttpContext context, string custom)
{
switch (custom)
{
case "currentAtomId":
var currentAtomId = SecurityManifold.Create().CurrentAtomId;
var returnString = currentAtomId == null ? Guid.NewGuid().ToString() : currentAtomId.ToString();
return returnString;
default:
throw new ArgumentException(string.Format("Argument '{0}' is not a valid cache argument.", custom));
}
}
The call to CurrentAtomId boils down to this:
public static int? GetCurrentAtomIdFromContext(HttpContext context)
{
int entityId;
if (context.Session == null)
{
throw new InvalidOperationException("Session is null");
}
var sessionEntityId = context.Session["CurrentEntityId"];
if (sessionEntityId == null || string.IsNullOrEmpty(sessionEntityId.ToString()))
{
return null;
}
if (!int.TryParse(sessionEntityId.ToString(), out entityId))
{
return null;
}
return entityId;
}
Finally, the code which specifies the CurrentEntityId is this:
var selectedEntityId = this.lstSecurityEntities.SelectedValue;
if (string.IsNullOrEmpty(selectedEntityId))
{
return;
}
Session["CurrentEntityId"] = selectedEntityId;
var possibleQueryString = Request.QueryString.ToString();
if (!string.IsNullOrEmpty(possibleQueryString))
{
possibleQueryString = "?" + possibleQueryString;
}
Response.Redirect("default.aspx" + possibleQueryString);
I'm baffled. Any thoughts would be appreciated.

I eventually determined the problem - when output caching is placed at a PAGE level (as opposed to a control level), the session is not available, and throws an exception. Because this exception is occurring in Global ABOVE the global error handler, it fails silently. I eventually figured this out by wrapping a try-catch block around the cache key generation code in VaryByCustomString and Response.Write-ing it out.
What a beatdown...at any rate, the solution is to implement caching at the control level, which unfortunately is a lot more work because the pieces of the page work together...but it's better than no caching. I hope this helps save somebody else some time.
Bottom Line: for varyByCustomString in global.asax - SESSION IS NOT AVAILABLE WHEN CACHING AT THE PAGE LEVEL.

Related

SiteMap.CurrentNode returns null when using query parameter

I have written a custom ASP.NET sitemap provider, it works well but if I add a query parameter to a virtual path SiteMap.CurrentNode returns null - it does not find the page. I've put breakpoints in all my code and never once does it enter my virtual path provider with a query parameter. What am I missing here?
I found an answer to my question and post it here for later use. Seems the sitemap provider always uses the path without the querystring parameters when lookup up matching paths. The trick is to not use Reqest.RawUrl in your overriden SiteMapProvider.CurrentNode() function but rather use Request.Path ; I've posted my solution below:
public class CustomSiteMapProvider : SiteMapProvider {
// Implement the CurrentNode property.
public override SiteMapNode CurrentNode {
get {
var currentUrl = FindCurrentUrl();
// Find the SiteMapNode that represents the current page.
var currentNode = FindSiteMapNode(currentUrl);
return currentNode;
}
}
// Get the URL of the currently displayed page.
string FindCurrentUrl() {
try {
// The current HttpContext.
var currentContext = HttpContext.Current;
if (currentContext != null) return currentContext.Request.Path;
throw new Exception("HttpContext.Current is Invalid");
} catch (Exception e) {
throw new NotSupportedException("This provider requires a valid context.", e);
}
}
...

#Url.Content doesnt resolve absolute path on one server but does on another

We currently have two different servers on same domain. But one server resolves
#Url.Content("~/api/User")'
as
http://domain.com/virtualdirectory/api/User
where as other server doesnt resolve it absolutely; rather it resolves it relatively like
api/user
The code base is same and we are using MVC4. I am not sure as to where we went wrong or if there is any IIS/DNS settings that need to be done in order to get this fixed.
All help is appreciated; thanks :)
This is related with the IIS Rewriting module in your IIS web server that return the path to http://domain.com/virtualdirectory/api/User
Take a look on the part of source code of #Url.Content below:
private static string GenerateClientUrlInternal(HttpContextBase httpContext, string contentPath)
{
if (String.IsNullOrEmpty(contentPath))
{
return contentPath;
}
// can't call VirtualPathUtility.IsAppRelative since it throws on some inputs
bool isAppRelative = contentPath[0] == '~';
if (isAppRelative)
{
string absoluteContentPath = VirtualPathUtility.ToAbsolute(contentPath, httpContext.Request.ApplicationPath);
return GenerateClientUrlInternal(httpContext, absoluteContentPath);
}
// we only want to manipulate the path if URL rewriting is active for this request, else we risk breaking the generated URL
bool wasRequestRewritten = _urlRewriterHelper.WasRequestRewritten(httpContext);
if (!wasRequestRewritten)
{
return contentPath;
}
// Since the rawUrl represents what the user sees in his browser, it is what we want to use as the base
// of our absolute paths. For example, consider mysite.example.com/foo, which is internally
// rewritten to content.example.com/mysite/foo. When we want to generate a link to ~/bar, we want to
// base it from / instead of /foo, otherwise the user ends up seeing mysite.example.com/foo/bar,
// which is incorrect.
string relativeUrlToDestination = MakeRelative(httpContext.Request.Path, contentPath);
string absoluteUrlToDestination = MakeAbsolute(httpContext.Request.RawUrl, relativeUrlToDestination);
return absoluteUrlToDestination;
}
Use the codes below to check whether your web servers are having the URL rewritten:
bool requestWasRewritten = (httpWorkerRequest != null && httpWorkerRequest.GetServerVariable("IIS_WasUrlRewritten") != null);
And Also:
private volatile bool _urlRewriterIsTurnedOnCalculated = false;
private bool _urlRewriterIsTurnedOnValue;
private object _lockObject = new object();
private bool IsUrlRewriterTurnedOn(HttpContextBase httpContext)
{
// Need to do double-check locking because a single instance of this class is shared in the entire app domain (see PathHelpers)
if (!_urlRewriterIsTurnedOnCalculated)
{
lock (_lockObject)
{
if (!_urlRewriterIsTurnedOnCalculated)
{
HttpWorkerRequest httpWorkerRequest = (HttpWorkerRequest)httpContext.GetService(typeof(HttpWorkerRequest));
//bool urlRewriterIsEnabled = (httpWorkerRequest != null && httpWorkerRequest.GetServerVariable(UrlRewriterEnabledServerVar) != null);
bool urlRewriterIsEnabled = (httpWorkerRequest != null && httpWorkerRequest.GetServerVariable("IIS_UrlRewriteModule") != null);
_urlRewriterIsTurnedOnValue = urlRewriterIsEnabled;
_urlRewriterIsTurnedOnCalculated = true;
}
}
}
return _urlRewriterIsTurnedOnValue;
}
In summary, If both requestWasRewritten and IsUrlRewriterTurnedOn
return true, that means one of your web server has IIS Rewrite Module
turned on and running while the other one doesn't have.
For more details on ASP.NET MVC source codes, please refer to this link:
http://aspnetwebstack.codeplex.com/
Hope it helps!

Web Performance Testing context paramters

I used the MSDN guide on creating Custom Extraction Rule, which presents this example (Extract method):
public override void Extract(object sender, ExtractionEventArgs e)
{
if (e.Response.HtmlDocument != null)
{
foreach (HtmlTag tag in e.Response.HtmlDocument.GetFilteredHtmlTags(new string[] { "input" }))
{
if (String.Equals(tag.GetAttributeValueAsString("name"), Name, StringComparison.InvariantCultureIgnoreCase))
{
string formFieldValue = tag.GetAttributeValueAsString("value");
if (formFieldValue == null)
{
formFieldValue = String.Empty;
}
// add the extracted value to the web performance test context
e.WebTest.Context.Add("someNameHere", formFieldValue);
e.Success = true;
return;
}
}
}
// If the extraction fails, set the error text that the user sees
e.Success = false;
e.Message = String.Format(CultureInfo.CurrentCulture, "Not Found: {0}", Name);
}
However, I just don't know how to use access the someNameHere in the Web Test and add it to the QueryString as a parameter.
Any help would be greatly appreciated.
Right-click on the request in the web test and select "Add URL query string parameter". Alter the name as needed and into the value field enter {{someNameHere}}. The doubled curly braces call for a context parameter value to be inserted. The doubled curly braces can be used to insert the value of a context parameter into many other places in a web test. Note that strings such as text{{someNameHere}}moretext can be used to join context values to other strings.

FB signed_request issue ASP.net c#

I want to access signed_request param on my asp.net page. FB is redirecting the request successfully to the right page. However, it is a GET request thus no signed_request param is passed.
Please help me out.
Trying to read the value using Request.Form["signed_request"] but it is blank.
Please let me know if this is due to wrong configuration or due to some change at FB end. How do I get this working.
Thanks
Syed Adil Umair
As you probably already know, when your page tab app is firstly loaded, Facebook makes a POST request to the URL you specified in application's Page Tab URL (or Secure Page Tab URL) along with the signed_request string. This is why its value is not null on your landing page. But when you click on a link to navigate to another page inside your tab app, it suddenly becomes null. This is how it should be because you are making a GET request with no signed_request. To make your other pages able to access signed_request, you need to store it. I came up with the following code which works for me but if someone comes with a better solution, I would be glad to hear it:
public static string StoredSignedRequest
{
get
{
string signedRequest = HttpContext.Current.Request["signed_request"];
// If signed_request is provided, store it
if (!String.IsNullOrEmpty(signedRequest))
{
WriteCookie("fb-app-signed-request", signedRequest);
return signedRequest;
}
else
{
return ReadCookie("fb-app-signed-request");
}
}
}
public static void WriteCookie(string strCookieName, string strCookieValue)
{
var hcCookie = new HttpCookie(strCookieName, strCookieValue);
HttpContext.Current.Response.Cookies.Set(hcCookie);
}
public static string ReadCookie(string strCookieName)
{
foreach (string strCookie in HttpContext.Current.Response.Cookies.AllKeys)
{
if (strCookie == strCookieName)
{
return HttpContext.Current.Response.Cookies[strCookie].Value;
}
}
foreach (string strCookie in HttpContext.Current.Request.Cookies.AllKeys)
{
if (strCookie == strCookieName)
{
return HttpContext.Current.Request.Cookies[strCookie].Value;
}
}
return null;
}
Then you can use a JSON parser to parse the value of StoredSignedRequest. In my case, I use Newtonsoft:
public static JObject GetSignedRequestJsonObject()
{
string signed_request = StoredSignedRequest;
if (String.IsNullOrEmpty(signed_request))
{
// If signed_request is null on all pages except the landing page, add the following code to all pages so that it is stored:
// <input type="hidden" name="signed_request" value="<%= FacebookAppHelper.StoredSignedRequest %>" />
return null;
}
string payload = signed_request.Split('.')[1];
UTF8Encoding encoding = new UTF8Encoding();
string decodedJson = payload.Replace("=", string.Empty).Replace('-', '+').Replace('_', '/');
byte[] base64JsonArray = Convert.FromBase64String(decodedJson.PadRight(decodedJson.Length + (4 - decodedJson.Length % 4) % 4, '='));
string json = encoding.GetString(base64JsonArray);
return JObject.Parse(json);
}
The critical part is to not forget to add the hidden field (see commented line above) to all pages that you plan to access using a GET request inside your page tab application.

Why doesn't this page redirect work in IE?

I check for a session variable in my asp.net page and redirect to my default page.
if (Session["OrgId"] != null)
{
// some logic
}
else
{
Response.Redirect("../Default.aspx?Sid=1", false);
}
and in my default.aspx page i ve done this,
Int64 id = GetId(Request.RawUrl.ToString());
if (id == 1)
{
// I ll show "Session Expired"
}
public Int64 GetId(string url)
{
Int64 id = 0;
if (url.Contains("="))
{
if (url.Length > url.Substring(url.LastIndexOf("=")).Length)
{
id = Convert.ToInt64(url.Substring(url.LastIndexOf("=") + 1));
}
}
return id;
}
This works in googlechrome,firefox but not in IE. "Operation aborted" exception.
try changing
Response.Redirect("../Default.aspx?Sid=1", false);
to
Response.Redirect("../Default.aspx?Sid=1");
or
Response.Redirect("../Default.aspx?Sid=1", true);
HttpResponse.Redirect Method
Redirect(String, Boolean)
Redirects a client to a new URL.
Specifies the new URL and whether
execution of the current page should
terminate.
That means that Response.Redirect("../Default.aspx?Sid=1", false); won't terminate the current response.
IE is much more sensitive than other browsers about changing the DOM after headers have been sent but before the page terminates.
Here is your problem:
Response.Redirect("../Default.aspx?Sid=1", false);
Try changing false to true.
Also, be very careful about the casing in your page names. "Default.aspx" and "default.aspx" are really not the same page even if Windows lets you get away with it.

Resources