Base solution for your next web application
Open Closed

Migration to new Azure Server with Gateway #11868


User avatar
0
ESTeam created

Hi,

We have an application created with ASP.NET Zero that always worked correctly in the various environments (DEV, UAT and PROD), however when we migrated the application to a new Azure server (with Gateway) some things stopped working - Microsoft told us that we could not use "." in the names of cookies and request headers (https://learn.microsoft.com/en-us/azure/application-gateway/application-gateway-faq#why-are-some-header-values-not-present-when-requests-are-forwarded-to-my-application) so we had to make these changes - do you know if the errors we are having could be related to this change?

Problems encountered so far:

DEV - We were unable to modify the application language

UAT - Error: loging in with a valid user (if the user and password are not valid we get the normal and expected error) - The DEV and UAT environments have the same code version and the Database is also the same (only the data changes) so I can't understand why the error only occurs in the UAT environment: ERROR 2024-02-08 14:30:13,059 [64 ] Mvc.ExceptionHandling.AbpExceptionFilter - An error occurred while updating the entries. See the inner exception for details. Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while updating the entries. See the inner exception for details. ---> Microsoft.Data.SqlClient.SqlException (0x80131904): Cannot insert the value NULL into column 'Id', table 'UatCufHealthPlan.dbo.AbpUserTokens'; column does not allow nulls. INSERT fails. The statement has been terminated. at Microsoft.Data.SqlClient.SqlCommand.<>c.<ExecuteDbDataReaderAsync>b__164_0(Task1 result) at System.Threading.Tasks.ContinuationResultTaskFromResultTask2.InnerInvoke() at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) --- End of stack trace from previous location where exception was thrown --- at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread) --- End of stack trace from previous location where exception was thrown --- at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken) ClientConnectionId:693fae65-22e2-4af2-a45e-6b9893c4a2a9 Error Number:515,State:2,Class:16 ClientConnectionId before routing:d4e11474-fd84-45d8-b437-096e351f398c Routing Destination:ffc20ecd812f.tr38522.westeurope1-a.worker.database.windows.net,11014 --- End of inner exception stack trace --- at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(IList1 entriesToSave, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(DbContext _, Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func4 operation, Func4 verifySucceeded, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken) at Abp.EntityFrameworkCore.AbpDbContext.SaveChangesAsync(CancellationToken cancellationToken) at Abp.Zero.EntityFrameworkCore.AbpZeroCommonDbContext3.SaveChangesAsync(CancellationToken cancellationToken) at Abp.EntityFrameworkCore.Uow.EfCoreUnitOfWork.SaveChangesInDbContextAsync(DbContext dbContext) at Abp.EntityFrameworkCore.Uow.EfCoreUnitOfWork.SaveChangesAsync() at Abp.EntityFrameworkCore.Uow.EfCoreUnitOfWork.CompleteUowAsync() at Abp.Domain.Uow.UnitOfWorkBase.CompleteAsync() at Abp.AspNetCore.Mvc.Uow.AbpUowActionFilter.OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextExceptionFilterAsync>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)

best regards, Dirceu Candeias


20 Answer(s)
  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi,

    Cannot insert the value NULL into column 'Id', table 'UatCufHealthPlan.dbo.AbpUserTokens'; column does not allow nulls.

    This last error seems unrelated to using "." in cookies and request headers. The Id field must be auto-increment so it must be set by the database provider. Are there any changes on the database as well ?

    For the issue using "." in cookies and request headers, I remember this problem from using NGINX. NGINX allows this by a custom configuration. Are you using NGINX ?

  • User Avatar
    0
    ESTeam created

    Hi,

    Regarding the cookies microsoft are checking if any exception can be made on AppGw accepted characters.

    I have now verified that the database error occurs because when the tables were migrated, the primary keys were left without the identity.

    Thanks, Dirceu

  • User Avatar
    0
    ESTeam created

    Hi,

    Cannot insert the value NULL into column 'Id', table 'UatCufHealthPlan.dbo.AbpUserTokens'; column does not allow nulls.

    This last error seems unrelated to using "." in cookies and request headers. The Id field must be auto-increment so it must be set by the database provider. Are there any changes on the database as well ?

    For the issue using "." in cookies and request headers, I remember this problem from using NGINX. NGINX allows this by a custom configuration. Are you using NGINX ?

    I have an response from microsoft - is there anything we can change in the code? (Previously I modified the ".AspNetCore.Culture" header to "AspNetCoreCulture" but I had to revert because I was unable to modify the language in the local environment, in the Azure environment it never worked).

    *AppGw V2 uses NGINX. As I previously mentioned we state in our docs that dots are not supported in the request header. A work around is to play with the re-write rule until you get the desired outcome or have the app in the backend support the header without the dots.

    Also, periods in header names seem to be in contradiction with RFC7230 https://learn.microsoft.com/en-us/azure/application-gateway/application-gateway-faq#why-are-some-header-values-not-present-when-requests-are-forwarded-to-my-application

    RFC: RFC 7230 - Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing (ietf.org)

    Please let me know if you need any further assistance.*

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi @ESTeam

    I'm glad that the DB error is fixed. When I look at the Microsoft's answer, it seems like you must change the request header names. You can change the Abp.TenantId used in AspNet Zero as shown below;

    • For client side, set abp.multiTenancy.tenantIdCookieName.
    • For server side, set Configuration.MultiTenancy.TenantIdResolveKey

    For Cookies generated by ASP.NET Core, you can check its documentation.

    If you can share how did you change AspNetCoreCulture, we can investigate why it didn't work.

  • User Avatar
    0
    ESTeam created

    Hi @ESTeam

    I'm glad that the DB error is fixed. When I look at the Microsoft's answer, it seems like you must change the request header names. You can change the Abp.TenantId used in AspNet Zero as shown below;

    • For client side, set abp.multiTenancy.tenantIdCookieName.
    • For server side, set Configuration.MultiTenancy.TenantIdResolveKey

    For Cookies generated by ASP.NET Core, you can check its documentation.

    If you can share how did you change AspNetCoreCulture, we can investigate why it didn't work.

    Hi,

    I changed the Abp.TenantId header as indicated and it worked, but when I change the ".AspNetCore.Culture" header to "AspNetCoreCulture" I can no longer modify the language in my local environment. I just modified the method getRequetHeadersWithDefaultValues on the client side: ![Capture.JPG] Note: I had previously removed the dots from all cookie names.

    best regards, Dirceu

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi,

    Did you also change it on the server side ? If not, you can try this https://stackoverflow.com/a/49268627

  • User Avatar
    0
    ESTeam created

    Hi,

    Did you also change it on the server side ? If not, you can try this https://stackoverflow.com/a/49268627

    Hi,

    I made this change on the server and client side and it didn't work, after the change I can't change the language in the local environment.

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi @ESTeam

    Thanks, we will try this and get back to you soon.

  • User Avatar
    0
    ESTeam created

    Hi @ESTeam

    Thanks, we will try this and get back to you soon.

    Hi,

    Do you have any news? - we are unable to complete the migration due to this problem/limitation in Azure/Gateway/NGINX - the solution would be to remove the dots from the name of the request header (but the solution you suggested did not work). Note: We are using Angular 9 and .NET Core 3.1 - ASP.NET ZERO version 8.7.0

    best regards, Dirceu

  • User Avatar
    0
    ESTeam created

    Hi again,

    Sorry to insist, but do you have an expected date for presenting a solution (to change the request header from ".AspNetCore.Culture" to "AspNetCoreCulture")?

    best regards, Dirceu

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi,

    Sorry for the delay. It seems like there was a few more place to change the default culture name. You can follow steps below to do this.

    1. Create a custom RequestCultureProvider as shown below;
    public class MyCookieRequestCultureProvider: CookieRequestCultureProvider
    {
        public override async Task<ProviderCultureResult> DetermineProviderCultureResult(HttpContext httpContext)
        {
            CookieName = "AspNetCoreCulture";
            return await base.DetermineProviderCultureResult(httpContext);
        }
    }
    
    1. Then, use it like this in your Startup.cs file;
    app.UseAbpRequestLocalization(opts =>
    {
    	opts.RequestCultureProviders.Insert(0, new MyCookieRequestCultureProvider());
    });
    

    These two steps allow you to read the cookie with name AspNetCoreCulture. You also need to change the name when the cookie is written. Follow steps below to do this.

    1. Create the Controller below in your Web.Mvc project;
    public class MyAbpLocalizationController : AbpController
    {
        protected Abp.Web.Http.IUrlHelper UrlHelper;
        private readonly ISettingStore _settingStore;
    
        private readonly ITypedCache<string, Dictionary<string, SettingInfo>> _userSettingCache;
    
        public MyAbpLocalizationController(
            Abp.Web.Http.IUrlHelper urlHelper,
            ISettingStore settingStore,
            ICacheManager cacheManager)
        {
            UrlHelper = urlHelper;
            _settingStore = settingStore;
            _userSettingCache = cacheManager.GetUserSettingsCache();
        }
    
        [DisableAuditing]
        public virtual ActionResult ChangeCulture(string cultureName, string returnUrl = "")
        {
            var cookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(cultureName, cultureName));
    
            Response.Cookies.Append(
                "AspNetCoreCulture",
                cookieValue,
                new CookieOptions
                {
                    Expires = Clock.Now.AddYears(2),
                    HttpOnly = true
                }
            );
    
            if (AbpSession.UserId.HasValue)
            {
                ChangeCultureForUser(cultureName);
            }
    
            if (Request.IsAjaxRequest())
            {
                return Json(new AjaxResponse());
            }
    
            if (!string.IsNullOrWhiteSpace(returnUrl))
            {
                var escapedReturnUrl = Uri.EscapeDataString(returnUrl);
                var localPath = UrlHelper.LocalPathAndQuery(escapedReturnUrl, Request.Host.Host, Request.Host.Port);
                if (!string.IsNullOrWhiteSpace(localPath))
                {
                    var unescapedLocalPath = Uri.UnescapeDataString(localPath);
                    if (Url.IsLocalUrl(unescapedLocalPath))
                    {
                        return LocalRedirect(unescapedLocalPath);
                    }
                }
            }
    
            return LocalRedirect("/");
        }
    
        protected virtual void ChangeCultureForUser(string cultureName)
        {
            var languageSetting = _settingStore.GetSettingOrNull(
                AbpSession.TenantId,
                AbpSession.GetUserId(),
                LocalizationSettingNames.DefaultLanguage
            );
    
            if (languageSetting == null)
            {
                _settingStore.Create(new SettingInfo(
                    AbpSession.TenantId,
                    AbpSession.UserId,
                    LocalizationSettingNames.DefaultLanguage,
                    cultureName
                ));
            }
            else
            {
                _settingStore.Update(new SettingInfo(
                    AbpSession.TenantId,
                    AbpSession.UserId,
                    LocalizationSettingNames.DefaultLanguage,
                    cultureName
                ));
            }
    
            _userSettingCache.Remove(AbpSession.ToUserIdentifier().ToString());
        }
    }
    
    1. And change the places in your MVC project where @Url.Action("ChangeCulture", "AbpLocalization") is called. You need to use @Url.Action("ChangeCulture", "MyAbpLocalization"...
  • User Avatar
    0
    ESTeam created

    Hi,

    Sorry for the delay. It seems like there was a few more place to change the default culture name. You can follow steps below to do this.

    1. Create a custom RequestCultureProvider as shown below;
    public class MyCookieRequestCultureProvider: CookieRequestCultureProvider 
    { 
        public override async Task<ProviderCultureResult> DetermineProviderCultureResult(HttpContext httpContext) 
        { 
            CookieName = "AspNetCoreCulture"; 
            return await base.DetermineProviderCultureResult(httpContext); 
        } 
    } 
    
    1. Then, use it like this in your Startup.cs file;
    app.UseAbpRequestLocalization(opts => 
    { 
      opts.RequestCultureProviders.Insert(0, new MyCookieRequestCultureProvider()); 
    }); 
    

    These two steps allow you to read the cookie with name AspNetCoreCulture. You also need to change the name when the cookie is written. Follow steps below to do this.

    1. Create the Controller below in your Web.Mvc project;
    public class MyAbpLocalizationController : AbpController 
    { 
        protected Abp.Web.Http.IUrlHelper UrlHelper; 
        private readonly ISettingStore _settingStore; 
     
        private readonly ITypedCache<string, Dictionary<string, SettingInfo>> _userSettingCache; 
     
        public MyAbpLocalizationController( 
            Abp.Web.Http.IUrlHelper urlHelper, 
            ISettingStore settingStore, 
            ICacheManager cacheManager) 
        { 
            UrlHelper = urlHelper; 
            _settingStore = settingStore; 
            _userSettingCache = cacheManager.GetUserSettingsCache(); 
        } 
     
        [DisableAuditing] 
        public virtual ActionResult ChangeCulture(string cultureName, string returnUrl = "") 
        { 
            var cookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(cultureName, cultureName)); 
     
            Response.Cookies.Append( 
                "AspNetCoreCulture", 
                cookieValue, 
                new CookieOptions 
                { 
                    Expires = Clock.Now.AddYears(2), 
                    HttpOnly = true 
                } 
            ); 
     
            if (AbpSession.UserId.HasValue) 
            { 
                ChangeCultureForUser(cultureName); 
            } 
     
            if (Request.IsAjaxRequest()) 
            { 
                return Json(new AjaxResponse()); 
            } 
     
            if (!string.IsNullOrWhiteSpace(returnUrl)) 
            { 
                var escapedReturnUrl = Uri.EscapeDataString(returnUrl); 
                var localPath = UrlHelper.LocalPathAndQuery(escapedReturnUrl, Request.Host.Host, Request.Host.Port); 
                if (!string.IsNullOrWhiteSpace(localPath)) 
                { 
                    var unescapedLocalPath = Uri.UnescapeDataString(localPath); 
                    if (Url.IsLocalUrl(unescapedLocalPath)) 
                    { 
                        return LocalRedirect(unescapedLocalPath); 
                    } 
                } 
            } 
     
            return LocalRedirect("/"); 
        } 
     
        protected virtual void ChangeCultureForUser(string cultureName) 
        { 
            var languageSetting = _settingStore.GetSettingOrNull( 
                AbpSession.TenantId, 
                AbpSession.GetUserId(), 
                LocalizationSettingNames.DefaultLanguage 
            ); 
     
            if (languageSetting == null) 
            { 
                _settingStore.Create(new SettingInfo( 
                    AbpSession.TenantId, 
                    AbpSession.UserId, 
                    LocalizationSettingNames.DefaultLanguage, 
                    cultureName 
                )); 
            } 
            else 
            { 
                _settingStore.Update(new SettingInfo( 
                    AbpSession.TenantId, 
                    AbpSession.UserId, 
                    LocalizationSettingNames.DefaultLanguage, 
                    cultureName 
                )); 
            } 
     
            _userSettingCache.Remove(AbpSession.ToUserIdentifier().ToString()); 
        } 
    } 
    
    1. And change the places in your MVC project where @Url.Action("ChangeCulture", "AbpLocalization") is called. You need to use @Url.Action("ChangeCulture", "MyAbpLocalization"...

    Hi,

    in the client side we use Angular 9 (not .NET MVC), so we don't have any reference to ChangeCulture (both on the client and server side):

    best regards, Dirceu

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi,

    In that case, you can change the cookie name in language-switch.component.ts

  • User Avatar
    0
    ESTeam created

    Hi,

    In that case, you can change the cookie name in language-switch.component.ts

    Hi,

    I tried one of 2 things and neither of them worked on the login page (after doing the login both solutions worked):

    1. I changed the name of the cookie on the server side from “.AspNetCore.Culture” to “AspNetCoreCulture”, as you indicated. On the client side, I set the cookie in language-switch.component.ts component (and also changed the name in file AppPreBootstrap.ts):
    2. I changed the name of the cookie on the server side from “.AspNetCore.Culture” to “AbpLocalizationCultureName” (this is another cookie used by ASP.NET Zero that was previously modified (because of the "." in the cookie name that had to be removed) from Abp.Localization.CultureName to AbpLocalizationCultureName). On the client side, I changed the name in file AppPreBootstrap.ts

    I also tried changing the cookie whenever I send the language in the request header but it didn't work on the login page either:

    The 2 cookies are with the "en" culture but the site remains in "pt-PT":

    The request header is also in "en":

    best regards, Dirceu

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi @ESTeam

    Just to be sure, you only have this problem for the not loggedin users, is that correct ?

  • User Avatar
    0
    ESTeam created

    Hi @ESTeam

    Just to be sure, you only have this problem for the not loggedin users, is that correct ?

    Hi,

    I've tested again now and after the login it only works if i open a new private window in the browser (Edge) - In chrome never works (even if you open a new incognito window). Before login never works.

  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi,

    Thanks. Is it possible to access your solution somehow ? It would be nice for us to test this using your project.

  • User Avatar
    0
    ESTeam created

    Hi,

    Thanks. Is it possible to access your solution somehow ? It would be nice for us to test this using your project.

    The source code? i've been testing localy We are currently changing the Azure environment to use Azure Front Door instead of Application Gateway. After testing (If the tests run successfully), it may no longer be necessary to modify the name of the request header ".AspNetCore.Culture" - We can wait and see if it works.

  • User Avatar
    0
    ESTeam created

    Hi,

    We already change to Azure Front Door and now we can change the language but when we call the method https://plano-mais-cuf-api-dev.sistemasaude.pt/AbpUserConfiguration/GetAll?d=1709222850822 is not returning the tenant id so regardless of the address it is always redirecting to the host: Host = https://host-dev.sistemasaude.pt Tenant 1 (default) - https://default-dev.sistemasaude.pt Tenant 2 (plano-mais-cuf) - https://plano-mais-cuf-dev.sistemasaude.pt

    in the development environment (with azure front door):

    in the UAT environment (with Gateway) - https://plano-mais-cuf-api-uat.sistemasaude.pt/AbpUserConfiguration/GetAll?d=1709223616322:

    Do you have any tips on what could be causing this as this problem did not occur when using the Gateway? Both environments are equal in terms of code deployed and databases are also equal.

    best regards, Dirceu

  • User Avatar
    0
    ESTeam created

    Hi,

    the problem (the last one reported in Azure Front Door) is resolved, the solution is here (#6 Overwrite ‘Host’ server variable on Windows IIS Web Apps, with applicationHost.xdt and Web.config): https://www.patrickob.com/2022/04/06/azure-front-door-afd-app-gateway-app-service-unexpected-issues/

    Therefore, we no longer need to modify the request header.

    thank you, Dirceu