I have the following solution in order to implement multiple IDistributedCache definitions:
public interface IDBCache : IDistributedCache
{
}
public class DBCacheOptions : RedisCacheOptions { }
public class DBCache : RedisCache, IDBCache
{
public DBCache(IOptions<DBCacheOptions> optionsAccessor) : base(optionsAccessor)
{
}
}
And I have other definitions like the above pointint to different redis instances.
I am registering the cache service at Startup.cs as:
services.Configure<DBCacheOptions>(options => options.Configuration = configuration.GetValue<string>("Cache:DB"));
services.Add(ServiceDescriptor.Singleton<IDBCache, DBCache>());
And then I am wrapping IDBCache as:
public class DBCacheManager
{
private const string DB_CACHE_FORMAT = "DB:{0}";
private const int DB_EXPIRATION_HOURS = 8;
private readonly IDistributedCache _cache;
public DBCacheManager(IDBCache cache)
{
_cache = cache;
}
public Task AddDBItem(string name, string value)
{
return _cache.SetStringAsync(string.Format(DB_CACHE_FORMAT, name), value,
new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(DB_EXPIRATION_HOURS) });
}
}
And when I check for clients connected to redis (info clients command) connected_clients are incrementing without stopping, also when I see the clients list (client list command) I see the large connection list with long ages and idles.
Insights: I am using redis implementation of AWS ElasticCache which has unlimited idle timeout by default but I guess I should not be forcing to close these connections, should I? I suppose my application should be responsible.
This was a bad implementation of dependency injection. IDistributedCache interface does not have implementation of redis INCR command so somewhere in our project we were connecting directly using StackExchange.Redis with a DI wrapper that was creating multiple connection multiplexers and IDatabases.
Bottom line: my bad
Related
Which is the better way to carry request data(Is there any difference between two way)?
For example:
Option 1(Scoped Service):
//Scoped Service(this may be interface)
public class SampleScopedService
{
public string Data { get; set; }
}
//Register service
services.AddScoped<SampleScopedService>();
//Set and Get Data
public class SampleUsage
{
private readonly SampleScopedService _sampleScopedService;
public SampleUsage(SampleScopedService sampleScopedService)
{
_sampleScopedService = sampleScopedService;
// _sampleScopedService.Data = "Sample";
// _sampleScopedService.Data
}
}
Option 2(HttpContext.Items)
//Scoped Service
public class SampleScopedService
{
private readonly IHttpContextAccessor _accessor;
public SampleScopedService(IHttpContextAccessor accessor)
{
_accessor = accessor;
}
public string GetData()
{
return (string)_accessor.HttpContext.Items["Data"];
}
}
//Register service
services.AddScoped<SampleScopedService>();
//Set Data
HttpContext.Items[“Data”] = ”Sample”;
//Get Data
public class SampleUsage
{
private readonly SampleScopedService _sampleScopedService;
public SampleUsage(SampleScopedService sampleScopedService)
{
_sampleScopedService = sampleScopedService;
//_sampleScopedService.GetData();
}
}
According to docs:
Avoid storing data and configuration directly in DI. For example, a
user’s shopping cart shouldn’t typically be added to the services
container. Configuration should use the Options Model. Similarly,
avoid “data holder” objects that only exist to allow access to some
other object. It’s better to request the actual item needed via DI, if
possible.
Since Options 1 is example of “data holder”, as far as possible we should avoid it.
Furthermore, Options 1 may cause Captive Dependency if you don't pay attention.
So using Option 2 with singleton lifetime is better way than using Option 1.
I have an Orchard CMS module that loads up some code which provides service functions. The service code is written to be host agnostic and has been used with ASP.NET and WCF previously. The service code uses MEF to load plugins. One such plugin is for audit.
In an attempt to allow access to the Orchard database for audit I have modified the service code to also allow the host to pass in an audit implementation instance. Thus my Orchard module can pass in an instance when the service starts with the intention that this instance writes audit data as records in the Orchard DB.
I have created a migration for my database:
public int UpdateFrom5()
{
SchemaBuilder.CreateTable("AuditRecord",
table => table
.Column<int>("Id", c => c.PrimaryKey().Identity())
.Column<int>("AuditPoint")
.Column<DateTime>("EventTime")
.Column("CampaignId", DbType.Guid)
.Column("CallId", DbType.Guid)
.Column<String>("Data")
);
return 6;
}
I have created my AuditRecord model in Models:
namespace MyModule.Models
{
public class AuditRecord
{
public virtual int Id { get; set; }
public virtual int AuditPoint { get; set; }
public virtual DateTime EventTime { get; set; }
public virtual Guid CampaignId { get; set; }
public virtual Guid CallId { get; set; }
public virtual String Data { get; set; }
}
}
I have added an IAuditWriter interface that derives from IDependency so that I can inject a new instance when my module starts.
public interface IAuditWriter : IDependency
{
void WriteAuditRecord(AuditRecord data);
}
For my audit writer instance to work with the existing service code it must be derived from an abstract class FlowSinkAudit defined in the service library. The abstract class defines the Audit method. When the service needs to write audit it calls the audit method on all instances derived from the FlowAuditSink abstract class that have been instantiated either through MEF or by passing in an instance at startup.
public class AuditWriter : FlowAuditSink, IAuditWriter
{
private readonly IComponentContext ctx;
private readonly IRepository<AuditRecord> repo;
public AuditWriter(IComponentContext ctx, IRepository<AuditRecord> repo)
{
this.ctx = ctx;
this.repo = repo;
}
public void WriteAuditRecord(AuditRecord data)
{
// Get an audit repo
//IRepository<AuditRecord> repo = (IRepository<AuditRecord>)ctx.Resolve(typeof(IRepository<AuditRecord>));
using (System.Transactions.TransactionScope t = new System.Transactions.TransactionScope(System.Transactions.TransactionScopeOption.Suppress))
{
this.repo.Create(data);
}
}
public override void Audit(DateTime eventTime, AuditPoint auditPoint, Guid campaignId, Guid callId, IDictionary<String, Object> auditPointData)
{
// Add code here to write audit into the Orchard DB.
AuditRecord ar = new AuditRecord();
ar.AuditPoint = (int)auditPoint;
ar.EventTime = eventTime;
ar.CampaignId = campaignId;
ar.CallId = callId;
ar.Data = auditPointData.AsString();
WriteAuditRecord(ar);
}
}
My service code is started from a module level class that implements IOrchardShellEvents
public class Module : IOrchardShellEvents
{
private readonly IAuditWriter audit;
private readonly IRepository<ServiceSettingsPartRecord> settingsRepository;
private readonly IScheduledTaskManager taskManager;
private static readonly Object syncObject = new object();
public ILogger logger { get; set; }
public Module(IScheduledTaskManager taskManager, IRepository<ServiceSettingsPartRecord> settingsRepository, IAuditWriter audit)
{
this.audit = audit;
this.settingsRepository = settingsRepository;
this.taskManager = taskManager;
logger = NullLogger.Instance;
}
...
When the service is started during the "Activated" event, I pass this.Audit to the service instance.
public void Activated()
{
lock (syncObject)
{
var settings = settingsRepository.Fetch(f => f.StorageProvider != null).FirstOrDefault();
InitialiseServer();
// Auto start the server
if (!StartServer(settings))
{
// Auto start failed, setup a scheduled task to retry
var tasks = taskManager.GetTasks(ServerAutostartTask.TaskType);
if (tasks == null || tasks.Count() == 0)
taskManager.CreateTask(ServerAutostartTask.TaskType, DateTime.Now + TimeSpan.FromSeconds(60), null);
}
}
}
...
private void InitialiseServer()
{
if (!Server.IsInitialized)
{
var systemFolder = #"C:\Scratch\Plugins";
if (!Directory.Exists(systemFolder))
Directory.CreateDirectory(systemFolder);
var cacheFolder = System.Web.Hosting.HostingEnvironment.MapPath("~/App_Data/MyModule/Cache");
if (!Directory.Exists(cacheFolder))
Directory.CreateDirectory(cacheFolder);
Server.Initialise(systemFolder, cacheFolder, null, (FlowAuditSink)audit);
}
}
All of this works as expected and my service code calls the audit sink.
My problem is that when the audit sink is called and I try to write the audit to the database using this.repo.Create(data) nothing is written.
I have also attempted to create a new repository object by using the IComponentContext interface but this errors with object already disposed. I assume this is because the audit sink is a long lived object instance.
I have attempted both with and without the current transaction suspended which doesn't affect the result. I assume this is because the call is not coming through ASP.NET MVC but from a thread created by the service code.
Can anyone tell my how I can get my audit data to appear in the Orchard database?
Thanks
Chris.
Well, I have a solution, but as I'm not very familiar with Orchards architecture it may not be the best way.
After a good deal of delving into the Orchard sources it struck me that the crux of this issue can be summarised as
"how do I access the Orchard autofac injection mechanism from a thread that does not use the Http request pipeline".
I figured that this is what a scheduled task must do so I created a scheduled task and set a breakpoint in IScheduledTaskHandler.Process to discover how the task was executed. Looking at Orchard\Tasks\SweepGenerator.cs showed me the way.
I modified my AuditWriter thusly:
public interface IAuditWriter : ISingletonDependency
{
}
public class AuditWriter : FlowAuditSink, IAuditWriter
{
private readonly IWorkContextAccessor _workContextAccessor;
public AuditWriter(IWorkContextAccessor workContextAccessor)
{
_workContextAccessor = workContextAccessor;
}
public override void Audit(DateTime eventTime, AuditPoint auditPoint, Guid campaignId, Guid callId, IDictionary<String, Object> auditPointData)
{
// Add code here to write audit into the Orchard DB.
AuditRecord ar = new AuditRecord();
ar.AuditPoint = (int)auditPoint;
ar.EventTime = eventTime;
ar.CampaignId = campaignId;
ar.CallId = callId;
ar.Data = auditPointData.AsString();
using (var scope = _workContextAccessor.CreateWorkContextScope())
{
// resolve the manager and invoke it
var repo = scope.Resolve<IRepository<AuditRecord>>();
repo.Create(ar);
repo.Flush();
}
}
}
scope.Resolve works and my data is successfully written to the Orchard DB.
At the moment, I don't think my use of ISingletonDependency is working correctly as my constructor is only called when my module injects an AuditWriter instance in its constructor and it happens more than once.
Anyway it seems that to gain access to the Orchard autofac resolution mechanism from a non Http thread we use IWorkContextAccessor
Please let me know if this is not correct.
Is there a Connection Pool Manager available for RedisNativeClient? We are doing byte level operations and use RedisNativeClient instead of the RedisClient.
Here is the solution I implemented. RedisClient inherits RedisNativeClient so using PooledRedisClientManager and then casting the connection to RedisNativeClient works fine. It holds the same TCP socket.
P.S. I am using Dependency Injection so I keep the lifestyle of this helper class singleton.
//Lifestyle is singleton
public class RedisHelper:IRedisHelper
{
private readonly PooledRedisClientManager _poolManager;
public RedisHelper()
{
_poolManager = new PooledRedisClientManager("localhost:6379");
}
public void RedisSingleSet(string redisKey, byte[] redisValues)
{
using (var client = (RedisNativeClient)_poolManager.GetClient())
{
client.Set(redisKey, redisValues);
}
}
}
I want to get Multiple HashSet. There is
public HashSet<string> GetAllItemsFromSet (string setId){ ....}
I need
public HashSet<string>[] GetAllItemsFromSets (string[] setIds)
How?
The API doesn't exist on the RedisClient and there is no specific Redis server operation for this task so you have to add extend the Redis client yourself, which you can do easily with an Extension method, e.g:
public static class RedisClientExtensions {
public static HashSet<string>[] GetAllItemsFromSets(this IRedisClient client,
string[] setIds)
{
return setIds.Select(x => client.GetAllItemsFromSet(x)).ToArray();
}
}
I'm currently creating a system which in some cases, if the database is not available, uses a MSMQ instead. E.g. if the application (in one of the cases it's a wcf web service) starts, and the database is not available, all incoming requests should be written to the MSMQ. When the database is available again, the requests should be written to the db again.
I am using NHibernate and the session factory is wrapped by a singleton. This is what the service looks like:
try
{
// to database (just an example)
SessionProvider.Current.CurrentSession.Save...
}
catch(NHibernate.ADOException)
{
// to msmq
}
This setup works when the service is up and running for some time and the session factory has been build. When stopping the SQL server ADO exceptions are raised and things are written to the MSMQ properly.
Now my problem. If the database is not available BEFORE the service is started the first time, the session factory cannot be build and a TypeInitializationException is thrown. My singleton session provider is now broken. So when the database is running again, I somehow need a way to rebuild the session factory. Would I do that timer based? Like trying to rebuild it every 5 minutes? How can I 'reinstantiate' a singleton?
Here's an excerpt of the session provider pattern I am using:
public sealed class SessionProvider : ISessionProvider
{
private ISessionFactory sessionFactory;
private SessionProvider()
{
sessionFactory = new Configuration().Configure().BuildSessionFactory();
}
public static ISessionFactory SessionFactory
{
get
{
return Nested.SessionProvider.sessionFactory;
}
}
public static ISessionProvider Current
{
get
{
// TypeInitializationException is thrown when building session factory fails
return Nested.SessionProvider;
}
}
private class Nested
{
internal static readonly SessionProvider SessionProvider = new SessionProvider();
}
}
I suggest you change your SessionProvider like this:
...
private ISessionFactory sessionFactory;
private Configuration config;
private SessionProvider()
{
config= new Configuration();
config.Configure();
}
public static ISessionFactory SessionFactory
{
get
{
if(sessionFactory==null)
sessionFactory=config.BuildSessionFactory();
return Nested.SessionProvider.sessionFactory;
}
}
...