I have a controller which returns SVG images.As I wanted to have good performances, I decide to use Caching.
From what I read on the web,once you set the last modified date with HttpContext.Response.Cache.SetLastModified(date)
you can request it from the browser using HttpContext.Request.Headers.Get("If-Modified-Since"). Compare the two dates. If they are equal it means that the image has not been modified, therefore you can return HttpStatusCodeResult(304, "Not Modified").
But something weird is happening, here is my code:
[OutputCache(Duration = 60, Location = OutputCacheLocation.Any, VaryByParam = "id")]
public ActionResult GetSVGResources(string id)
{
DateTime lastModifiedDate = Assembly.GetAssembly(typeof(Resources)).GetLinkerTime();
string rawIfModifiedSince = HttpContext.Request.Headers.Get("If-Modified-Since");
if (string.IsNullOrEmpty(rawIfModifiedSince))
{
// Set Last Modified time
HttpContext.Response.Cache.SetLastModified(lastModifiedDate);
}
else
{
DateTime ifModifiedSince = DateTime.Parse(rawIfModifiedSince);
if (DateTime.Compare(lastModifiedDate, ifModifiedSince) == 0)
{
// The requested file has not changed
return new HttpStatusCodeResult(304, "Not Modified");
}
}
if (!id.Equals("null"))
return new FileContentResult(Resources.getsvg(id), "image/svg+xml");
else
return null;
}
What is happening is the function
HttpContext.Response.Cache.SetLastModified(lastModifiedDate); does not set the "If-Modified-Since" return from the browser, In fact the the function HttpContext.Request.Headers.Get("If-Modified-Since") retuns exactly the time when the image is returned from the previous call return new FileContentResult(Resources.getsvg(id), "image/svg+xml");.
So my question is,
1 - What does the function HttpContext.Response.Cache.SetLastModified(lastModifiedDate) set exactly ?
2 - How can I (the server) set the "If-Modified-Since" return by the browser ?
It seems like you're muddling a bunch of related but nonetheless completely different concepts here.
OutputCache is a memory-based cache on the server. Caching something there means that while it still exists in memory and is not yet stale, the server can forgo processing the action and just returned the already rendered response from earlier. The client is not involved at all.
HTTP cache is an interaction between the server and the client. The server sends a Last-Modified response header, indicating to the client when the resource was last updated. The client sends a If-Modified-Since request header, to indicate to the server that its not necessary to send the resource as part of the response if it hasn't been modified. All this does is save a bit on bandwidth. The request is still made and a response is still received, but the actual data of the resource (like your SVG) doesn't have to be transmitted down the pipe.
Then, there's basic browser-based caching that works in concert with HTTP cache, but can function without it just as well. The browser simply saves a copy of every resource it downloads. If it still has that copy, it doesn't bother making a request to the server to fetch it again. In this scenario, a request may not even be made. However, the browser may also send a request with that If-Modified-Since header to see if the file it has is still "fresh". Then, if it doesn't get the file again from the server, it just uses its saved copy.
Either way, it's all on the client. A client could be configured to never cache, in which case it will always request resources, whether or not they've been modified, or it may be configured to always use a cache and never even bother to check it the resource has been updated or not.
There's also things like proxies that complicate things further still, as the proxy acts as the client and may choose to cache or not cache at will, before the web browser or other client of the end-user even gets a say in the matter.
What all that boils down to is that you can't set If-Modified-Since on the server and you can't control whether or not the client sends it in the request. When it comes to forms of caching that involve a client, you're at the whims of the client.
Related
We are using the [ResponseCacheAttribute] from Microsoft.AspNetCore.Mvc.Core with a policy like so on action methods or controllers:
[ResponseCache(CacheProfileName = "Default")]
In case a non 200 response is send like 400, 403 or 500 it is also being cached. So the first time we go to the server and get for example bad request. The second time no call is made to the server and the answer is still bad request (from disk cache).
I read in the documentation that when using response cache middleware only 200 responses are being cached. This attribute seems to be flawed and always adds the caching response header no matter what status code.
We like to define caching only on certain controllers or action methods. Not on all requests.
Does anyone know the solution for this?
I simulate the problem by using a status code result:
return StatusCode(500);
I would then expect it to always come back to this code with a breakpoint and never caching it.
I am trying to use HttpContext.Session in my ASP.NET Core Blazor Server application (as described in this MS Doc, I mean: all correctly set up in startup)
Here is the code part when I try to set a value:
var session = _contextAccessor.HttpContext?.Session;
if (session != null && session.IsAvailable)
{
session.Set(key, data);
await session.CommitAsync();
}
When this code called in Razor component's OnAfterRenderAsync the session.Set throws following exception:
The session cannot be established after the response has started.
I (probably) understand the message, but this renders the Session infrastructure pretty unusable: the application needs to access its state in every phase of the execution...
Question
Should I forget completely the DistributedSession infrastructure, and go for Cookies, or Browser SessionStorage? ...or is there a workaround here still utilizing HttpContext.Session? I would not want to just drop the distributed session infra for a way lower level implementation...
(just for the record: Browser's Session Storage is NOT across tabs, which is a pain)
Blazor is fundamentally incompatible with the concept of traditional server-side sessions, especially in the client-side or WebAssembly hosting model where there is no server-side to begin with. Even in the "server-side" hosting model, though, communication with the server is over websockets. There's only one initial request. Server-side sessions require a cookie which must be sent to the client when the session is established, which means the only point you could do that is on the first load. Afterwards, there's no further requests, and thus no opportunity to establish a session.
The docs give guidance on how to maintain state in a Blazor app. For the closest thing to traditional server-side sessions, you're looking at using the browser's sessionStorage.
Note: I know this answer is a little old, but I use sessions with WebSockets just fine, and I wanted to share my findings.
Answer
I think this Session.Set() error that you're describing is a bug, since Session.Get() works just fine even after the response has started, but Session.Set() doesn't. Regardless, the workaround (or "hack" if you will) includes making a throwaway call to Session.Set() to "prime" the session for future writing. Just find a line of code in your application where you KNOW the response hasn't sent, and insert a throwaway call to Session.Set() there. Then you will be able to make subsequent calls to Session.Set() with no error, including ones after the response has started, inside your OnInitializedAsync() method. You can check if the response is started by checking the property HttpContext.Response.HasStarted.
Try adding this app.Use() snippet into your Startup.cs Configure() method. Try to ensure the line is placed somewhere before app.UseRouting():
...
...
app.UseHttpsRedirection();
app.UseStaticFiles();
//begin Set() hack
app.Use(async delegate (HttpContext Context, Func<Task> Next)
{
//this throwaway session variable will "prime" the Set() method
//to allow it to be called after the response has started
var TempKey = Guid.NewGuid().ToString(); //create a random key
Context.Session.Set(TempKey, Array.Empty<byte>()); //set the throwaway session variable
Context.Session.Remove(TempKey); //remove the throwaway session variable
await Next(); //continue on with the request
});
//end Set() hack
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
...
...
Background Info
The info I can share here is not Blazor specific, but will help you pinpoint what's happening in your setup, as I've come across the same error myself. The error occurs when BOTH of the following criteria are met simultaneously:
Criteria 1. A request is sent to the server with no session cookie, or the included session cookie is invalid/expired.
Criteria 2. The request in Criteria 1 makes a call to Session.Set() after the response has started. In other words, if the property HttpContext.Response.HasStarted is true, and Session.Set() is called, the exception will be thrown.
Important: If Criteria 1 is not met, then calling Session.Set() after the response has started will NOT cause the error.
That is why the error only seems to happen upon first load of a page--it's because often in first loads, there is no session cookie that the server can use (or the one that was provided is invalid or too old), and the server has to spin up a new session data store (I don't know why it has to spin up a new one for Set(), that's why I say I think this is a bug). If the server has to spin up a new session data store, it does so upon the first call to Session.Set(), and new session data stores cannot be spun up after the response has started. On the other hand, if the session cookie provided was a valid one, then no new data store needs to be spun up, and thus you can call Session.Set() anytime you want, including after the response has started.
What you need to do, is make a preliminary call to Session.Set() before the response gets started, so that the session data store gets spun up, and then your call to Session.Set() won't cause the error.
SessionStorege has more space than cookies.
Syncing (two ways!) the sessionStorage is impossible correctly
I think you are thinking that if it is on the browser, how can you access that in C#? Please see some examples. It actually read from the browser and transfers (use) on the server side.
sessionstorage and localstorage in blazor are encrypted. We do not need to do extra for encryption. The same applies for serialization.
I have a server making a head request to a database dump I've created. The remote server does this to make sure that it's not using excessive bandwidth when not necessary.
However, due to some other circumstances outside my control this causes the script to be hit twice: once for the head request, and then another time to download the data.
What I'd like is to have the script I've written detect the head request, send back a couple of headers (e.g. last modified is right now, filesize different than before), and exit. Is there a way to do this?
Since this is still unanswered, I'll give it a try, even though I don't do much ASP.NET.
I'm only familiar with ASP.NET MVC 3, so here's some example code I got to work for responding to HEAD requests:
Function Index() As ActionResult
ControllerContext.HttpContext.Response.AddHeader("NewHeader", "Value")
Return View()
End Function
<ActionName("Index")>
<AcceptVerbs(HttpVerbs.Head)>
Function IndexHead() As ActionResult
Return Index()
End Function
I'm not sure if this still sends content or not, but I didn't see any content in Firefox (with Live HTTP Headers) or with a WebRequest. It could just be that those two ignore content; I wasn't able to confirm with with my packet sniffer, though.
Also, if you want more control over headers, you'll need IIS 7.0, as mentioned in this MSDN Article.
I have a Web server that updates its data once per minute, and want to make that data available to clients of all types. In order to reduce bandwidth, I set up the PHP script to support conditional GETs, using IF-MODIFIED-SINCE and/or IF-NONE-MATCH. The idea is that clients can poll every 30 seconds and thereby be sure that they won't miss anything, but also won't get duplicate data.
That all works great for most types of clients, and I've verified that it works with clients that support the standard HTTP conditional GET semantics.
But it doesn't work with JavaScript because JSONP inserts a <script> tag into the DOM and lets the browser handle things--and there's no support (at least, none that I know of) for conditional GETs in <script> tags.
So I modified my PHP script to support passing an etag value. The returned data contains an etag value that's unique for that minute. When the JavaScript client receives data from the server, it saves the etag value so it can use that value in subsequent requests. The request takes the form:
http://api.mydomain.com/script.php?fmt=json&callback=jscallback&etag=ab79bc65e
If the etag of the data doesn't match the passed etag, then I send the new data.
This all works well and was surprisingly easy to code up using jQuery. My dilemma, though is what to do if the etag matches. I see two choices:
Return an HTTP 304 (Not Modified)
Return an HTTP 200 (OK), but with the returned data containing just the header information (modified date, etag, etc.) and no actual data items.
If I do the first, then the JavaScript client code is greatly simplified. The browser seems to work just fine if it gets a 304 response to an injected <script> tag. But ... something bothers me about this solution. I don't know what it is, but it seems like I'm depending on behavior that could be browser-specific. Some browser might decide to report an error if it gets a 304.
Doing the second would require a little bit more work on the server, slightly more bandwidth, and would require the clients to check the data to see if the data was updated. It's more work for everybody, but it seems cleaner.
So, to my question. If you were writing a JavaScript client to get this data, which would you prefer? A silent failure that never calls your "success" callback? Or a "success" return that has no data (beyond status) in it? A third option?
Absent any discussion from others, I went with my gut here and implemented the second option. The web server returns an HTTP 200, and the data contains a "Not Modified" status code along with header information, but no records. That makes the JavaScript just slightly more complicated, but prevents me from depending on undocumented behavior.
I'm implementing a custom web server of a kind. And am looking into adding an Expires header support. However, I'm a little unsure of how exactly to implement it.
If multiple cold-cache requests are being made to the same unchanged resource on the server and the server returned different Expires header (say it uses relative time to calculate the exact value of the Expires date e.g. +6 hours from the request time), does that invalidate the cache on all the proxy servers in-between as well? Or is it impossible to happen (per the spec)?
Does the Expires HTTP header needs to be consistent across multiple cold-cache requests?
Ok, never mind, found the relevant information under the Cache Revalidation and Reload Controls section of the HTTP Spec
Basically, you can serve all the different validators you want but you must be aware that in such case proxies may have a set of different validators from their own cache and from various user agents communicating with the proxy. They may choose to send one to you and that might not be the correct or the most optimal one for the end-users. However, a "best approach" has been suggested in the spec.
I suppose this should covers Expires headers as well as ETags, Cache-Control and whatnot.
Here's the relevant excerpt, in case anyone's interested:
When an intermediate cache is forced,
by means of a max-age=0 directive, to
revalidate its own cache entry, and
the client has supplied its own
validator in the request, the supplied
validator might differ from the
validator currently stored with the
cache entry. In this case, the cache
MAY use either validator in making its
own request without affecting semantic
transparency. However, the choice of
validator might affect performance.
The best approach is for the
intermediate cache to use its own
validator when making its request. If
the server replies with 304 (Not
Modified), then the cache can return
its now validated copy to the client
with a 200 (OK) response. If the
server replies with a new entity and
cache validator, however, the
intermediate cache can compare the
returned validator with the one
provided in the client's request,
using the strong comparison function.
If the client's validator is equal to
the origin server's, then the
intermediate cache simply returns 304
(Not Modified). Otherwise, it returns
the new entity with a 200 (OK)
response. If a request includes the
no-cache directive, it SHOULD NOT
include min-fresh, max-stale, or
max-age.