@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();
}
I am running ABP MVC .Net Core on a web farm behind a load balancer. I use SignalR for notifications. After logging in attempts are made to initiate a SignalR connection. In the browser dev tools you can see the activity:
/signalr/connect?transport=serverSentEvents&clientProtocol=1.5&connectionToken=xxx&connectionData=%5B%7B%22name%22%3A%22abpcommonhub%22%7D%2C%7B%22name%22%3A%22chathub%22%7D%5D&tid=6
However, this gives a 400 with the following error: The ConnectionId is in the incorrect format.
Following advice from other sources: I have shared .NET Core data protection keys in Redis. IIS has defined machine keys so these are the same on all servers. The app pools are set to run as the same domain user.
Can you help me discover what I have missed please.
Thanks
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.
Hi,
I want to write my own WebAPI based on the documentation here:
<a class="postlink" href="https://aspnetboilerplate.com/Pages/Documents/Web-API-Controllers">https://aspnetboilerplate.com/Pages/Doc ... ontrollers</a>
However, the nuget package is not for .NET Core. Is there a different approach for .NET Core?
Thanks
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! :)
Hi,
I am trying to trigger EF migrations using Dtabase.Migrate.
I have used this code as a reference: <a class="postlink" href="https://stackoverflow.com/questions/45123604/ef-core-migration-with-aspnetboilerplate-where-to-trigger-context-database-migra">https://stackoverflow.com/questions/451 ... base-migra</a>
But I think migration should take place before seeding - seeding may need new schema!
Currently I do this:
public class MyEntityFrameworkCoreModule : AbpModule
{
/* Used it tests to skip dbcontext registration, in order to use in-memory database of EF Core */
public bool SkipDbContextRegistration { get; set; }
public bool SkipDbSeed { get; set; }
public override void PreInitialize()
{
if (!SkipDbContextRegistration)
{
Configuration.Modules.AbpEfCore().AddDbContext<MyDbContext>(options =>
{
if (options.ExistingConnection != null)
{
MyDbContextConfigurer.Configure(options.DbContextOptions, options.ExistingConnection);
}
else
{
MyDbContextConfigurer.Configure(options.DbContextOptions, options.ConnectionString);
}
});
}
}
public override void Initialize()
{
IocManager.RegisterAssemblyByConvention(typeof(MyEntityFrameworkCoreModule).GetAssembly());
}
public override void PostInitialize()
{
// THIS IS WHERE I DO MIGRATIONS
EnsureMigrated();
if (!SkipDbSeed)
{
SeedHelper.SeedHostDb(IocManager);
}
}
private void EnsureMigrated()
{
// THIS IS HOW SeedHelper DOES IT
using (var uowManager = IocManager.ResolveAsDisposable<IUnitOfWorkManager>())
{
using (var uow = uowManager.Object.Begin(TransactionScopeOption.Suppress))
{
var context = uowManager.Object.Current.GetDbContext<MyDbContext>(MultiTenancySides.Host);
context.Database.Migrate();
uow.Complete();
}
}
}
However, this code throws an exception: connection is already in a transaction and cannot participate in another transaction
Any help appreciated.
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.