Base solution for your next web application

Activities of "devkev2403"

@ismcagdas

Yes, I have spent quite a lot of time on this issue. I have done a lot of research and as per my previous post I have:

Put data protection keys in Redis (not sure SignalR uses them) Made machine keys the same on both servers Have the app pool on both servers running as the same user

I'm not sure where the problem lies - just wondered if you have any ideas?

I will keep investigating.

Thank you for your reply.

I am actually using Abp.Web.SignalR. The ASP.NET Zero template I am using is version 2.2.2.

SIgnalR itself works, except when load balanced. This is how I configure SignalR:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            //Initializes ABP framework.
            app.UseAbp(options =>
            {
                options.UseAbpRequestLocalization = false; //used below: UseAbpRequestLocalization
            });

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseStatusCodePagesWithRedirects("~/Error?statusCode={0}");
                app.UseExceptionHandler("/Error");
            }

            app.UseAuthentication();
            app.UseJwtTokenMiddleware();

            if (bool.Parse(_appConfiguration["IdentityServer:IsEnabled"]))
            {
                app.UseIdentityServer();
            }

            app.UseStaticFiles();
            app.UseAbpRequestLocalization();

#if FEATURE_SIGNALR
            //Integrate to OWIN
            app.UseAppBuilder(ConfigureOwinServices);
#endif

            app.UseSession();

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "defaultWithArea",
                    template: "{area}/{controller=Home}/{action=Index}/{id?}");

                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }

#if (FEATURE_SIGNALR)
        private void ConfigureOwinServices(IAppBuilder app)
        {
            app.Properties["host.AppName"] = "MyApp";
            app.UseAbp();

            //added based on this https://github.com/aspnetboilerplate/aspnetboilerplate/issues/1962
            GlobalHost.DependencyResolver.Register(typeof(IAssemblyLocator), () => new SignalRAssemblyLocator());

            // configure SignalR to use Redis backplane
            GlobalHost.DependencyResolver.UseRedis(_appConfiguration.GetRedisConnectionString(), 6379, "", "MyQueue");
            app.MapSignalR();

        }

Unfortunately the code isn't mine to share as it belongs to my employer, however, let me see what I can do, as we do like to give back to the community when we can.

Thank you, that is the approach I am taking after finding:

<a class="postlink" href="https://docs.microsoft.com/en-us/aspnet/core/migration/webapi">https://docs.microsoft.com/en-us/aspnet ... ion/webapi</a>

Good to know I am on the right track.

Thanks again.

Actually, this worked! Thanks

public class MultiTenantMigrateExecuter : ITransientDependency
    {
        public ILogger Log { get; }

        private readonly AbpZeroDbMigrator _migrator;
        private readonly IRepository<Tenant> _tenantRepository;
        private readonly IDbPerTenantConnectionStringResolver _connectionStringResolver;

        public MultiTenantMigrateExecuter(
            AbpZeroDbMigrator migrator,
            IRepository<Tenant> tenantRepository,
            ILogger log,
            IDbPerTenantConnectionStringResolver connectionStringResolver)
        {
            Log = log;
            _migrator = migrator;
            _tenantRepository = tenantRepository;
            _connectionStringResolver = connectionStringResolver;
        }

        public void Run()
        {
            var hostConnStr = _connectionStringResolver.GetNameOrConnectionString(new ConnectionStringResolveArgs(MultiTenancySides.Host));
            if (hostConnStr.IsNullOrWhiteSpace())
            {
                Log.Error("Configuration file should contain a connection string named 'Default'");
                return;
            }
            Log.Info("Host database: " + ConnectionStringHelper.GetConnectionString(hostConnStr));

            Log.Info("HOST database migration started...");
            try
            {
                _migrator.CreateOrMigrateForHost(SeedHelper.SeedHostDb);
            }
            catch (Exception ex)
            {
                Log.Error("Canceled migrations - An error occured during migration of host database.", ex);
                return;
            }
            Log.Info("HOST database migration completed.");

            var migratedDatabases = new HashSet<string>();
            var tenants = _tenantRepository.GetAllList(t => t.ConnectionString != null && t.ConnectionString != "");
            for (var i = 0; i < tenants.Count; i++)
            {
                var tenant = tenants[i];
                Log.Info($"Tenant database migration started... ({(i + 1)} / {tenants.Count})");
                Log.Info("Name              : " + tenant.Name);
                Log.Info("TenancyName       : " + tenant.TenancyName);
                Log.Info("Tenant Id         : " + tenant.Id);
                Log.Info("Connection string : " + SimpleStringCipher.Instance.Decrypt(tenant.ConnectionString));

                if (!migratedDatabases.Contains(tenant.ConnectionString))
                {
                    try
                    {
                        _migrator.CreateOrMigrateForTenant(tenant);
                    }
                    catch (Exception ex)
                    {
                        Log.Error("An error occured during migration of tenant database. Skipped this tenant and will continue for others...", ex);
                    }
                    migratedDatabases.Add(tenant.ConnectionString);
                }
                else
                {
                    Log.Info("This database has already migrated before (you have more than one tenant in same database). Skipping it....");
                }

                Log.Info($"Tenant database migration completed. ({i + 1} / {tenants.Count})");
            }

            Log.Info("All databases have been migrated.");
        }
    }
public class MyEntityFrameworkCoreModule : AbpModule
    {
...
...
...
        public override void PostInitialize()
        {
            EnsureMigrated();
            if (!SkipDbSeed)
            {
                SeedHelper.SeedHostDb(IocManager);
            }
        }

        private void EnsureMigrated()
        {
            using (var migrateExecuter = IocManager.ResolveAsDisposable<MultiTenantMigrateExecuter>())
            {
                migrateExecuter.Object.Run();
            }
        }
    }

Hi, thanks for the reply.

The migrator tool works and that is what we currently use. However, we want the migrations to be automatically applied on our staging system so we can have Continuous Delivery. We explicitly create the migrations but want them automatically applied.

We will be using the migrator tool on our live environment BTW! :)

I think I have solved this. The problem is a limitation in the OnlineClientManager. The SignalRRealTimeNotifications class that is used to send SignalR message uses the OnlineClientManager to determine who should get the notification and what the SignalR connection id is. However, the OnlineClientManager is only aware of clients connected to the server it is running on, not the entire web farm.

To fix this I am moving the dictionary used by the OnlineClientManager into Redis. It's not finished but so far so good.

Yes, all users are subscribed. I think the back plane is working. I am now using my own version of SignalRRealTimeNotifier as I think the issue may be here.

Thank you for your reply.

I will have to read up on ABP back ground jobs.

For the backplane, I have a Redis installation and I have simply configured ABP to use it. I haven't made any SignalR changes yet.

private void ConfigureOwinServices(IAppBuilder app)
{
    app.Properties["host.AppName"] = "Phone";

    app.UseAbp();

    // configure SignalR to use Redis backplane
    GlobalHost.DependencyResolver.UseRedis(_redisConnString, 6379, "", "PhonePbx");
    app.MapSignalR();
}

Then, as per documentation on notifications, I create a notification in AppNotifier class

public async Task IncomingCallAsync(string ddi, string cli)
{
    await _notificationPublisher.PublishAsync(
        AppNotificationNames.IncomingCall,
        new MessageNotificationData($"New incoming phone call to {ddi} from {cli}"),
        severity: NotificationSeverity.Success
    );
}

I haven't had to change any client side code as this is already built in to ABP/Metronic.

Is this the correct approach. I have a web form behind a load balancer. When I raise a notification I want all clients to receive the notification, which means all servers will have to use SIgnalR to push the notification to their clients.

Hi Aaron,

this only seems to be a problem on .NET Core v3.0.0.0. The code fix I suggested is not needed on on the MVC 5.x version bt the UserAppService code looks the same (2 minor differences on .NET core version).

Showing 1 to 10 of 26 entries