asp.net core different session id with ajax

Mike Feng 26 Reputation points
2023-03-11T05:34:08.79+00:00

There is a strange issue, the session has lost a value. Here is the steps to reproduce this issue:

  1. open vs2019, and choose "ASP.NET Core Web App(Model-View-Controller)" template.
  2. Leave the project name as default: WebApplication1.
  3. choose Target Framework: .NET 5.0, "None" Authentication Type.
  4. Use this code for Startup.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace WebApplication1
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();
            services.AddSession(options => { options.IdleTimeout = TimeSpan.FromMinutes(5); });
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }
            app.UseSession();
            app.UseStaticFiles();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
}

this code for Program.cs

    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureLogging((context, ILoggingBuilder) =>
                {
                    ILoggingBuilder.AddFilter("System", LogLevel.Warning);
                    ILoggingBuilder.AddFilter("Microsoft", LogLevel.Warning);
                    ILoggingBuilder.AddDebug();
                })
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }

this following code for Index.cshtml

@{
    ViewData["Title"] = "Home Page";
}

<div class="text-center">
    <h1 class="display-4">Welcome</h1>
    <p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>

    <button onclick="SubmitImage(0)">SubmitImage</button>
    <button onclick="GetNextImage(0)">GetNextImage 0</button>
    <button onclick="GetNextImage(1)">GetNextImage 1</button>
    <button onclick="GetImage()">GetNextImage 0 + 1</button>
    <button onclick="GetImage2()">GetNextImage2 0 + 1</button>
</div>

@section scripts {
    <script type="text/javascript">
        function GetNextImage(i) {
            $.ajax({
                url: "/Home/GetNextImage/" + i,
                type: "get",
                success: function (data) {
                    alert(i);
                },
                error: function (err) {
                }
            });
        }

        function SubmitImage(i) {
            $.post("/Home/Compare/" + i, function (e) {
                alert("success");
            }).fail(function (xhr, status, info) {
                alert(xhr.responseText);
            });
        }

        function GetImage() {
            GetNextImage(0); GetNextImage(1);
        }

        function GetImage2() {
            $.ajax({
                url: "/Home/GetNextImage/" + 0,
                type: "get",
                success: function (data) {
                    alert("0");
                    $.ajax({
                        url: "/Home/GetNextImage/" + 1,
                        type: "get",
                        success: function (data) {
                            alert("1");
                        },
                        error: function (err) {
                        }
                    });
                },
                error: function (err) {
                }
            });
        }

    </script>
}

At last, the following code is for HomeController.cs

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using WebApplication1.Models;

namespace WebApplication1.Controllers
{
    public class HomeController : Controller
    {
        private readonly ILogger<HomeController> _logger;

        public HomeController(ILogger<HomeController> logger)
        {
            _logger = logger;
        }

        public IActionResult Index()
        {
            Debug.WriteLine($"Session: {HttpContext.Session.Id}");
            return View();
        }

        public IActionResult Privacy()
        {
            return View();
        }

        [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
        public IActionResult Error()
        {
            return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
        }


        public IActionResult GetNextImage(int id)
        {
            string objName = "";
            if (id == 0)
            {
                objName = "ImageTagModelTop";  // top image
            }
            else
            {
                objName = "ImageTagModelBottom";  // bottom image
            }

            string model = HttpContext.Session.GetString(objName);
            if (model != null)
            {
            }

            string modelnew = DateTime.Now.ToString();

            HttpContext.Session.SetString(objName, modelnew);
            Debug.WriteLine($"Session: {HttpContext.Session.Id}   {objName}:{modelnew}");

            return Ok();
        }


        public IActionResult Compare(int id)
        {
            Debug.WriteLine($"Session: {HttpContext.Session.Id}");
            string top = HttpContext.Session.GetString("ImageTagModelTop");
            string bottom = HttpContext.Session.GetString("ImageTagModelBottom");

            if (top == null)
                return StatusCode(406, "top");
            if (bottom == null)
                return StatusCode(406, "bottom");

            return Ok();
        }
    }
}

Please remember to install Newtonsoft.Json Package.

Before run this example, please make sure: Jquery version is jQuery JavaScript Library v3.5.1 Now, lets run it: The following operations will lead to different scenarios:

Scenario A: first of all, restart debug

  1. click button: GetNextImage 0
  2. click button: GetNextImage 1
  3. click button: SubmitImage And then you can get "success" alert.

Scenario B: first of all, restart debug

  1. click button: GetNextImage 0 + 1
  2. click button: SubmitImage And then you can get "top" alert or "bottom" alert, that differs.

Scenario C: first of all, restart debug

  1. click button: GetNextImage2 0 + 1
  2. click button: SubmitImage And then you can get "success".

So can anyone told me why scenario B failed? Thank you very much, this stuck me several days. EDIT:2023/3/9 I test more on scenario B, I found they have the same request data. I copied them from browser run in F12 mode.

:authority: localhost:44309
:method: GET
:path: /Home/GetNextImage/0
:scheme: https
accept: */*
accept-encoding: gzip, deflate, br
accept-language: en-US,en;q=0.9
cookie: .AspNetCore.Antiforgery.o83foM2chuM=CfDJ8Gw-GSx5k5pPsftWf04glopQhp-i3KKqOKzLzD7eaIV5ibPACwXQwjXrzhOhWt4qNtw4wskW6thpjiH4Hozvgmtgcy5YzVxy5MDZcLkWEPdua0Ynhuc3xdQ7YeN1a1zFaK15RgKJyULgoWKWPeC1SFw; .AspNetCore.Cookies=CfDJ8Gw-GSx5k5pPsftWf04glop4NxL9N9R7OJWj2UMYlHyPbRhlgmucKlghUexJdNGQ48RBSSrVZUDwXRkBkUMkZ5zG0OgNoR4gjXeMyGpTzfyV9O2j1kDdkaxUizHwmoI_oStb28BQER95butXRTpvp5cfZn9gETdmyBl0nrNk79P7xf0UVhvZ37E5uQXWYWKRqFuwvEgV-dYDUQR0OYBhuUC8bNppXqJKScrXOsUP30Q8UTrOUxRBWZHR4Whuy9Rdqn41P9jBy9w3wYu-O3wZPmrc6dwextX3s9L960wyWaSI0veMATB-7ViHrmjkTVk4BJOXM7b3_qntFagB-w0vZSMGM4ODKJDsITBjsILBc8o7j-t9lhVid126dJJTOsu2X4Fje9dUAXizp_W2dLnnqZnWhlt2ClnA5SoBr6YqvENTNziF-t-aDDpf49HVyjC3jw; .AspNetCore.Session=CfDJ8Gw%2BGSx5k5pPsftWf04gloqOCxRn7WSvae%2FEeJMVA76P9wvwxd8XbXabx4B9%2FzRAvxcC%2F%2BgGGWwoPAFQrVbPeZk7KWiuzs1IjbgyyVNzMau%2BJ9sWHvcvK2UANq8lOldVqsHQ803oVCJImrhy8M33eRDpEIIWnpUbdX7Y57GXfmhx
referer: https://localhost:44309/Home/CompareTag
sec-ch-ua: "Chromium";v="110", "Not A(Brand";v="24", "Microsoft Edge";v="110"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
sec-fetch-dest: empty
sec-fetch-mode: cors
sec-fetch-site: same-origin
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.63
x-requested-with: XMLHttpRequest

and this is for the second one:

:authority: localhost:44309
:method: GET
:path: /Home/GetNextImage/1
:scheme: https
accept: */*
accept-encoding: gzip, deflate, br
accept-language: en-US,en;q=0.9
cookie: .AspNetCore.Antiforgery.o83foM2chuM=CfDJ8Gw-GSx5k5pPsftWf04glopQhp-i3KKqOKzLzD7eaIV5ibPACwXQwjXrzhOhWt4qNtw4wskW6thpjiH4Hozvgmtgcy5YzVxy5MDZcLkWEPdua0Ynhuc3xdQ7YeN1a1zFaK15RgKJyULgoWKWPeC1SFw; .AspNetCore.Cookies=CfDJ8Gw-GSx5k5pPsftWf04glop4NxL9N9R7OJWj2UMYlHyPbRhlgmucKlghUexJdNGQ48RBSSrVZUDwXRkBkUMkZ5zG0OgNoR4gjXeMyGpTzfyV9O2j1kDdkaxUizHwmoI_oStb28BQER95butXRTpvp5cfZn9gETdmyBl0nrNk79P7xf0UVhvZ37E5uQXWYWKRqFuwvEgV-dYDUQR0OYBhuUC8bNppXqJKScrXOsUP30Q8UTrOUxRBWZHR4Whuy9Rdqn41P9jBy9w3wYu-O3wZPmrc6dwextX3s9L960wyWaSI0veMATB-7ViHrmjkTVk4BJOXM7b3_qntFagB-w0vZSMGM4ODKJDsITBjsILBc8o7j-t9lhVid126dJJTOsu2X4Fje9dUAXizp_W2dLnnqZnWhlt2ClnA5SoBr6YqvENTNziF-t-aDDpf49HVyjC3jw; .AspNetCore.Session=CfDJ8Gw%2BGSx5k5pPsftWf04gloqOCxRn7WSvae%2FEeJMVA76P9wvwxd8XbXabx4B9%2FzRAvxcC%2F%2BgGGWwoPAFQrVbPeZk7KWiuzs1IjbgyyVNzMau%2BJ9sWHvcvK2UANq8lOldVqsHQ803oVCJImrhy8M33eRDpEIIWnpUbdX7Y57GXfmhx
referer: https://localhost:44309/Home/CompareTag
sec-ch-ua: "Chromium";v="110", "Not A(Brand";v="24", "Microsoft Edge";v="110"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
sec-fetch-dest: empty
sec-fetch-mode: cors
sec-fetch-site: same-origin
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.63
x-requested-with: XMLHttpRequest

Thank you.

Developer technologies ASP.NET ASP.NET Core
Developer technologies .NET Other
{count} votes

Accepted answer
  1. Anonymous
    2023-03-16T09:41:58.2233333+00:00

    Hi @Mike Feng

    As we all known, ASP.NET Core maintains session state by providing a cookie to the client that contains a session ID. The cookie session ID is sent to the app with each request, and it is used by the app to fetch the session data.

    Scenario B: first of all, restart debug

    1. click button: GetNextImage 0 + 1
    2. click button: SubmitImage And then you can get "top" alert or "bottom" alert, that differs.

    In this scenario, before clicking the button, you can use the F12 developer Application tools to check the session cookie, we can see it doesn't contain the cookie, so when click the "GetNextImage 0 + 1", the ajax request doesn't contain the cookie session ID, so in the controller, it will create session with different session ID. Then, it will cause that when click the SubmitImage button, the request only transfers one cookie session ID to the controller, so the compare result is not success.

    To solve this issue, you can try to change the Ajax method to synchronous:

            function GetNextImage(i) {
                $.ajax({
                    url: "/Home/GetNextImage/" + i,
                    type: "get",
                    async:false,
                    success: function (data) {
                        alert(i);
                    },
                    error: function (err) {
                    }
                });
            }
    

    Or you can use In-Memory Caching to store the data. Refer to the following code:

        public class HomeController : Controller
        {
            private readonly ILogger<HomeController> _logger;
            private IMemoryCache _cache;
            public HomeController(ILogger<HomeController> logger, IMemoryCache cache)
            {
                _logger = logger;
                _cache = cache;
            } 
    
            public IActionResult Index()
            {
                _logger.LogInformation($"Session: {HttpContext.Session.Id}");
                return View();
            } 
            public IActionResult GetNextImage(int id)
            {
                string objName = "";
                if (id == 0)
                {
                    objName = "ImageTagModelTop";  // top image
                }
                else
                {
                    objName = "ImageTagModelBottom";  // bottom image
                }
                string modelnew = DateTime.Now.ToString();
                var cacheEntryOptions = new MemoryCacheEntryOptions()
                     .SetSlidingExpiration(TimeSpan.FromSeconds(60))
                     .SetAbsoluteExpiration(TimeSpan.FromSeconds(3600))
                     .SetPriority(CacheItemPriority.Normal)
                     .SetSize(1024);
                _cache.Set(objName, modelnew, cacheEntryOptions);
                return Ok();
            }
    
            public IActionResult Compare(int id)
            {
                Debug.WriteLine($"Session: {HttpContext.Session.Id}");
                //string top = HttpContext.Session.GetString("ImageTagModelTop");
                //string bottom = HttpContext.Session.GetString("ImageTagModelBottom");
                _cache.TryGetValue("ImageTagModelTop", out string top);
                _cache.TryGetValue("ImageTagModelBottom", out string bottom);
                if (top == null)
                    return StatusCode(406, "top");
                if (bottom == null)
                    return StatusCode(406, "bottom");
    
                return Ok();
            }
    

    The result as below:

    image1


    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    Best regards,

    Dillion


2 additional answers

Sort by: Most helpful
  1. Jamesbuttler 5 Reputation points
    2023-03-17T17:56:18.38+00:00

    In ASP.NET Core, different session IDs may be generated when using AJAX requests due to the way that cookies are handled by the browser. By default, cookies are not sent with AJAX requests made from a different domain or subdomain than the original request.

    To ensure that the session ID is consistent and show promotion ideas across all requests, you can configure the ASP.NET Core application to use the same cookie for both the main request and AJAX requests. One way to achieve this is by setting the SameSite property of the cookie to "none" and the Secure property to "true" in the Startup.cs file:

    javascript Copy code services.Configure<CookiePolicyOptions>(options => { options.MinimumSameSitePolicy = SameSiteMode.None; options.CheckConsentNeeded = context => true; options.Secure = CookieSecurePolicy.Always; }); In addition, you may need to add a specific response header to allow cross-origin requests. You can do this by adding the following code to the Configure method in Startup.cs:

    python Copy code app.Use(async (context, next) => { context.Response.Headers.Add("Access-Control-Allow-Origin", "*"); await next.Invoke(); }); With these changes, your ASP.NET Core application should use the same session ID for both the main request and any AJAX requests.

    
    
    1 person found this answer helpful.
    0 comments No comments

  2. Bruce (SqlWork.com) 77,686 Reputation points Volunteer Moderator
    2023-03-11T16:32:45.3233333+00:00

    Unlike the former framework, asp.net core does not implement locking for session access. Because you perform 2 concurrent Ajax calls, they overwrite each others session update.

    it’s a bad practice to use session with Ajax, but if you do, you will need to implement a session locking mechanism, or be sure you never have 2 concurrent requests.


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.