Symptom

 

Normally, you can configure the application.json to change the password, and then restart the Web API. Sometimes, you may need to change the database password dynamically when PowerServer Web API is running.

E.g.: the password is not allowed to be stored in the Configuration file due to security policies, the database password needs to be obtained from a third party.

 

Environment

PowerServer 2022 R3 and later

 

Resolution

 

Solution 1. Storing database connections in the database

Refer to Storing database connections in the database - - PowerServer 2022 R3 Help

 

Solution 2. Write your own logic to get the password

This is an example based on the Sales App.

Step 1. Follow PS 2022 R3 Quick Start to make sure the Example Sales App can run successfully in PowerBuilder IDE.

https://docs.appeon.com/ps2022r3/quick_start_guide1.html

Step 2. Change the C# solution

a. After the application is deployed, open the C# solution and add the class AppUserConfigurationProvider.cs to the C# solution >> UserExtensions\AppConfig folder. You can add your own logic in the GetDbPassword() method to change the password for a database connection.

Please note that the constructor used in the sample code may vary depending on the PowerServer version. Starting from PowerServer 2025 R2, FileSystemConfigurationProvider requires an additional parameter.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using PowerServer.Core;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration.Json;

namespace ServerAPIs
{
    public class AppUserConfigurationProvider : FileSystemConfigurationProvider
    {
        private readonly IHttpContextAccessor _httpContextAccessor;
        
        // PowerServer 2022/2025
        public AppUserConfigurationProvider(IConfiguration configuration) :
            base(Path.Combine(AppContext.BaseDirectory, "appconfig\\applications.json"),
            new ArgumentsConfigurationProvider(configuration))
        {
            
        }


       // PowerServer 2025 R2 and later: an additional IConfiguration parameter is required.
       //public AppUserConfigurationProvider(IConfiguration configuration) :
       //    base(Path.Combine(AppContext.BaseDirectory, "appconfig\\applications.json"), 
       //       configuration,
       //       new ArgumentsConfigurationProvider(configuration))
       //{
       //    
       //} 
        
        public async override Task<IEnumerable<ConnectionConfigurationItem>> GetConnectionsAsync(
            string cacheGroup, System.Threading.CancellationToken cancellationToken)
        {
            // Get a sequence of connection configurations that belong to a cache group.
            var dbConnectons = await base.GetConnectionsAsync(cacheGroup, cancellationToken);
            
            return dbConnectons.Select(o =>
            {
                // Refresh the password of each database cache name.
                o.Configuration.Password = GetDbPassword(o.CacheName);
                
                return o;
            });
        }
        
        private string GetDbPassword(string cacheName)
        {
            // Add your own logic here to get the new password.
            // ...  
            // E.g.:
            if (cacheName == "sales")
            {
                return "sql";
            }
            else
            {
                return "";
            }
        }
    }
}

b. Rewrite the UserExtensions\AppConfig\ AppConfigExtensions.cs file in the C# solution.

Notice that all Code blocks generated by default have already been removed.

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using PowerServer.Core;

namespace ServerAPIs
{
    public static class AppConfigExtensions
    {
        //To be called in Startup.ConfigureServices, for adding services related to the PowerServer configuration management module
        public static IServiceCollection AddPowerServerAppConfig(
            this IServiceCollection services, IConfiguration configuration, IHostEnvironment hostingEnvironment)
        {
            // Use a customized AppConfig provider.
            services.AddSingleton<IPowerServerConfigurationProvider, AppUserConfigurationProvider>();
            
            return services;
        }
    }
}

Solution 3. Dynamically retrieve database credentials at runtime

This solution applies only when the database connection information has already been stored in the PowerServer configuration table as described in Solution 1. You can then implement your own logic to dynamically retrieve and refresh the User ID and password at runtime.

The following example is based on the Sales App.

Step 1. Follow the tutorial below to store the database connection information in the database and ensure that the Example Sales App can run successfully in the PowerBuilder IDE.

https://docs.appeon.com/ps2022r3/quick_start_guide1.html https://docs.appeon.com/ps2022r3/Storing_the_database_connection_in_the_database.html

Step 2. Change the C# solution

a. After deploying the application and storing the database connection information in the database table, open the C# solution and add the AppUserConfigurationProvider.cs class to the UserExtensions\AppConfig folder of the C# solution. You can add your custom logic in the GetDBUserID() and GetDbPassword() methods to dynamically determine and return the database UserID and Password.

using Microsoft.Extensions.Options;
using PowerServer.Core;
using SnapObjects.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace ServerAPIs
{
    public class AppUserConfigurationProvider : DatabaseConfigurationProvider
    {
        public AppUserConfigurationProvider(
            Func creator,
            ArgumentsConfigurationProvider provider,
            IOptionsMonitor options)
            : base(creator, provider, options)
        {
           
        }
      
        public async override Task> GetConnectionsAsync(
            string cacheGroup, System.Threading.CancellationToken cancellationToken)
        {
            // Get a sequence of connection configurations that belong to a cache group.
            var dbConnectons = await base.GetConnectionsAsync(cacheGroup, cancellationToken);

            return dbConnectons.Select(o =>
            {
                // Refresh the UserID of each database cache name.
                o.Configuration.UserID = GetDbUserID(o.CacheName);

                // Refresh the password of each database cache name.
                o.Configuration.Password = GetDbPassword(o.CacheName);

                return o;
            });
        }

        private string GetDbUserID(string cacheName)
        {
            // Add your own logic here to get the user ID.
            // ...  
            // E.g.:
            if (cacheName == "sales")
            {
                return "dba";
            }
            else
            {
                return "";
            }
        }

        private string GetDbPassword(string cacheName)
        {
            // Add your own logic here to get the new password.
            // ...  
            // E.g.:
            if (cacheName == "sales")
            {
                return "sql";
            }
            else
            {
                return "";
            }
        }
    }
}

b. Rewrite the UserExtensions\AppConfig\ AppConfigExtensions.cs file in the C# solution.

Notice that all Code blocks generated by default have already been removed.

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using PowerServer.Core;
using SnapObjects.Data;
using System;
using System.Linq;

namespace ServerAPIs
{
    public static class AppConfigExtensions
    {
       public static IServiceCollection AddPowerServerAppConfig(
            this IServiceCollection services, IConfiguration configuration, IHostEnvironment hostingEnvironment)
        {
            services.AddAppConfigFromDatabaseEx(context =>
            {
                context.Configuration = configuration;

                context.AppConfigDirectory = "AppConfig";

                context.HostingEnvironment = hostingEnvironment;

                context.ConnectionString = configuration.GetConnectionString("AppConfig");
            });

            return services;
        }

        public static IServiceCollection AddAppConfigFromDatabaseEx(
            this IServiceCollection services, Action contextAction)
        {
            services = AddAppConfigFromDatabaseEx(services, contextAction, DatabaseType.SqlAnywhere, true);

            // Uncomment the following line to use SQL Server as the configuration database.
            //services = AddAppConfigFromDatabaseEx(services, contextAction, DatabaseType.SqlServer, false); 

            // Uncomment the following line to use Oracle as the configuration database.
            //services = AddAppConfigFromDatabaseEx(services, contextAction, DatabaseType.Oracle, false);

            return services;
        }

        private static IServiceCollection AddAppConfigFromDatabaseEx(
            this IServiceCollection services,
            Action contextAction,
            DatabaseType databaseType,
            bool isOdbc = false)
        {
            var context = new DatabaseConfigurationContext();

            contextAction(context);

            var connectionString = context.ConnectionString
                ?? throw new ArgumentNullException(nameof(context.ConnectionString));

            var argConfigProvider = new ArgumentsConfigurationProvider(context.Configuration);

            services.AddSingleton(m =>
            {
                var providers = m.GetServices();

                var provider = providers.FirstOrDefault(m => m.CanCreate(databaseType, isOdbc));

                if (provider != null)
                {
                    var options = m.GetRequiredService>();

                    return new AppUserConfigurationProvider(() => provider.Create(connectionString), argConfigProvider, options);
                }

                throw new NotSupportedException("The specified data provider was not found.");
            });

            return services;
        }

    }
}
10
0