I'd like to use Redis for caching, but I still want my service to be functional if a Redis instance isn't found at runtime.
Are there any examples of this in practice?
You could do something like the following in your AppHost Configure method
public override void Configure(Container container)
{
...
try
{
var redisManager = new PooledRedisClientManager("localhost:6379");
// do some sort of test to see if we can talk to the redis server
var client = redisManager.GetCacheClient();
var fakeKey = "_________test_______";
client.Add(fakeKey, 1);
client.Remove(fakeKey);
// if it worked register the cacheClient
container.Register(c => redisManager.GetCacheClient()).ReusedWithin(ReuseScope.None);
}
catch (Exception ex)
{
// fall back to in memory cache
// Log some sort of warning here.
container.Register<ICacheClient>(c => new MemoryCacheClient());
}
...
}
Related
as the title suggests I should use Redis both as DistributedCache and to store the key for the DataProtection, the problem is that I don't know if it is correct to register the Redis instance twice, like this:
public void ConfigureServices(IServiceCollection services)
{
//......
// First Instance of Redis
serviceCollection.AddStackExchangeRedisCache(options =>
{
options.ConfigurationOptions = new ConfigurationOptions();
options.ConfigurationOptions.EndPoints.Add("127.0.0.1", 6379);
options.ConfigurationOptions.Password = "*********";
options.ConfigurationOptions.ConnectRetry = 5;
});
// Second Instance of Redis
var redis = ConnectionMultiplexer.Connect("127.0.0.1:6379");
serviceCollection.AddDataProtection()
.PersistKeysToStackExchangeRedis(redis, "DataProtection-Keys");
//......
}
or it is possible to share the same instance already registered in the first method?
In case it is possible to come to do?
Thanks
in .NET 6, microsoft have added new option when configuring redis to allow get or set delegate to create ConnectionMultiplexer, below is naive example
ConnectionMultiplexer cm = ConnectionMultiplexer.Connect("server1:6729");
services.AddSingleton<IConnectionMultiplexer>(cm);
services.AddStackExchangeRedisCache(options =>
{
options.ConnectionMultiplexerFactory = () =>
{
var serviceProvider = services.BuildServiceProvider();
IConnectionMultiplexer connection = serviceProvider.GetService<IConnectionMultiplexer>();
return Task.FromResult(connection);
};
});
Wy not like this?
IConnectionMultiplexer? connectionMultiplexer = ConnectionMultiplexer.Connect(Configuration.GetConnectionString("LockExchange"));
services.AddSingleton(connectionMultiplexer);
services.AddStackExchangeRedisCache(options =>
{
options.ConnectionMultiplexerFactory = () => Task.FromResult(connectionMultiplexer);
});
My company is using 2 Windows servers. 1 Server is running as a backup server and other than SQL replication, the backup server requires manual intervention to get it running as the primary. I have no control over this, but I do have control of the apps/services running on the servers.
What I have done is I got all the services to be running on both and added Rabbit MQ as a clustered message broker to kind of distribute the work between the servers. This is all working great and when I take a server down, nothing is affected.
Anyway, to the point of the question, the only issue I see is that the services are using the same SQL server and I have nothing in place to automatically switch server if the primary goes down.
So my question is, is there a way to get Entity Framework to use an alternative connection string should one fail?
I am using the module approach with autofac as dependency injection for my services. This is the database registration.
public class AppsDbModule : Module
{
protected override void Load(ContainerBuilder builder)
{
RegisterContext<AppsDbContext>(builder);
}
private void RegisterContext<TContext>(ContainerBuilder builder) where TContext : DbContext
{
builder.Register(componentContext =>
{
var serviceProvider = componentContext.Resolve<IServiceProvider>();
var configuration = componentContext.Resolve<IConfiguration>();
var dbContextOptions = new DbContextOptions<TContext>(new Dictionary<Type, IDbContextOptionsExtension>());
var optionsBuilder = new DbContextOptionsBuilder<TContext>(dbContextOptions)
.UseApplicationServiceProvider(serviceProvider)
.UseSqlServer(configuration.GetConnectionString("AppsConnection"),
serverOptions => serverOptions.EnableRetryOnFailure(5, TimeSpan.FromSeconds(30), null));
return optionsBuilder.Options;
}).As<DbContextOptions<TContext>>()
.InstancePerLifetimeScope();
builder.Register(context => context.Resolve<DbContextOptions<TContext>>())
.As<DbContextOptions>()
.InstancePerLifetimeScope();
builder.RegisterType<TContext>()
.AsSelf()
.InstancePerLifetimeScope();
}
}
and my appsettings.json as this
"ConnectionStrings": {
"AppsConnection": "Data Source=primary;Initial Catalog=Apps;User Id=me;Password=topsecret"
}
Couldn't really find anything on the web, other than posts where you was in full control of creating the db connection, but I am being supplied the connection via DI.
Using .Net 5 and the applications are worker services.
You can define on custom retry strategy on implementing the interface IExecutionStrategy.
If you want reuse the default SQL Server retry strategy, you can derive from SqlServerRetryingExecutionStrategy on override the method ShouldRetryOn :
public class SqlServerSwitchRetryingExecutionStrategy : SqlServerRetryingExecutionStrategy
{
public string _switchConnectionString;
public SqlServerSwitchRetryingExecutionStrategy(ExecutionStrategyDependencies dependencies, string switchConnectionString)
: base(dependencies, 3)
{
_switchConnectionString = switchConnectionString;
}
protected override bool ShouldRetryOn(Exception exception)
{
if (exception is SqlException sqlException)
{
foreach (SqlError err in sqlException.Errors)
{
switch (err.Number)
{
// For this type of error, switch the connection string and retry
case 1418: // The server can't be reached or does not exist
case 4060: // Cannot open database
case 4064: // Cannot open user default database database
var db = Dependencies.CurrentContext.Context.Database;
var current = db.GetConnectionString();
if(current != _switchConnectionString)
db.SetConnectionString(_switchConnectionString);
return true;
}
}
}
return base.ShouldRetryOn(exception);
}
}
I am not sure which errors to catch for your scenario.
You should test and find the errors to handle.
The full list is available Database engine errors.
To inject the strategy :
new DbContextOptionsBuilder<TContext>(dbContextOptions)
.UseSqlServer(
configuration.GetConnectionString("AppsConnection"),
serverOptions => serverOptions.ExecutionStrategy(dependencies =>
new SqlServerSwitchRetryingExecutionStrategy(
dependencies,
configuration.GetConnectionString("AppsConnectionBackup"))
)
);
If you want a full custom strategy, you can get inspiration from SqlServerRetryingExecutionStrategy.
I’m currently testing concurrent update situations for my c#/NHibernate application.
In the hibernate-configuration command_timeout is set 1 second for testing purposes.
I use the hibernate version in the mapping file.
Here is the code for testing:
namespace Test
{
class Program
{
static void Main(string[] args)
{
MyHibernateConnector hc = new MyHibernateConnector(); // 1 usage see below; also provides some other hibernate related methods
MyHibernateConfig myhibconfig = new MyHibernateConfig(); // reads and holds hibernate configuration
hc.setHibernateConfig(myhibconfig);
ISession session = hc.getSessionAndStartTransaction();
// getSessionAndStartTransaction() does the following:
// - on first call: reads the hibernate configuration and builds the SessionFactory
// - gets the session as follows:
// ISession session;
// if (CurrentSessionContext.HasBind(sf))
// {
// session = sf.GetCurrentSession();
// }
// else
// {
// session = sf.OpenSession();
// CurrentSessionContext.Bind(session);
// }
// - and does session.BeginTransaction();
MyClass obj;
IQuery q = session.CreateQuery("select mc from MyClass as mc where mc.ID = ?");
q.SetInt64(0, 60);
l = q.List<MyClass>();
if (l.Count > 0)
obj = l[0];
session.Transaction.Rollback();
// now update obj in another application with autocommit off and do not commit or rollback
/* breakpont is set here */ session = hc.getSessionAndStartTransaction();
try
{
session.Lock(obj, LockMode.Upgrade);
} catch (Exception e)
{
try
{
session.Transaction.Rollback();
}
catch (Exception e2)
{
Type t = e2.GetType();
}
}
// ...
}
}
}
On session.Lock(obj, LockMode.Upgrade) an exception (GenericADOException) is thrown as expected with message:
could not lock: … for update
If I now catch this exception and try to do an ISession.Transaction.Rollback() a TransactionException is thrown.
I would have expected that a rollback is the appropriate action to resolve concurrent update situations. Is this not so ? And what would be the appropriate action ? What is the state of the transaction after the TransactionException ?
I use NHibernate version 5.1.0.0 and MySql.Data version 6.10.7.0. Hibernate property dialect is NHibernate.Dialect.MySQL55InnoDBDialect.
After some more experimenting I think I found a solution - or should I better say a workaround:
The ISession.Connection is closed after timeout occured on session.Lock(obj, LockMode.Upgrade), so open it again before the rollback:
if (session.Connection.State.Equals(ConnectionState.Closed)) // <- !!!
session.Connection.Open();
session.Transaction.Rollback();
This works and I could do further transactions with the same session.
Why the connection is closed in the first place remains a mystery to me!
And I wonder if this behaviour is common to other (all?) underlying DBMS and/or drivers.
I create custom error logger in CRM 2013 have functionality to save error information into CRM entity. I debug my code and find that my code works well. But the problem is when CRM rollback the transaction, the log entity also disappear. I want to know is it possible to create entity on catch block and still throw that error?
public void Execute(IServiceProvider serviceProvider)
{
try
{
...
}
catch (Exception ex)
{
IPluginExecutionContext context =
(IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.
GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = serviceFactory.CreateOrganizationService(Guid.Empty);
var log = new Log
{
Message = ex.Message
};
service.Create(log);
throw;
}
}
I found the other way to solve this issue. We can create new service to create new transaction outside the transaction being failed. Here some snippet if you want to do the same:
try
{
...
}
catch (Exception ex)
{
var HttpCurrentContext = HttpContext.Current;
var UrlBase = HttpCurrentContext.Request.Url.Host;
string httpUrl = #"http://";
if (HttpCurrentContext.Request.IsLocal)
{
UrlBase += ":" + HttpCurrentContext.Request.Url.Port;
}
if (!UrlBase.Contains(httpUrl))
{
UrlBase = httpUrl + UrlBase;
}
var UriBase = UriBuilder(UrlBase.ToLowerInvariant().Trim() + "/xrmservices/2011/organization.svc").Uri;
IServiceConfiguration<IOrganizationService> orgConfigInfo =
ServiceConfigurationFactory.CreateConfiguration<IOrganizationService>(UriBase);
var creds = new ClientCredentials();
using (_serviceProxy = new OrganizationServiceProxy(orgConfigInfo, creds))
{
// This statement is required to enable early-bound type support.
_serviceProxy.ServiceConfiguration.CurrentServiceEndpoint.Behaviors.Add(new ProxyTypesBehavior());
_service = (IOrganizationService)_serviceProxy;
var log = new Log
{
Message = ex.Message
};
_service.Create(NewLog);
}
throw;
}
Essentially, no. You cannot prevent that an exception rolls back the transaction. See a similar question on StackOverflow.
A common approach is to create a separate logging service that can store logs outside of the database transaction.
B.t.w. Dynamics CRM 2015 spring release introduces the capability to store logs regardless if your plugin is participating in a database transaction.
I've setup a series of tasks using the Application events in a global.asax.cs file like so:
// Note: For instructions on enabling IIS7 classic mode,
// visit http://go.microsoft.com/?LinkId=301868
public class MvcApplication : System.Web.HttpApplication
{
private static IContainer ContainerGlobal;
private static ILogger Logger;
public ILifetimeScope Container
{
get { return (ILifetimeScope)HttpContext.Current.Items["_Container"]; }
set { HttpContext.Current.Items["_Container"] = value; }
}
protected void Application_Start()
{
AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier;
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
//configure the Autofac IoC container
var container = AutofacBuilder.Configure(Assembly.GetExecutingAssembly(),
new MvcModule(), new TaskModule());
// startup the logging
var path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().CodeBase)
.Replace("file:\\", string.Empty);
var file = new FileInfo(path + #"\log4net.ui.config");
Logger = new Logger(MethodBase.GetCurrentMethod().DeclaringType, file);
var runAtInits = container.Resolve<IEnumerable<IRunAtInit>>();
if (runAtInits != null)
{
Logger.LogFormat(LogType.Debug, "Found {0} IRunAtInit instances",
runAtInits.Count());
foreach (var task in runAtInits)
{
task.Execute();
}
}
var runAtStartups = container.Resolve<IEnumerable<IRunAtStartup>>();
if (runAtStartups != null)
{
Logger.LogFormat(LogType.Debug, "Found {0} IRunAtStartup instances",
runAtStartups.Count());
foreach (var task in runAtStartups)
{
task.Execute();
}
}
ContainerGlobal = container;
}
public void Application_BeginRequest()
{
try
{
Container = ContainerGlobal.BeginLifetimeScope();
var runOnEachRequests =
Container.Resolve<IEnumerable<IRunOnEachRequest>>();
if (runOnEachRequests == null)
return;
Logger.LogFormat(LogType.Debug, "Found {0} IRunOnEachRequest instances",
runOnEachRequests.Count());
foreach (var task in runOnEachRequests)
{
task.Execute();
}
}
catch(Exception ex)
{
Logger.Log(LogType.Error, ex);
}
}
public void Application_Error()
{
try
{
var runOnErrors = Container.Resolve<IEnumerable<IRunOnError>>();
if (runOnErrors == null)
return;
Logger.LogFormat(LogType.Debug, "Found {0} IRunOnError instances",
runOnErrors.Count());
foreach (var task in runOnErrors)
{
task.Execute();
}
}
catch (Exception ex)
{
Logger.Log(LogType.Error, ex);
}
}
public void Application_EndRequest()
{
try
{
var runAfterEachRequests =
Container.Resolve<IEnumerable<IRunAfterEachRequest>>();
if (runAfterEachRequests == null)
return;
Logger.LogFormat(LogType.Debug, "Found {0} IRunAfterEachRequest instances",
runAfterEachRequests.Count());
foreach (var task in runAfterEachRequests)
{
task.Execute();
}
}
catch (Exception ex)
{
Logger.Log(LogType.Error, ex);
}
finally
{
if (Container != null)
{
Container.Dispose();
Container = null;
}
}
}
}
This is discussed by Matt Honeycut here:
https://github.com/MattHoneycutt/Fail-Tracker
As we make extensive use of Autofac throughout our application, we've implemented it using Autofac rather than Structuremap.
The problem is, we get exceptions raised for the following events: Application_EndRequest and sometimes for Application_BeginRequest. The exception raised in both cases is:
"No scope with a Tag matching 'AutofacWebRequest' is visible from the scope in which the instance was requested. This generally indicates that a component registered as per-HTTP request is being requested by a SingleInstance() component (or a similar scenario.) Under the web integration always request dependencies from the DependencyResolver.Current or ILifetimeScopeProvider.RequestLifetime, never from the container itself"
We've ensured that we use the ILifeScope for our Container object, rather than the root Container object. However, this doesn't fix the errors. Does anyone else have any suggestions as to what else we need to do?
For information, the call to AutofacBuilder.Configure above registers all necessary library types and modules and returns the root Container from autofac by calling the Build method.
thanks
PS We're using:
Visual Studio 2013,
MVC 5.1
Autofac 3.3
The error message pretty much says it all, but to be more specific, one or more of the following is occurring:
One or more of your begin/end request handlers is being registered as InstancePerHttpRequest
One or more of the dependencies required by your begin/end request handlers is being registered as InstancePerHttpRequest
One or more of the request handlers (or the dependencies for the request handlers) is trying to use DependencyResolver.Current during app start/end.
The Autofac dependency resolver requires a web request context to work. You can read more about that in the answer here: Autofac - The request lifetime scope cannot be created because the HttpContext is not available - due to async code?
App start/end don't have a web request running. They're outside the request pipeline.
Go through your registrations (which appear to be in AutofacBuilder.Configure) and check to see which ones are InstancePerHttpRequest. Something in there that is required during your handlers' execution is getting incorrectly registered that way, so when the web request scope isn't found - boom. And, again, it may not be just one thing - it could be that all your handlers are registered correctly, but one of the dependencies for your handlers are registered InstancePerHttpRequest.
If you find the issues and you don't want to switch to registering them as SingleInstance, consider switching the registration to InstancePerLifetimeScope. Chances are, unless your app is creating a bunch of lifetime scopes for units of work or something, that InstancePerLifetimeScope will behave just like InstancePerHttpRequest but will properly resolve without a web request.
That said, I would recommend wrapping the task execution in a lifetime scope so memory gets cleaned up:
using(var scope = container.BeginLifetimeScope())
{
// Resolve from a scope.
var runAtStartups = scope.Resolve<IEnumerable<IRunAtStartup>>();
// Do the run, etc.
}
Finally, something to be aware of: I see you're manually generating some sort of request lifetime scope yourself in the BeginRequest event. I'm not sure if it's important, but the scope you're creating there will not be the request lifetime scope that Autofac actually uses. It will only be used for your components, and it won't work with InstancePerHttpRequest registered stuff.