Scheduling job using Quartz.Net

Scheduling job using Quartz.Net

Quartz consists of jobs, triggers and schedulers. Job is task that needs to be executed and trigger is how the job will be executed. Scheduler schedules the jobs and jobs and triggers have to be registered with it.


  1. Nuget package: Quartz.Net

  2. Create job factory to create multiple jobs

  3.  1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    namespace Web.Models
    {
        public class JobFactory : IJobFactory
        {
            private readonly IServiceProvider _serviceProvider;
    
            public JobFactory(IServiceProvider serviceProvider)
            {
                _serviceProvider = serviceProvider;
            }
           
    
            public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
            {
                return _serviceProvider.GetService(bundle.JobDetail.JobType) as IJob;
            }
    
            public void ReturnJob(IJob job)
            {
                
            }
        }
    }

  4. Create JobSchedule which takes the type of job and cron expression

  5. 
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    namespace Web.Models
    {
        public class JobSchedule
        {
            public JobSchedule (Type jobType, string cron)
            {
                JobType = jobType;
                Cron = cron;
            }
    
            public Type JobType { get; set; }
            public string Cron { get; set; }
        }
    }

  6. Create a job DateTimeJob which writes DateTime to file  

  7.  1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    namespace Web.Models
    {
        [DisallowConcurrentExecution]
        public class DateTimeJob : IJob
        {
            private readonly ILogger<DateTimeJob> _logger;
    
            public DateTimeJob(ILogger<DateTimeJob> logger)
            {
                _logger = logger;
            }
            public Task Execute(IJobExecutionContext context)
            {
                _logger.LogInformation($"Time is : {DateTime.Now}");
                //using (StreamWriter sw = new StreamWriter(@"C:\Mrugjal-time.txt",true) )
                //{
                //    sw.Write(DateTime.Now.ToUniversalTime());
                //}
    
                return Task.CompletedTask;
                //return Task.FromResult(0);
            }
        }
    }
    

  8. Create JobScheduler and register job and trigger to it

  9.  1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    namespace Web.Models
    {
        public class JobScheduler
        {
            public static void Start()
            {
                IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler().Result;
                scheduler.Start();
    
                IJobDetail job = JobBuilder.Create<DateTimeJob>().Build();
    
                ITrigger trigger = TriggerBuilder.Create()
                    .WithIdentity("DateTimeJob", "DateTime")
                    .WithCronSchedule("0 1 * * * ?")
                    .StartAt(DateTime.Now.ToUniversalTime())
                    .WithPriority(1)
                    .Build();
    
                scheduler.ScheduleJob(job, trigger);
    
            }
        }
    }
    

  10. Create QuartzHostedService

  11.  1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    namespace Web.Services
    {
        public class QuartzHostedService: IHostedService
        {
            private readonly ISchedulerFactory _schedulerFactory;
            private readonly IJobFactory _jobFactory;
            private readonly IEnumerable<JobSchedule> _jobSchedule;
    
            public QuartzHostedService(
           ISchedulerFactory schedulerFactory,
           IJobFactory jobFactory,
           IEnumerable<JobSchedule> jobSchedule)
            {
                _schedulerFactory = schedulerFactory;
                _jobSchedule = jobSchedule;
                _jobFactory = jobFactory;
            }
    
            public IScheduler Scheduler { get; set; }
    
            public async Task StartAsync(CancellationToken cancellationToken)
            {
                Scheduler = await _schedulerFactory.GetScheduler(cancellationToken);
                Scheduler.JobFactory = _jobFactory;
    
                foreach (var jobSchedule in _jobSchedule)
                {
                    var job = CreateJob(jobSchedule);
    
                    var trigger = CreateTrigger(jobSchedule);
    
                    await Scheduler.ScheduleJob(job, trigger, cancellationToken);
                }
    
                await Scheduler.Start();
            }
    
            private static IJobDetail CreateJob(JobSchedule jobSchedule)
            {
                var jobType = jobSchedule.JobType;
                return JobBuilder
                    .Create(jobSchedule.JobType)
                    .WithIdentity(jobType.FullName)
                    .Build();
            }
    
            private static ITrigger CreateTrigger(JobSchedule jobSchedule)
            {
                return TriggerBuilder
                    .Create()
                    .WithIdentity(jobSchedule.JobType.FullName)
                    .WithCronSchedule(jobSchedule.Cron)
                    .WithDescription(jobSchedule.Cron)
                    .Build();
            }
    
            public async Task StopAsync(CancellationToken cancellationToken)
            {
                await Scheduler.Shutdown(cancellationToken);
            }
        }
    }
    

  12. Add below to Startup.cs/ConfigureServices

  13.  1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    public void ConfigureServices(IServiceCollection services)
            {        
    ...
                services.AddSingleton<IJobFactory, JobFactory>();
                services.AddSingleton<ISchedulerFactory, StdSchedulerFactory>();
    
                services.AddSingleton<DateTimeJob>();
    
                services.AddSingleton(new JobSchedule(jobType: typeof(DateTimeJob),  cron: "0 0/1 * * * ?"));
    
                services.AddHostedService<QuartzHostedService>();
    
            }
    

LinQ Queires

List of LinQ queries

  1. Aggregate:

  2. 1
    2
     var errors = Error.Aggregate("Error occured: ",
                                (current, error) => current + $"Code - {error.Code}, Detail - {error.Text}; ");
    
  3. Select Many:

  4. IEnumerable data;
                if (data.SelectMany(s => s.Category)
                                     .Where(si => si.SubCategory.Any(si => si.Selected)).Select(ti => ti.SubCategoryId)
                                     .Distinct().Count() > 1)
                {
                    //
                }
  5. For Each:

  6. Parallel.ForEach(Categories,
                  Category =>
                  {
                      Logger.Log(
                         Category.Id);
                  };
    
                parameters.ToList().ForEach(x => command.Parameters.Add(x));
    
    foreach (var node in xnode.Descendants("node1"))
     {
                    node.Nodes().ToList().ForEach(n => n.Remove());
     }
  7. Order By:

  8. data.OrderBy(s => s.date).ToArray();
  9. Join:

  10. $"{Error} (Description: {string.Join(", ", ErrorCodes?.Select(e => e.ToString()))}";
    
    var amount = from a in price.LineItems
                 join b in discount.LineItems on a.Id equals b.Id
                 where (a.amount ?? 0)) > b.amount)
                 select new { a, b };
  11. Select New:

  12.  return (from cat in result.Cast<dynamic>()
                        group cat by cat.Id
                               into bycat
                        let subcat =
                            from region in bycat
                            group region by region.Region
                                into byRegion
                            let subsubcat =
                                        from code in byRegion
                                    group code by code.Code
                                            into byCode
                                    select (string)byCode.Key
                            select new
                            {
                                Name = (string)byRegion.Key,
                                Codes =
                                            subsubcat.ToList()
                            }
                        select new Cat<string>
                        {
                            Name = byCat.Key,
                            Regions =
                                subcat.ToDictionary(r => r.Name, r => r.Codes,
                                    StringComparer.InvariantCultureIgnoreCase)
    
                        }).ToDictionary(c => c.Name, c => c, StringComparer.InvariantCultureIgnoreCase);
            }
    
            select new Info((new[]
                    {
                        cat.Select(lineItem => lineItem.LineItem).ToList()
                    })
                    .ToList()
                    .ForEach(Infos.Add);
  13. Group By:

  14. foreach (var grpCat in items.GroupBy(cat => cat.Id))
                {
                }
  15. TakeWhile

    1.  foreach (var cat in categories.TakeWhile(esi => categories == null))
        {
        }
  16. IEnumerable:

  17.  1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    using System.Collections.Generic;
    
    namespace Web.Models
    {
        public static class Class
        {
            IEnumerable<CatInfo> data;
            if (TryExecuteQuery(out data, "GetData", parameters))
            {
                info = data.ToList();
            }
        }
    }

.NET core logging [serilog]

Logging using Serilog

Per serilog:  Serilog provides diagnostic logging to files, the console, and elsewhere. It is easy to set up, has a clean API, and is portable between recent .NET platforms.


  1. Nuget package: Serilog.Extensions.Logging.File

  2. In Startup.cs, in Configure add ILoggerFactory loggerFactory and below code

  3.  1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
     // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
            public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
            {
                
                //Serilog.Extensions.Logging.File
                loggerFactory.AddFile("Logs/MyApp-{Date}.txt");
    
                // .NET logging
    
                loggerFactory = LoggerFactory.Create(builder => builder.AddFilter("Microsoft", LogLevel.Warning)
                .AddFilter("System", LogLevel.Warning)
                .AddFilter("LoggingConsoleApp.Program", LogLevel.Debug)
                .AddEventLog()
                .AddDebug());
            }
    

  4. Inject ILogger in your class and log as below

  5.  1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    public class UserProducer : IUserProducer
        {
            private readonly ILogger<UserProducer> _logger;
    
            public UserProducer()
            {
    
            }
            public  UserProducer (ILogger<UserProducer> logger)
            {
                _logger = logger;
            }
            public async void Produce(string message)
            {
                var config = new ProducerConfig { BootstrapServers = "localhost:9094" };
    
                using (var producer = new ProducerBuilder<Null, string>(config).Build())
                {
                    try
                    {
                        _logger.LogInformation($"Trying to deliver message {message}");
                        var result = await producer.ProduceAsync("send-message", new Message<Null, string> { Value = "hi" });
                        _logger.LogInformation($"Delivered message {result.Value} to {result.TopicPartitionOffset}");
                    }
                    catch (ProduceException<Null, string> e)
                    {
                        _logger.LogError($"Delivery of message failed {e.Error.Reason}");
                    }
                    catch (Exception ex)
                    {
                    }
                }
            }
        }
    

Update Dell m/c drivers

suspend-bitlocker -mountpoint c:

https://www.dell.com/support/home/us/en/04/product-support/servicetag/0-dTdhMlZHMHdsWVFwMFdTcm1YWit4dz090/drivers

Cron scheduler format

The Cron time string is seven values separated by spaces:


SequenceCharacterValuesNote
1Second0 to 59* for all values, ? for no specific value
2Minute0 to 59* for all values, ? for no specific value
3Hour0 to 23* for all values, ? for no specific value
4Day of Month1 to 31* for all values, ? for no specific value
5Month1 to 12* for all values, ? for no specific value
6Day of Week0 to 7* for all values, ? for no specific value
7Year--

Examples


Cron stringDescription
0 15 * * * *Execute at 15 minutes past the hour, every hour.
0 15 10 * * *Execute at 10:15 am everyday.
0 0 15 * * MONExecute at 1:00 p.m on Monday's.
0 */2 * * * *Execute every two minutes.
0 0 */5 * * *Execute every five hour, on the hour.
0 0/5 12,16,18 * * ?Execute every 5 minutes start at 12pm till 12:55pm, repeat same for 16 and 18th hour
0 10 12 01 11 ?Execute at 12:10 on 01'st of November
0 10 12 ? * 1#2Execute at 12:10 on the 2nd Monday

Url Rewrite [iis]

Web.Config file

Add below


<rule name="SSL Call">
  <match url=".*" />
  <conditions logicalGrouping="MatchAny" trackAllCaptures="false">
 <add input="{HTTPS}" pattern="^ON$" />
 <add input="{HTTP_X_FORWARDED_PROTO}" pattern="https" />
  </conditions>
  <serverVariables>
 <set name="SSL_REQUEST" value="true" />
  </serverVariables>
  <action type="None" />
</rule>
<rule name="SSLRequired" stopProcessing="true">
  <match url="(.*)" />
  <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
 <add input="{SSL_REQUEST}" pattern="true" negate="true" />
  </conditions>
  <action type="Redirect" url="https://{SERVER_NAME}/{R:1}" redirectType="Found" />
</rule>

Setup and test Kafka

Scenario:

Setup Kafka on windows and test the messaging.

Solution:

Kafka is a distributed streaming platform which allows for Publishing and subscribing to streams of records,
store streams and lets consumer process streams of records.

Kafka runs as a cluster on servers. The records are stored as topics, which consists of key - value pair with  timestamp.

  1. Install:
    1. JRE - >  Latest version from 
      1. https://www.oracle.com/technetwork/java/javase/downloads/jre8-downloads-2133155.html
      2. Control Panel -> System -> Advanced system settings -> Environment Variables - > System environment variables -> new variable -> "JAVA_HOME" as variable name and "C:\Program Files\Java\jre1.8.0_191\" as value. Edit path variable and add "%JAVA_HOME%\bin"
    2. Zookeeper
      1. http://zookeeper.apache.org/releases.html .. bin.tar file
      2. Go to ../conf folder and change  “zoo_sample.cfg” to  “zoo.cfg”
      3. Change dataDir to the folder you want like /data
    3. Kafka
      1. http://kafka.apache.org/downloads.html .. bin.tar file
      2. Go to ../config/server.properties, change log.dir to the folder you want like /kafka_logs
      3. To change the default port:9094 update the .\config\server.properties and add "port=9094"

  2. Run below commands to start Zookeeper and Kafka. Open new Command prompt for each of the commands.

  3. 
    
    Zookeeper;
    
    ..\bin\ zkserver
    
    Kafka Broker
    
    Kafka root -> .\bin\windows\kafka-server-start.bat .\config\server.properties
    
    Create topic
    
    Kafka root -> .\bin\windows>kafka-topics.bat --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test_1
    
    Producer
    
    Kafka root -> .bin\windows>kafka-console-producer.bat --broker-list localhost:9092 --topic test_1
    
    Consumer
    
    Kafka root -> .\bin\windows>kafka-console-consumer.bat --bootstrap-server localhost:9092 --topic test_1

  4. Got to Producer and type "Hello world" and then the message would be rendered on Consumer.

ASP.NET core Redis

Scenario:

Create a Sign on service to allow creation of users in our app. The web App calls this service to get all users. Cache the data into Redis and serve the next request from cache till it expires.

Solution:

You need the Redis server and then you need to get the .NET client API (like StackExchange.Redis).

  1. Install:
    1. Download from https://github.com/MicrosoftArchive/redis/releases
    2. Install as windows service using Redis-x64-3.0.504.msi. set port to 6379
    3. It is avialble on "127.0.0.1" and port "6379".

  2. Install Nuget package - StackExchange.Redis

  3. Add ICache.cs

  4.  1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    namespace Web.Services
    {
        public interface ICache<TValue>
        {
            bool TrySet(string key, TValue value);
    
            bool TryGet(string key, out TValue value);
    
            void Remove(string key);
    
            void RemoveAll();
        }
    }
    

  5. Add ICacheService.cs

  6. 1
    2
    3
    4
    5
    6
    7
    namespace Web.Services
    {
        public interface ICacheService
        {
            ICache<TValue> GetCache<TValue>(string name);
        }
    }
    

  7. Add RedisCacheService.cs

  8.   
     
     1
      2
      3
      4
      5
      6
      7
      8
      9
     10
     11
     12
     13
     14
     15
     16
     17
     18
     19
     20
     21
     22
     23
     24
     25
     26
     27
     28
     29
     30
     31
     32
     33
     34
     35
     36
     37
     38
     39
     40
     41
     42
     43
     44
     45
     46
     47
     48
     49
     50
     51
     52
     53
     54
     55
     56
     57
     58
     59
     60
     61
     62
     63
     64
     65
     66
     67
     68
     69
     70
     71
     72
     73
     74
     75
     76
     77
     78
     79
     80
     81
     82
     83
     84
     85
     86
     87
     88
     89
     90
     91
     92
     93
     94
     95
     96
     97
     98
     99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    using GrpcServer;
    using Microsoft.Extensions.Caching.Memory;
    using Microsoft.Extensions.Primitives;
    using StackExchange.Redis;
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Runtime.Serialization.Formatters.Binary;
    using System.Threading;
    
    namespace Web.Services
    {
        //Redis cache
        public class RedisCacheService : ICacheService
        {
            private static IConnectionMultiplexer _connectionMultiplexer;
            private static readonly Dictionary<string, object> _repo;
            private readonly ReaderWriterLockSlim Lock = new ReaderWriterLockSlim();
            public RedisCacheService(IConnectionMultiplexer connectionMultiplexer)
            {
                _connectionMultiplexer = connectionMultiplexer;
            }
    
            static RedisCacheService()
            {
                //create cache repo
                _repo = new Dictionary<string, object>();
            }
            public ICache<TValue> GetCache<TValue>(string name)
            {
                object cache = null;
    
                Lock.EnterUpgradeableReadLock();
                try
                {
                    Lock.EnterWriteLock();
                    if (!_repo.TryGetValue(name, out cache))
                    {
                        cache = new RedisCache<TValue>(_connectionMultiplexer);
                        _repo[name] = cache;
                    }
                }
                finally
                {
                    Lock.ExitWriteLock();
                    Lock.ExitUpgradeableReadLock();
                }
    
                return (ICache<TValue>)cache;
            }
        }
    
        public class RedisCache<TValue> : ICache<TValue>
        {
            private static IDatabase _cache;
            private static IConnectionMultiplexer _connectionMultiplexer;
    
            private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
    
            static RedisCache()
            {
            }
    
            public RedisCache(IConnectionMultiplexer connectionMultiplexer)
            {
                _connectionMultiplexer = connectionMultiplexer;
            }
    
            public void Remove(string key)
            {
                _lock.EnterWriteLock();
                try
                {
                    _cache.KeyDelete(key);
                }
                finally
                {
                    _lock.ExitWriteLock();
                }
            }
    
            public bool TryGet(string key, out TValue value)
            {
                _lock.EnterUpgradeableReadLock();
                try
                {
                    var result = _connectionMultiplexer.GetDatabase().StringGet(key);
                    
                    if (result.HasValue)
                       {
                        value = Deserialize<TValue>(result);
                        return true;
                    }
                }
                catch (Exception e)
                {
                    value = default(TValue);
                }
                finally
                {
                    _lock.ExitUpgradeableReadLock();
                }
                value = default(TValue);
                return false;
            }
    
            public bool TrySet(string key, TValue value)
            {
                _lock.EnterWriteLock();
    
                var result = _connectionMultiplexer.GetDatabase().StringGet(key);
    
                if (!result.HasValue)
                {
                    try
                    {
                        _connectionMultiplexer.GetDatabase().StringSet(key, Serialize(value), TimeSpan.FromSeconds(10));
                        return true;
                    }
                    catch (Exception e)
                    {
                        return false;
                    }
                    finally
                    {
                        _lock.ExitWriteLock();
                    }
                }
    
                return true;
            }
    
            public void RemoveAll()
            {
                _lock.EnterWriteLock();
                try
                {
                    _connectionMultiplexer.GetServer(ConfigurationManager.AppSetting["redis:server"]).FlushAllDatabases();
                }
                catch (Exception)
                {
                }
                finally
                {
                    _lock.ExitWriteLock();
                }
            }
    
            static byte[] Serialize(object o)
            {
                if (o == null)
                {
                    return null;
                }
    
                BinaryFormatter binaryFormatter = new BinaryFormatter();
                using (MemoryStream memoryStream = new MemoryStream())
                {
                    binaryFormatter.Serialize(memoryStream, o);
                    byte[] objectDataAsStream = memoryStream.ToArray();
                    return objectDataAsStream;
                }
            }
    
            static TValue Deserialize<TValue>(byte[] stream)
            {
                if (stream == null)
                {
                    return default(TValue);
                }
    
                BinaryFormatter binaryFormatter = new BinaryFormatter();
                using (MemoryStream memoryStream = new MemoryStream(stream))
                {
                    TValue result = (TValue)binaryFormatter.Deserialize(memoryStream);
                    return result;
                }
            }
        }
    
    

  9. In Startup.cs

  10. 1
    2
    3
    4
    5
    6
    7
                services.AddSingleton<ICacheService, RedisCacheService>();
                services.AddSingleton<ICache<object>, RedisCache<object>>();
    
                var options = ConfigurationOptions.Parse(ConfigurationManager.AppSetting["redis:server"]);
                options.AllowAdmin = true;
    
                services.AddSingleton<IConnectionMultiplexer>(ConnectionMultiplexer.Connect(options));
    
  11. Add HomeController.cs

  12.  1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
     public class HomeController : Controller
        {
    private ILogger _logger;
    private readonly ICache<List<string>> _userCache;
     public HomeController(ILoggerFactory loggerFactory, SignOn.SignOnClient signOnClient, ICacheService cacheService, IMemoryCache memoryCache)
            {
    _logger = loggerFactory.CreateLogger<InMemoryCacheService>();
                _userCache = cacheService.GetCache<List<string>>("cache_users");
                _cache = memoryCache;
    }
    private async Task<Tuple<bool, List<string>>> GetUsers()
            {
                var userlist = new List<string>();
                string name = string.Empty;
    
                var cachekey = CacheKeys.Users;
    
                _usersLock.EnterReadLock();
    
                try
                {
                    if (_userCache.TryGet(cachekey, out userlist))
                    {
                        return new Tuple<bool, List<string>>(true, userlist);
                    }
    
                    if (_userCache.TryGet(CacheKeys.CallbackMessage, out var message))
                    {
                        _logger.LogDebug(message.First());
                    }
                }
                finally
                {
                    _usersLock.ExitReadLock();
                }
    
                try
                {
                    _usersLock.EnterWriteLock();
    
                    using (var call = _signOnClient.GetAllUsers(new UserRequest()))
                    {
                        userlist = new List<string>();
                        while (await call.ResponseStream.MoveNext())
                        {
                            var user = call.ResponseStream.Current;
                            userlist.Add(user.Name);
                        }
                    }
    
                    if (_userCache.TrySet(cachekey,userlist))
                    {
                        return new Tuple<bool, List<string>>(true, userlist);
                    }
    
                    return new Tuple<bool, List<string>>(false, null);
                }
                catch (Exception)
                {
                    return new Tuple<bool, List<string>>(false, null);
                }
                finally
                {
                    _usersLock.ExitWriteLock();
                }
            }
     try
                {
                    if (!all)
                    {
                        _userCache.Remove(CacheKeys.Users);
                    }
                    else
                    {
                        _userCache.RemoveAll();
                    }
                }
                catch (Exception e)
                {
                    throw e;
                }
    
                return View();
            }
        }
    

ASP.NET core Memcached


Scenario:

Create a Sign on service to allow creation of users in our app. The web App calls this service to get all users. Cache the data into memcached and serve the next request from cache till it expires.

Solution:

You need the Memcached server and then you need to get the .NET client API (like easyCaching).

  1. Install:
    1. Download from http://memcached.org/
    2. command prompt -> C:\..\memcached -d install
    3. It is avialble on "127.0.0.1" and port "11211".

  2. Install Nuget package - EasyCaching.Memcached

  3. Add ICache.cs

  4.  1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    namespace Web.Services
    {
        public interface ICache<TValue>
        {
            bool TrySet(string key, TValue value);
    
            bool TryGet(string key, out TValue value);
    
            void Remove(string key);
    
            void RemoveAll();
        }
    }
    

  5. Add ICacheService.cs

  6. 1
    2
    3
    4
    5
    6
    7
    namespace Web.Services
    {
        public interface ICacheService
        {
            ICache<TValue> GetCache<TValue>(string name);
        }
    }
    

  7. Add MemCacheService.cs

  8.   
      1
      2
      3
      4
      5
      6
      7
      8
      9
     10
     11
     12
     13
     14
     15
     16
     17
     18
     19
     20
     21
     22
     23
     24
     25
     26
     27
     28
     29
     30
     31
     32
     33
     34
     35
     36
     37
     38
     39
     40
     41
     42
     43
     44
     45
     46
     47
     48
     49
     50
     51
     52
     53
     54
     55
     56
     57
     58
     59
     60
     61
     62
     63
     64
     65
     66
     67
     68
     69
     70
     71
     72
     73
     74
     75
     76
     77
     78
     79
     80
     81
     82
     83
     84
     85
     86
     87
     88
     89
     90
     91
     92
     93
     94
     95
     96
     97
     98
     99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    using EasyCaching.Core;
    using Microsoft.Extensions.Caching.Memory;
    using Microsoft.Extensions.Primitives;
    using System;
    using System.Collections.Generic;
    using System.Threading;
    
    namespace Web.Services
    {
        //InMemory cache
        public class MemCacheService : ICacheService
        {
            private static IEasyCachingProvider _easyCache;
            private static readonly Dictionary<string, object> _repo;
            private readonly ReaderWriterLockSlim Lock = new ReaderWriterLockSlim();
            public MemCacheService(IEasyCachingProvider easyCache)
            {
                _easyCache = easyCache;
            }
    
            static MemCacheService()
            {
                //create cache repo
                _repo = new Dictionary<string, object>();
            }
            public ICache<TValue> GetCache<TValue>(string name)
            {
                object cache = null;
    
                Lock.EnterUpgradeableReadLock();
                try
                {
                    Lock.EnterWriteLock();
                    if (!_repo.TryGetValue(name, out cache))
                    {
                        cache = new MemCache<TValue>(_easyCache);
                        _repo[name] = cache;
                    }
                }
                finally
                {
                    Lock.ExitWriteLock();
                    Lock.ExitUpgradeableReadLock();
                }
    
                return (ICache<TValue>)cache;
            }
        }
    
        public class MemCache<TValue> : ICache<TValue>
        {
            private static IEasyCachingProvider _cache;
            private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
    
            static MemCache()
            {
            }
    
            public MemCache(IEasyCachingProvider cache)
            {
                _cache = cache;
            }
    
            public void Remove(string key)
            {
                _lock.EnterWriteLock();
                try
                {
                    _cache.Remove(key);
                }
                finally
                {
                    _lock.ExitWriteLock();
                }
            }
    
            public bool TryGet(string key, out TValue value)
            {
                _lock.EnterUpgradeableReadLock();
                try
                {
                    value = _cache.Get<TValue>(key).Value;
    
                    if (value != null)
                    {
                        return true;
                    }
                }
                catch (Exception)
                {
                    value = default(TValue);
                }
                finally
                {
                    _lock.ExitUpgradeableReadLock();
                }
    
                return false;
            }
    
            public bool TrySet(string key, TValue value)
            {
                _lock.EnterWriteLock();
    
                var result = _cache.Get<TValue>(key).Value;
    
                if (result == null)
                {
                    try
                    {
                        _cache.Set<TValue>(key, value, TimeSpan.FromSeconds(10));
                        return true;
                    }
                    catch (Exception e)
                    {
                        return false;
                    }
                    finally
                    {
                        _lock.ExitWriteLock();
                    }
                }
    
                return true;
            }
    
            public void RemoveAll()
            {
                _lock.EnterWriteLock();
                try
                {
                    _cache.Flush();
                }
                catch (Exception)
                {
                }
                finally
                {
                    _lock.ExitWriteLock();
                }
            }
        }
    }
    
    

  9. In Startup.cs

  10. 1
    2
    3
    4
    5
    6
         public void ConfigureServices(IServiceCollection services)
              services.AddSingleton<ICacheService, MemCacheService>();
                services.AddSingleton<ICache<object>, MemCache<object>>();
    
                services.AddEasyCaching(option => option.UseMemcached(config => config.DBConfig.AddServer("127.0.0.1", 11211)));
            }

  11. Add HomeController.cs

  12.  1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
     public class HomeController : Controller
        {
    private ILogger _logger;
    private readonly ICache<List<string>> _userCache;
     public HomeController(ILoggerFactory loggerFactory, SignOn.SignOnClient signOnClient, ICacheService cacheService, IMemoryCache memoryCache)
            {
    _logger = loggerFactory.CreateLogger<InMemoryCacheService>();
                _userCache = cacheService.GetCache<List<string>>("cache_users");
                _cache = memoryCache;
    }
    private async Task<Tuple<bool, List<string>>> GetUsers()
            {
                var userlist = new List<string>();
                string name = string.Empty;
    
                var cachekey = CacheKeys.Users;
    
                _usersLock.EnterReadLock();
    
                try
                {
                    if (_userCache.TryGet(cachekey, out userlist))
                    {
                        return new Tuple<bool, List<string>>(true, userlist);
                    }
    
                    if (_userCache.TryGet(CacheKeys.CallbackMessage, out var message))
                    {
                        _logger.LogDebug(message.First());
                    }
                }
                finally
                {
                    _usersLock.ExitReadLock();
                }
    
                try
                {
                    _usersLock.EnterWriteLock();
    
                    using (var call = _signOnClient.GetAllUsers(new UserRequest()))
                    {
                        userlist = new List<string>();
                        while (await call.ResponseStream.MoveNext())
                        {
                            var user = call.ResponseStream.Current;
                            userlist.Add(user.Name);
                        }
                    }
    
                    if (_userCache.TrySet(cachekey,userlist))
                    {
                        return new Tuple<bool, List<string>>(true, userlist);
                    }
    
                    return new Tuple<bool, List<string>>(false, null);
                }
                catch (Exception)
                {
                    return new Tuple<bool, List<string>>(false, null);
                }
                finally
                {
                    _usersLock.ExitWriteLock();
                }
            }
     try
                {
                    if (!all)
                    {
                        _userCache.Remove(CacheKeys.Users);
                    }
                    else
                    {
                        _userCache.RemoveAll();
                    }
                }
                catch (Exception e)
                {
                    throw e;
                }
    
                return View();
            }
        }
    

    Notes

    • For Atomic operations Memcache d provides functions "getidentifiable()" which is called before you update cache and gets the current state and before updating it calls "putIfUntoched()", so if the 2 concurrent requests comes and the 2 nd one has already updated the value before the first the putIfUntoched() would not update the cache.
    • Difference between L1 and L2 cache
      • L1 is first and then L2 in the hierarchy.
      • L1 in-built in chip [SRAM], L2 is soldered to motherboard close to the chip [DRAM].L1 need refreshing, L2 does not.
      • Memory capacity: L1 < L2.
      • Access speed: L1 > L2.
      • L2 accessed only if requested data not in L1.

Move Github Sub Repository back to main repo

 -- delete .gitmodules git rm --cached MyProject/Core git commit -m 'Remove myproject_core submodule' rm -rf MyProject/Core git remo...