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.
Related
My ASP.NET Core 3.0 in a particular configuration/deployment logs:
[INF] CORS policy execution failed.
[INF] Request origin https://bla.com does not have permission to access the resource.
How can I log at that point the resource that was requested for debugging ?
(note this question is not about the actual issue or solving it)
(note that I am not after globally increasing the log level etc)
Well, that middleware is locked down pretty badly, and I haven't found any sensible way to hook into it.
If you want to replace the CorsMiddleware, you can't just create a decorator that calls Invoke() on the middleware, because you'll have no idea what happened.
Another solution might be to replace the CorsService:ICorsService registration in the service collection with a decorator, and then check the CorsResult after delegating the call to EvaluatePolicy(). That way you could emit an additional log message close to where the original message is emitted.
But there is another possible solution, both very simple and very crude: To check what happened in the request. Albeit that is a bit farther away from the original logged message.
The code below is a delegate added to the pipeline (in Startup/Configure, before .UseCors()) that checks if the request was a preflight request (the same way CorsService does), and if it was successful, i.e. the AccessControlAllowOrigin header is present. If it wasn't successful, it logs a message with the same EventId and source as the CorsService.
app.Use(async (ctx, next) =>
{
await next();
var wasPreflightRequest = HttpMethods.IsOptions(ctx.Request.Method)
&& ctx.Request.Headers.ContainsKey(CorsConstants.AccessControlRequestMethod);
var isCorsHeaderReturned = ctx.Response.Headers.ContainsKey(HeaderNames.AccessControlAllowOrigin);
if (wasPreflightRequest && !isCorsHeaderReturned)
{
ctx.RequestServices.GetRequiredService<ILoggerFactory>()
.CreateLogger<CorsService>()
.LogInformation(new EventId(5, "PolicyFailure"),
$"CORS preflight failed at resource: {ctx.Request.Path}.");
}
});
Based on my testing it seems to work. ¯\_(ツ)_/¯
It might not be what you were looking for, but who knows, maybe it will be useful for someone.
(Obviously a good way to deal with these things is to use a structured logging solution, like Serilog, and add enrichers to capture additional request information, or add stuff manually to a diagnostic context. But setting that up is quite a bit more involved.)
We are trying to move from server session (IIS) to Redis-backed session. I updated my web.config with the custom sessionState configuration. I'm finding that only SOME of my key/value pairs are being saved. Of the 5 I expect to be in there, there are only 2. I verified all my code is ultimately hitting HttpContext.Current.Session.Add. I verified that my POCOs are marked as serializable. Looking at monitoring, I see that it adds the first two pairs, then everything after that just doesn't make it. No hit, no rejection, no exceptions. Nothing.
Anyone ever see this? Know where I could start to look to resolve?
TIA,
Matt
Update 1: I've switched to using a JSON serializer to store my data. Same thing. Doesn't seem to be a serialization issue.
Update 2: I've now downloaded the source code, compiled and am debugging it. The method SetAndReleaseItemExclusive, which seems to send the session items to Redis, is only hit once, though it should be hit more than once as my web site handle SSO and bounces from page to page to load the user and such. Have to investigate why it's only firing once...
Figured it out. Turns out that my AJAX request to an "API" endpoint without my MVC app did not have the appropriate session state attached. Therefore, the SetAndReleaseItemExclusive was never called. Adding this fixed it:
protected void Application_PostAuthorizeRequest()
{
if (HttpContext.Current.Request.Url.LocalPath == "/api/user/load")
HttpContext.Current.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.Required);
else
HttpContext.Current.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.ReadOnly);
}
I have a lambda function that uses S3.listObjects to return a directory listing. The listing is sometimes (not always!) out of date - it doesn't contain recently uploaded objects and has old modification dates for the objects that it does have.
When I run the identical code locally it always works fine.
Clearly some sort of caching but I don't understand where...
Here's the relevant code:
function listFiles() {
return new Promise(function (resolve, reject) {
const params = {
Bucket: "XXXXX",
Prefix: "YYYYY"
};
s3.listObjects(params, function (err, data) {
if (err) reject(err);
else resolve(data.Contents);
});
})
}
That is due to Amazon S3 Data Consistency Model. S3 provides read-after-write consistency for PUTs, however other requests - including listObjects are eventually consistent which means there could be a delay in propagation.
The read-after-write consistency in practice settles in a matter of seconds. It's not a guarantee, however. It's unlikely, but not impossible that amazon returns stale data minutes later, esp if across zones. It's more likely however that your client is caching a previous response for that same URL.
You might have run into a side effect of your lambda container being reused. This is explained at a high-level here. One consequence of container reuse is that background processes, temporary files, and global variable modifications are still around when your lambda is re-invoked. Another article talking about how to guard for it.
If you are sending your logs to cloudwatch logs, you can confirm that a container is being reused if the logs for a lambda seem to be appended to the end of a previous log stream, instead of creating a new log stream.
When your lambda container gets reused, the global variables outside your handler function will be reused. For instance, if you change the loglevel of your logging calls to DEBUG at the end of your handler, if your container gets reused, it will start at the top of the handler in the same loglevel.
If you're using the default s3 client session (it seems like you are), then this connection stays in a global (singleton). If your s3 client connection is reused, it might pull the cached results of calls prior, and I would expect that connection to be reused in a later invocation.
One way to avoid this is to specify the If-None-Match request header. If the ETag of the object you're accessing doesn't match on the remote end, you'll get fresh data. You may set it to the last Etag you got (which you'd store in a global), or alternatively you may try setting a completely random value -- which should act as a cache buster. It doesn't look like list_objects() accepts an If-None-Match header, however. You may try to create a new client session just for the current invocation.
This article on recursive lambdas discusses the issue.
I wrote the following MVC Controller to test cancellation functionality:
class MyController : Controller
{
[HttpGet("api/CancelTest")]
async Task<IActionResult> Get()
{
await Task.Delay(1000);
CancellationToken token = HttpContext.RequestAborted;
bool cancelled = token.IsCancellationRequested;
logger.LogDebug(cancelled.ToString());
return Ok();
}
}
Say, I want to cancel the request, so the value 'true' is logged in the controller action above. This is possible server-side if the server implements the IHttpRequestLifetimeFeature. Luckily Kestrel does, and this can be accomplished the following way:
var feature = (IHttpRequestLifetimeFeature) HttpContext.Features[typeof(IHttpRequestLifetimeFeature)];
feature.Abort();
The problem however is that I want to cancel the request on the client side. For example, in the browser. In pre-core versions of ASP.NET MVC/WebApi the cancellation token would automatically be cancelled if the browser aborted a request. Example: refresh the page a couple of times in Chrome. In the Network tab of the chrome dev tools you can now see the previous (unfinished) request be cancelled.
The thing is: in ASP.NET Core running on Kestrel, I can only see the following entry in the log:
Microsoft.AspNetCore.Server.Kestrel.Internal.Networking.UvException:
Error -4081 ECANCELED operation canceled
So the abort request from the browser DOES arrive and is handled by the Kestrel webserver. It does however not affect the RequestAborted property of the HttpContext in the controller, because the value 'false' is still logged by the method.
Question:
Is there a way to abort/cancel my controller's method, so that the HttpContext.RequestAborted property will be marked as cancelled?
Perhaps I can make something that would subscribe to Kestrel's operation cancelled trigger and call the IHttpRequestLifetimeFeature.Abort() method?
Update:
I did some further testing and it seems the HttpRequest IS in fact aborted, but there seems to be some kind of delay before the cancellation actually takes place. The delay is not time-factored, and seems to come straight from libuv (the library where the Kestrel webserver is build on top of). I posted more info on https://github.com/aspnet/KestrelHttpServer/issues/1103
More updates:
Issue has been moved to another one, because the previous one contained multiple problems. https://github.com/aspnet/KestrelHttpServer/issues/1139
Turns out that that simply using HttpContext.RequestAborted is indeed the right way, but due to a bug in Kestrel (the order in which FIN/RST packages were handled), the request was not aborted on a browser abort.
The bug should finally be fixed in Kestrel 2.0.
See the updates in my question for more information.
I do asynchronous requests in LoadState method of a certain Page. I use HttpClient to make a request and I expect the splashscreen to go away while I await the result.
If I am not connected to any networks, the splashscreen immediately goes away and I get a blank page because the request obviously didn't happen.
But if I am connected to a network but have connectivity issues (for example, I set a wrong IP address) it seems to start a request and just block.
My expectation was that the HttpClient would realize that it cannot send a request and either throw an exception or just return something.
I managed to solve the issue of blocking by setting a timeout of around 800 milliseconds, but now it doesn't work properly when the Internet connection is ok. Is this the best solution, should I be setting the timeout at all? What is the timeout that's appropriate which would enable me to differentiate between an indefinitely blocking call and a proper call that's just on a slower network?
I could perhaps check for Internet connectivity before each request, but that sounds like an unpredictable solution...
EDIT: Now, it's really interesting. I have tried again, and it blocks at this point:
var rd = await httpClient.SendAsync(requestMsg);
If I use Task.Run() as suggested in the comments and get a new Thread, then it's always fine.
BUT it's also fine without Task.Run() if there is no Internet access but the network access is not "Limited" (it says that the IPv4 connectivity is "Internet access" although I cannot open a single website in a browser and no data is returned from the web service. It just throws System.Net.Http.HttpRequestException which was something I was expecting in the first place) Only blocks when the network connection is Limited.
What if instead of setting a timeout, you checked the connection status using
public static bool IsConnected
{
get
{
return NetworkInformation.GetInternetConnectionProfile() != null;
}
}
This way if IsConnected, then you make the call; otherwise, ignore it.
I'm not sure if you are running this in App.xaml.cs? I've found requests made in that class can be fickle and it may be best to move the functionality to an extended splash screen to ensure the application makes it all the way through the activation process.
http://msdn.microsoft.com/en-us/library/windows/apps/xaml/Hh868191(v=win.10).aspx