Ticket T1046565
Visible to All Users

Blazor - Alternatives to XAF's ValueManager or Best Practices to store and access custom values in ASP.NET Core

created 3 years ago (modified 3 years ago)

Hello,

In my scenario, i need to defined dynamically the connection string based on the user.
In order to do that i have a CustomLogonController. Where in insert the CS in a the Value Manager.
This seems to work.

C#
public class CustomLogonParametersCtrl : ObjectViewController<DetailView, AuthenticationStandardLogonParameters> { public CustomLogonParametersCtrl() : base() { // Target required Views (use the TargetXXX properties) and create their Actions. } protected override void OnActivated() { base.OnActivated(); LogonController controller = Frame.GetController<LogonController>(); if(controller != null) { controller.Actions["Logon"].Executing += CustomLogonParametersCtrl_Executing; } } private void CustomLogonParametersCtrl_Executing(object sender, System.ComponentModel.CancelEventArgs e) { BlazorApplication app = ((BlazorApplication)Application); IValueManagerStorageContext valueManagerStorageContext = app.ServiceProvider.GetRequiredService<IValueManagerStorageContext>(); valueManagerStorageContext.RunWithStorage(() => { ValueManager.GetValueManager<string>("ApplicationCS").Value = "Integrated Security=SSPI;Pooling=false;Data Source=(localdb)\\mssqllocaldb;Initial Catalog=XAfTest0034"; }); } }

Then on the Blazor application in the OnLoggingOn, I get the CS.

C#
protected override void OnLoggingOn(LogonEventArgs args) { base.OnLoggingOn(args); Tracing.Tracer.LogText("Going into OnLoggingOn"); try { var username = ((AuthenticationStandardLogonParameters)args.LogonParameters).UserName; string cs = string.Empty; IValueManagerStorageContext valueManagerStorageContext = ServiceProvider.GetRequiredService<IValueManagerStorageContext>(); valueManagerStorageContext.RunWithStorage(() => { cs = ValueManager.GetValueManager<string>("ApplicationCS").Value; Tracing.Tracer.LogText($"The CS from value manager is not empty : {cs}"); if (string.IsNullOrEmpty(cs)) { Tracing.Tracer.LogText($"The CS from value manager is empty, setting value... : {cs}"); ValueManager.GetValueManager<string>("ApplicationCS").Value = "Integrated Security=SSPI;Pooling=false;Data Source=(localdb)\\mssqllocaldb;Initial Catalog=XAfTest0034"; } }); ((XPObjectSpaceProvider)ObjectSpaceProviders[0]).SetDataStoreProvider(GetDataStoreProvider(cs, null)); } catch (Exception ex) { throw new UserFriendlyException($"Nom d'utilsateur ou mot de passe inconnu. Veuillez vérifier les données entrées ou appeler le support technique."); } }

When running, I see that the first time going in into the OnLoggingOn, I can get the CS from the value manager. But the app goes a second time into the OnLogginOn and the ValueManager provides a null value.

Can you help on why the second time it has a null value ?

Many thanks
ISA

Show previous comments (2)

    Hi Gosha,

    Just finished my message and did not see you answered.
    When using the report wizard / designer or dashboard, this solution goes in the OnLoggingOn dozens times.
    This generates many connections that are not directly closed. What is strange is that in a normal way (this cs in the appsetting.json), i have only around 5 open sessions in the pool.

      For example, if you run the attached sample and run a dashboard designer, we will see that my tracing messages in the OnlogginOn will appear around 20 times just by starting the dashboard designer and adding a grid to it.

        Maybe I should go in a simpler way here and just put static string property that I assign in OnLoggingOn once.
        The idea is to keep this value with the CS until the user logoff.

        C#
        public static string TenantCS; protected override void OnLoggingOn(LogonEventArgs args) { base.OnLoggingOn(args); try { Tracing.Tracer.LogText("Going into OnLoggingOn"); IConfiguration configuration = ServiceProvider.GetRequiredService<IConfiguration>(); var adminCS = configuration.GetConnectionString("AdminCS"); var username = ((AuthenticationStandardLogonParameters)args.LogonParameters).UserName; if(string.IsNullOrEmpty(TenantCS)) { TenantCS = BrokeroAdminHelper.GetTenantCSFromNpgsqlAsync(adminCS, username); } ((XPObjectSpaceProvider)ObjectSpaceProviders[0]).SetDataStoreProvider(GetDataStoreProvider(TenantCS, null)); } catch(Exception ex) { throw new UserFriendlyException($"Nom d'utilsateur ou mot de passe inconnu. Veuillez vérifier les données entrées ou appeler le support technique."); } }

        Do you see any possible issue doing this ?

        Answers approved by DevExpress Support

        created 2 years ago

        But the app goes a second time into the OnLogginOn and the ValueManager provides a null value.

        We published the following documentation on the subject:

        Web API Service and other non-XAF UI Apps:

        XAF Blazor UI:

        Breaking Change in v22.1+

          created 3 years ago (modified a month ago)

          Thank you for describing your scenario, ISA.

          First, we do not recommend that you rely on IValueManagerStorageContext and other internal and undocumented APIs (they may be removed in future versions and we do not provide formal support on them - use them at your own risk).

          To get the desired functionality, it's better to create your own service that will store the user name and the user connection string in a dictionary. For example, you can implement it as in your Blazor-specific Module:

          C#
          public class MyUserCSService { Dictionary<string, string> store; public MyUserCSService() { store = new Dictionary<string, string>(); } public void AddCS(string userName, string cs) { if (!store.ContainsKey(userName)) { store.Add(userName, cs); } } public string GetCS(string userName) { string cs; store.TryGetValue(userName, out cs); return cs; } }

          Then, register this service in the ConfigureServices method of the Startup.cs file. NOTE: your choice of a Singleton or Scoped service fully depends on your business requirements - review the ASP.NET Core documentation for more information. For demo purposes, below we will register a Singleton service that will be created only once and be alive while the application is alive (you may have services specific to each user request).

          C#
          services.AddSingleton<MyUserCSService>();

          After this, you can get the user connection string or add a new user connection string in the following way:

          C#
          // add MyUserCSService myService = ServiceProvider.GetRequiredService<MyUserCSService>(); myService.AddCS(userName, "CS"); // get MyUserCSService myService = ServiceProvider.GetRequiredService<MyUserCSService>(); string cs = myService.GetCS(username);

          Please let me know how this solution works for you.

            Comments (3)

              Thanks Gosha !

              This works well.

              BR
              ISA

                Hi Gosha,
                Why do you suggest singleton instead of scoped service? Please can you clarify it.
                In case of scoped service we did not need to use a dictionary but just a single class instance that stores user specific data in the service. So if same user logs from different locations/computers, this could be more suitable solution i think.
                What do you think about this solution, do you see any drawbacks?

                Thanks

                Dennis Garavsky (DevExpress) 2 years ago

                  @Akin: your choice of a Singleton or Scoped service fully depends on your business requirements - review the ASP.NET Core documentation for more information. For demo purposes, below we registered a Singleton service that will be created only once and be alive while the application is alive (you may have services specific to each user request). I've updated our answer above to avoid confusion.

                  Disclaimer: The information provided on DevExpress.com and affiliated web properties (including the DevExpress Support Center) is provided "as is" without warranty of any kind. Developer Express Inc disclaims all warranties, either express or implied, including the warranties of merchantability and fitness for a particular purpose. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.

                  Confidential Information: Developer Express Inc does not wish to receive, will not act to procure, nor will it solicit, confidential or proprietary materials and information from you through the DevExpress Support Center or its web properties. Any and all materials or information divulged during chats, email communications, online discussions, Support Center tickets, or made available to Developer Express Inc in any manner will be deemed NOT to be confidential by Developer Express Inc. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.