How to implement VaryByCustom caching? - c#

I'm trying to implement functionality to cache certain pages depending on the host. This is because I can have multiple versions of a page which have the same parameters, and where the only difference in terms of a request is the host that is being requested.
So, for example these two URLs will request the same page, but they are styled differently:
http://www.a.com/something/specific
and
http://www.b.com/something/specific
I'm going through the example outlined here:
http://msdn.microsoft.com/en-us/library/5ecf4420%28v=VS.90%29.aspx
but it's not making sense to me.
I've added this to my global.asax:
public override string GetVaryByCustomString(HttpContext context, string arg)
{
if (arg == "host")
{
return "host=" + context.Request.Url.Host;
}
return base.GetVaryByCustomString(context, arg);
}
and the example states "To set the custom string programmatically, call the SetVaryByCustom method and pass it the custom string to use", with code similar to the following:
Response.Cache.SetVaryByCustom("host");
The problem is I'm not sure what to do with this. I've added the previous line to MvcApplication_EndRequest because it seems like it makes sense, but I don't think this is right because when I set breakpoints in GetVaryByCustomString they never get hit.
Can somebody please tell me what I'm missing here? Or if I need to do this differently?
Edit: RE Darin's answer below, I'm already decorating my actions with:
[CustomOutputCache(CacheProfile = "FundScreener")] // or similar depending on the action
where CustomOutputCacheAttribute is defined as:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class CustomOutputCacheAttribute: OutputCacheAttribute
{
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
AddLabelFilesDependency(filterContext);
base.OnResultExecuted(filterContext);
}
private static void AddLabelFilesDependency(ControllerContext filterContext)
{
IConfigurationManager configurationManager = ObjectFactory.TryGetInstance<IConfigurationManager>();
if (configurationManager == null
|| filterContext == null
|| filterContext.RequestContext == null
|| filterContext.RequestContext.HttpContext == null
|| filterContext.RequestContext.HttpContext.Response == null
)
{
return;
}
string[] files = Directory.GetFiles(configurationManager.LabelsDirectoryPath, "*.xml");
foreach(var file in files)
{
filterContext.RequestContext.HttpContext.Response.AddFileDependency(file);
}
}
}
where the profile is defined as:
<add name="FundScreener"
location="Server"
enabled="true"
varyByParam="*"
duration="1200"
sqlDependency="mmftms:offline.ScreenerData"/>
Do I need to change this?

You don't need to call SetVaryByCustom in MVC. You could use the OutputCache attribute. Checkout the following blog post.

If you want to have different cache for different hosts, you can use:
VaryByHeader="host"
Because, that would make it use the value of header "host" in the request to vary the cache. You can add this in the OutputCache directive on your controllers/actions, or you can specify it globally in your web.config probably.
a host header will always be present if you use host-bindings, which seems to be the case for you.

GetVaryByCustomString(...) is called by the caching layer per request and you have an opportunity to inspect the request and the passed-in argument to decide how to "categorize" this request. So if you set the VaryByCustom property/attribute to "host", you would then write code inside GetVaryByCustomString function which returns the host (as in your example, above). If the caching layer finds that it has already cached the argument "host" with the value you've returned then it will return the cached response, otherwise it executes the request and adds it to the cache.

Based on your edit, add VaryByCustom="host" to your FundScreener output cache profile.

Related

How to execute a service in Global's Session_Start()

I currently have a asp.net mvc application with ABP implementation. I currently want to execute a service method inside the Session_Start(), may I ask how abouts would I do that.
The service can be executed anywhere I have access to the IOC resolve but I'm in the global file and I'm not entirely sure how to do that from there.
protected void Session_Start()
{
// starting a session and already authenticated means we have an old cookie
var existingUser = System.Web.HttpContext.Current.User;
if (existingUser != null && existingUser.Identity.Name != "")
{
// execute app service here.
// if I'm exposed to IOCresolver I would do the following below
var srv = _iocResolver.Resolve<SettingsAppService>();
srv.UpdateItems();
}
}
May I ask how do I access IOC resolver on global.asax.cs file, if even possible. My goal is to execute the service when the user has re-established his session.
From the documentation on Dependency Injection:
The IIocResolver (and IIocManager) also have the CreateScope extension method (defined in the Abp.Dependency namespace) to safely release all resolved dependencies.
At the end of using block, all resolved dependencies are automatically removed.
If you are in a static context or can not inject IIocManager, as a last resort, you can use a singleton object IocManager.Instance everywhere.
So, use a scope with IocManager.Instance:
using (var scope = IocManager.Instance.CreateScope()) { ... }
→ IocManager.Instance.UsingScope(scope => { ... })
protected void Session_Start()
{
// Starting a session and already authenticated means we have an old cookie
var existingUser = System.Web.HttpContext.Current.User;
if (existingUser != null && existingUser.Identity.Name != "")
{
IocManager.Instance.UsingScope(scope => // Here
{
// Execute app service here.
var srv = scope.Resolve<SettingsAppService>();
srv.UpdateItems();
});
}
}
You can create a static link to your IoC resolver and use it in Global.asax. You even can add it to Global.asax.cs. Set this property after container registration and use it from anywhere.
public static YourIocResolver IocResolver { get; set; }

Force specific casing for known header in ASP.NET Core 2

I have an ASP.NET Core 2 app with Kestrel. The app is deployed to AWS Lambda/API Gateway. Everything works as expected except for a small detail that makes all the difference.
Some requests to my app need to issue multiple security-related Set-Cookie headers. Due to the way data are passed between API Gateway and Lambda, duplicate header names are joined together, which renders the Set-Cookie header invalid and the browser refuses to honor it.
A suggested solution to overcome this limitation is to use multiple headers names that vary only by casing: Set-Cookie, Set-cookie, set-cookie...
I know this is a hacky solution, but if it works it should be good enough while AWS fixes this limitation.
However, when using HttpContext.Response.Headers.Add(name, value), known header names are normalized and become regular duplicate headers.
Is it possible to go around this normalization mechanism or achieve the end goal in some other way?
When I started working on this question, I thought it would be easy. After half a day of researches (so cool that I'm on vacation), I could finally share the results.
HttpContext.Response.Headers has type of IHeaderDictionary. By default, in ASP.NET Core application on Kestrel, FrameResponseHeaders implementation is used. The main logic resides in FrameHeaders base class. This headers dictionary is highly optimized for seting / getting of frequently used standard http headers. Here is a code snippet that handles setting cookie (AddValueFast method):
if ("Set-Cookie".Equals(key, StringComparison.OrdinalIgnoreCase))
{
if ((_bits & 67108864L) == 0)
{
_bits |= 67108864L;
_headers._SetCookie = value;
return true;
}
return false;
}
As far as StringComparison.OrdinalIgnoreCase is used for key comparison, you can't set another cookie header that differs only by the case. This makes sense because HTTP headers are case-insensitive.
But let's try to overcome it.
The obvious solution here is to replace implementation of IHeaderDictionary with the case-sensitive one. ASP.NET Core contains a lot of seams and extensibility points for this, starting from IHttpResponseFeature that contains setable Headers property and ending with the possibility to replace implementation of HttpContext.
Unfortunately, all those replacements will not do the trick when running on Kestrel. If you check source code of Frame class that is responsible for writing HTTP response headers, you will see that it creates instance of FrameResponseHeaders by itself and does not respect any other instances set through IHttpResponseFeature or HttpContext.Response.Headers:
protected FrameResponseHeaders FrameResponseHeaders { get; } = new FrameResponseHeaders();
So we should return back to FrameResponseHeaders and its base FrameHeaders classes and try to adjust their behavior.
FrameResponseHeaders class uses fast setting of known headers (see AddValueFast above) but stores all other unknown headers in MaybeUnknown field:
protected Dictionary<string, StringValues> MaybeUnknown;
which is initialized as:
MaybeUnknown = new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase);
We could try to bypass fast header setting and add them directly to the MaybeUnknown dictionary. We should however replace the dictionary created with StringComparer.OrdinalIgnoreCase comparer with the default implementation that is case-sensitive.
MaybeUnknown is a protected field and again we can't make Kestrel to use our custom implementation for holding class. That's why we are forced to set this field through reflection.
I've put all this dirty code into extension class over FrameHeaders:
public static class FrameHeadersExtensions
{
public static void MakeCaseInsensitive(this FrameHeaders target)
{
var fieldInfo = GetDictionaryField(target.GetType());
fieldInfo.SetValue(target, new Dictionary<string, StringValues>());
}
public static void AddCaseInsensitiveHeader(this FrameHeaders target, string key, string value)
{
var fieldInfo = GetDictionaryField(target.GetType());
var values = (Dictionary<string, StringValues>)fieldInfo.GetValue(target);
values.Add(key, value);
}
private static FieldInfo GetDictionaryField(Type headersType)
{
var fieldInfo = headersType.GetField("MaybeUnknown", BindingFlags.Instance | BindingFlags.NonPublic);
if (fieldInfo == null)
{
throw new InvalidOperationException("Failed to get field info");
}
return fieldInfo;
}
}
MakeCaseInsensitive replaces MaybeUnknown with case-sensitive dictionary.
AddCaseInsensitiveHeader adds header directly to MaybeUnknown dictionary bypassing fast header setting.
Remaining part is only to call these methods in appropriate places in the controller:
[Route("api/[controller]")]
public class TestController : Controller
{
[NonAction]
public override void OnActionExecuting(ActionExecutingContext context)
{
var responseHeaders = (FrameResponseHeaders)HttpContext.Response.Headers;
responseHeaders.MakeCaseInsensitive();
}
// GET api/values
[HttpGet]
public string Get()
{
var responseHeaders = (FrameResponseHeaders)HttpContext.Response.Headers;
responseHeaders.AddCaseInsensitiveHeader("Set-Cookie", "Cookies1");
responseHeaders.AddCaseInsensitiveHeader("SET-COOKIE", "Cookies2");
return "Hello";
}
}
Here is result headers set:
Described solution is a very dirty hack. It will work only with Kestrel and things could change with future releases. Everything would be much easier and cleaner if Kestrel fully supports ASP.NET seams. But if you don't have any other choices for this moment, I hope this will help you.
Thanks #CodeFuller for your prompt and thorough response. However, after digging into Amazon.Lambda.AspNetCoreServer source code, I realized a custom IServer implementation is used instead of Kestrel.
I located the code inside APIGatewayProxyFunction where headers are copied to the response and joined together:
foreach (var kvp in responseFeatures.Headers)
{
if (kvp.Value.Count == 1)
{
response.Headers[kvp.Key] = kvp.Value[0];
}
else
{
response.Headers[kvp.Key] = string.Join(",", kvp.Value);
}
...
}
But just like Kestrel, this library uses its own implementation of IHttpResponseFeature. It is inside a multi-purpose InvokeFeatures class, which is instantiated directly and cannot be replaced through configuration. However, APIGatewayProxyFunction exposes a few virtual Post* methods to modify some parts of the request/response at different points. Unfortunately, there is no method to intercept the ASP.NET core response just before it is converted into an APIGatewayProxyResponse (something like a PreMarshallResponseFeature maybe?), so the best option I could find was to add some code to PostCreateContext:
var responseFeature = context.HttpContext.Features.Get<IHttpResponseFeature>();
responseFeature.Headers = new MyHeaderDictionary(responseFeature.Headers);
MyHeaderDictionary is a wrapper around IHeaderDictionary where I override the IEnumerator<KeyValuePair<string, StringValues>> GetEnumerator() method:
class MyHeaderDictionary : IHeaderDictionary
{
private readonly IHeaderDictionary _inner;
public MyHeaderDictionary(IHeaderDictionary inner)
{
_inner = inner;
}
public IEnumerator<KeyValuePair<string, StringValues>> GetEnumerator()
{
foreach (var kvp in _inner)
{
if (kvp.Key.Equals(HeaderNames.SetCookie) && kvp.Value.Count > 1)
{
int i = 0;
foreach (var stringValue in kvp.Value)
{
// Separate values as header names that differ by case
yield return new KeyValuePair<string, StringValues>(ModifiedHeaderNames[i], stringValue);
i++;
}
}
else
{
yield return kvp;
}
}
}
// Implement all other IHeaderDictionary members as wrappers around _inner
}
This returns different Set-Cookie headers inside the foreach (var kvp in responseFeatures.Headers) block in APIGatewayProxyFunction.
This solution was tested and seems to work so far. No edge cases or performance considerations have been taken into account, though. Suggestions and improvements are welcome.

VaryByCustom - Data being shown to wrong users

I'm using Windows Auth in an Intranet setting to Cache information pulled from Active Directory. The purpose of the Cache is to speed up the page, as reading from AD is not particularly fast, and doesn't need to be done every single time. (The data doesn't change all that often)
To do this, i'm setting a custom key in HttpContext.Application.
This is the code located in Global.asax to handle VaryByCustom:
public override string GetVaryByCustomString(HttpContext context, string arg)
{
System.Diagnostics.Debug.Print("GetVaryByCustomString : " + context.Application["BrowsingSession_Key"].ToString());
if (arg == "BrowsingSession_Key")
{
object o = context.Application["BrowsingSession_Key"];
if (o == null)
{
o = Guid.NewGuid();
context.Application["BrowsingSession_Key"] = o;
}
return o.ToString();
}
return base.GetVaryByCustomString(context, arg);
}
In my BaseController (Inherited by all my controllers):
protected override void Initialize(RequestContext requestContext)
{
base.Initialize(requestContext);
//Custom Cache Initiation Variable
if (HttpContext.Application["BrowsingSession_Key"] == null)
{
HttpContext.Application["BrowsingSession_Key"] = Guid.NewGuid();
System.Diagnostics.Debug.Print("BaseController.Initialize : " + HttpContext.Application["BrowsingSession_Key"].ToString());
}
}
And finally, in my method inside a controller:
[OutputCache(Duration = 300, VaryByCustom = "BrowsingSession_Key", Location = OutputCacheLocation.Server)]
public ActionResult Index(HomeViewModel model)
//...
return View("index", model);
}
The issue is simple - the first person to view the page has their info cached, and the Guid for BrowsingSession_Key is set.
However, the next user visits the page within the 5 minute window, and reaches the last users cached content.
As you can see - i'm attempting to give each user a unique BrowsingSession_Key, so that they get their own cached content.
I'm using VaryByCustom so that i can quickly invalidate the cache by assigning a new BrowsingSession_Key (Guid) to that user - and pull a non-cached copy of a page for them.
Can you see what's going wrong here?
From my testing - it seems Initialize is often called, as is GetVaryByCustomString, in the places you'd expect them to be called. However, i can't run debug as multiple users, so i can't see why they're getting the same Guid and the same outputcache.
As it turns out, Application-level variables are not a good place to be storing per-user information, even temporarily.
In the end, i swapped over to using a cookie with a stored GUID, and invalidate the cookie (reset the GUID, or delete & Re-create). This meant multiple users were then able to use the site, with GetVaryByCustomString instead handling information stored in the Cookie.
This works for me - but i need to take into account the possibility of users swapping cookies, so will look further into encryption options.
For now - the answer is - Don't use Application-level variables this way, they're not suited to such tasks.

StackFrame behaving differently in release mode

Here is my code:
public class UserPreferences
{
/// <summary>
/// The EMail signature.
/// </summary>
[UserPreferenceProperty(Category = "Email", DefaultValue = "My default value")]
public static string Signature
{
get
{
return UserPreferenceManager.GetValue();
}
set
{
UserPreferenceManager.SetValue(value);
}
}
}
public static string GetValue()
{
if (((VTXPrincipal)Thread.CurrentPrincipal).VTXIdentity.OperatorID == null)
{
throw new Exception("Missing Operator ID");
}
string value = string.Empty;
var frame = new StackFrame(1); ***** <------ problem here.....
var property = frame.GetMethod();
var propertyname = property.Name.Split('_')[1];
var type = property.DeclaringType; ***** <------ problem here.....
if (type != null)
{
var userPreference = typeof(UserPreferences).GetProperty(propertyname).GetCustomAttributes(true).FirstOrDefault() as UserPreferencePropertyAttribute;
if (userPreference != null)
{
string category = userPreference.Category;
string description = propertyname;
value = GetValue(category, description, ((VTXPrincipal)Thread.CurrentPrincipal).VTXIdentity.OperatorID);
if (value == null)
{
// always return something
return userPreference.DefaultValue;
}
}
else
{
throw new Exception("Missing User Preference");
}
}
return value;
}
Inside the GetValue method, StackFrame works differently in release mode vs. debug mode.
In debug mode, I correctly get the property name as signature
But in Release mode, property name is GetUserPreferenceValueTest because this is the test method that makes the calls as clients.
There fore my code works in debug mode but fails in release mode.
Q. How can I use StackFrame properly so it works in Debug vs. Release modes.
Q. Is there any other way to get calling property name and related information at run time?
I answered a similar question once, please read my answer here.
In short, this is a very bad design decision because your method is a hypocrite—it talks different to different callers but doesn't tell it in open. Your API should never, ever rely on who calls it. Also, the compiler can break the stack trace in an unexpected way due to language features like lambdas, yield and await, so even if this worked in Release mode, it would certainly break some day.
You're effectively building a complex indirection mechanism instead of using language feature designed for passing information to methods—method parameters.
Why do you use attributes? Do you read them elsewhere?
If you do, and you don't want to repeat "Email" both as parameter to GetValue call and attribute value, you may consider passing a property Expression<> to GetValue, which will extract the attribute. This is similar to your solution, but it is explicit:
[UserPreferenceProperty(Category = "Email", DefaultValue = "My default value")]
public string Signature
{
get { return GetValue (prefs => prefs.Signature); }
set { SetValue (prefs => prefs.Signature, value); }
}
This answer shows how to implement this.
I see you are checking Thread.CurrentPrincipal in your code. Again, this is not a really good practice because it is not obvious to client code that accessing a property can result in an exception. This is going to be a debugging nightmare for someone who supports your code (and trust me, your code may run for years in production, long after you move onto another project).
Instead, you should make VTXIdentity a parameter to your settings class constructor. This will ensure the calling code knows you enforce security on this level and by definition knows where to obtain this token. Also, this allows you to throw an exception as soon as you know something is wrong, rather than when accessing some property. This will help maintainers catch errors earlier—much like compile errors are better than runtime errors.
Finally, while this is a fun exercise, there are plenty performant and tested solutions for storing and reading configuration in C#. Why do you think you need to reinvent the wheel?
Assuming your problem survives the discussion of whether you could just use another library rather than rolling your own... if you find yourself using C# 5 &.NET 4.5, take a look at the CallerMemberName attribute. With CallerMemberName you can modify your GetValue() method signature to be
public static string GetValue([CallerMemberName] string callerName = "")
The property can then call GetValue() with no parameter and you'll get the property name passed into GetValue() as you want.

Workaround for HttpContext.HideRequestResponse being internal? Detect if HttpContext.Request is really available?

We're migrating an application to use IIS7 integrated mode. In library code that is designed to work either within the context of an HTTP request or not, we commonly have code like this:
if (HttpContext.Current != null &&
HttpContext.Current.Request != null) {
// do something with HttpContext.Current.Request
} else {
// do equivalent thing without HttpContext..
}
But in IIS7 integrated mode the check for HttpContext.Current.Request throws an exception whenever this code is called from Application_Start.
protected void Application_Start(object sender, EventArgs e)
{
SomeLibrary.DoSomethingWithHttpContextCurrentDetection();
}
Results in:
System.Web.HttpException: Request is not available in this context
How can I detect whether the request is really available without wrapping these calls in an exception handler and taking action based on whether an exception is generated or not.
Looking at HttpContext in Reflector I see it has an internal bool HideRequestResponse field but it's internal so I can only get to it with reflection and that's fragile. Is there a more official/approved way to determine if it's ok to call HttpContext.Request?
This blog post about the subject says not to use HttpContext, but how, in generic library code, can you determine if it's ok to use HttpContext?
http://mvolo.com/iis7-integrated-mode-request-is-not-available-in-this-context-exception-in-applicationstart/
I'm using the work-around mentioned there which is to use Application_BeginRequest and an initialized field to only initialize once as part of BeginRequest, but that has to be done in every calling application whereas I'd prefer to make the library code more robust and handle this situation regardless of where it's called from.
I would refactor your code to this:
if (IsRequestAvailable())
{
// do something with HttpContext.Current.Request...
}
else
{
// do equivalent thing without HttpContext...
}
public Boolean IsRequestAvailable()
{
if (HttpContext.Current == null)
return false;
try
{
if (HttpContext.Current.Request == null)
return false;
}
catch (System.Web.HttpException ex)
{
#if DEBUG
// Testing exception to a magic string not the best practice but
// it works for this demo.
if (ex.Message == "Request is not available in this context")
return false;
throw;
#else
return false;
#endif
}
return true;
}
Your question asked not to use exception handling (I assume for performance reasons) and my answer does. However, by changing your code from using "If (HttpContext.Current != null && HttpContext.Current.Request != null)" to "If (IsRequestAvailable())" you only have one place to change the code when you find an answer how not to use exception handling.
I'm afraid the answer is that you can't get what you want - Microsoft sees this case as an 'exceptional circumstance' and so it will throw an exception.
You can use reflection as you describe in your answer but you don't want to and so are limited by the API that Microsoft have provided, for better or for worse.
If you do decide to use reflection, of note is the HttpApplication.InitInternal method which is what sets the HideRequestResponse flag.
Hope that helps. I would suggest you file a report with Microsoft Connect.
You should not even use Request (or Response) in the Application_Start since application could be started without a request. So in the future your application won't even run when other parts of framework stop providing the Request object.
If you want to just hack it temporarily, you could use Reflection (if you have above-medium trust) or catching an exception (even though you don't want to) and store the result in a static variable or possibly use a static HttpContext wrapper:
Also you could use HttpRuntime.UsingIntegratedPipeline.
So the best approach is remove the dependance of your classes on HttpContext when they are being initialized or not initalize them in appstart.
What is your reasoning to use Request in the app start anyway? For statistics? Or just telling the user he woke the application?
Edited with code to explain better:
public static class ContextWrapper
{
public static HttpRequest Request
{
get
{
HttpContext context = HttpContext.Current;
if (context == null) return null;
if (HttpRuntime.UsingIntegratedPipeline)
{
try { return context.Request; }
catch (HttpException e) { /* Consume or log e*/ return null; }
// Do not use message comparison - .NET translates messages for multi-culture environments.
}
return context.Request;
}
}
}
And in code:
if (ContextWrapper.Request != null) //...
Or a user-controlled faster way:
public static class ContextWrapper2
{
public static bool IsIis7IntegratedAppStart { get; set; }
public static HttpRequest Request
{
get
{
if (ContextWrapper2.IsIis7IntegratedAppStart) return null;
HttpContext context = HttpContext.Current;
if (context == null) return null;
return context.Request;
}
}
}
And in app start:
protected void Application_Start(object sender, EventArgs e)
{
yourLibraryNamespace.ContextWrapper2.IsIis7IntegratedAppStart = true;
//...
yourLibraryNamespace.yourClass.Init();
//...
yourLibraryNamespace.ContextWrapper2.IsIis7IntegratedAppStart = false;
}
You could note this behaviour in your documentation and all should be well. AppStart-like context should be the only place where you get such an exception.
You could also implement IDisposable on a member and use it in appStart with the using statement so you do not forget to set IsIis7IntegratedAppStart = false.
I think I have the solution for you. I maintain a logging library and have the same issue as you. If it is a web request I am grabbing some data from the HttpContext. But depending on how the logging library is used this same scenario can happen. So here is my solution. The key fix for me was checking if the Handler was null or not.
if (System.Web.Hosting.HostingEnvironment.IsHosted
&& System.Web.HttpContext.Current != null
&& System.Web.HttpContext.Current.Handler != null
&& System.Web.HttpContext.Current.Request != null)
{
//access the Request object here
}
Depending on what you are trying to accomplish, you may be able to get some of the properties and settings around the web app from System.Web.Hosting.HostingEnvironment
I added a comment, but it gets auto-hidden.
I think it's more important to have an idea of what it is that you need from the request.
For instance, the link you provided which provides a workaround is looking for Request.ApplicationPath.
If that's actually what you're looking for (for, say, loading the web.config vs the app.config), you could do this:
if (HttpRuntime.AppDomainAppId != null)
return WebConfigurationManager.OpenWebConfiguration(HttpRuntime.AppDomainAppVirtualPath);
else
return ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
If this (or HttpRuntime.ApplicationPath) isn't what you're actually looking for, it would be helpful to know which properties of the Request you are actually looking for. Maybe there's a better, safer way to get there.

Categories

Resources