├── SmartCacheManager.Demo
├── Views
│ ├── _ViewStart.cshtml
│ ├── _ViewImports.cshtml
│ ├── Home
│ │ ├── Privacy.cshtml
│ │ └── Index.cshtml
│ └── Shared
│ │ ├── _ValidationScriptsPartial.cshtml
│ │ ├── Error.cshtml
│ │ └── _Layout.cshtml
├── wwwroot
│ ├── favicon.ico
│ ├── js
│ │ └── site.js
│ ├── lib
│ │ ├── jquery-validation-unobtrusive
│ │ │ ├── LICENSE.txt
│ │ │ └── jquery.validate.unobtrusive.min.js
│ │ ├── jquery-validation
│ │ │ └── LICENSE.md
│ │ ├── bootstrap
│ │ │ ├── LICENSE
│ │ │ └── dist
│ │ │ │ └── css
│ │ │ │ ├── bootstrap-reboot.min.css
│ │ │ │ └── bootstrap-reboot.css
│ │ └── jquery
│ │ │ └── LICENSE.txt
│ └── css
│ │ └── site.css
├── appsettings.Development.json
├── Models
│ ├── FlightSearchResult.cs
│ ├── ErrorViewModel.cs
│ └── FlightCacheManager.cs
├── appsettings.json
├── SmartCacheManager.Demo.csproj
├── Program.cs
├── Properties
│ └── launchSettings.json
├── Startup.cs
└── Controllers
│ └── HomeController.cs
├── .gitlab-ci.yml
├── SmartCacheManager
├── FodyWeavers.xml
├── Caching
│ ├── EasyCaching
│ │ ├── CachingProviderType.cs
│ │ ├── CompressionType.cs
│ │ ├── EasyCachingOptions.cs
│ │ └── EasyCachingConfigurationExtensions.cs
│ └── ICacheManager.cs
├── Data
│ ├── DbContext
│ │ ├── SmartCacheManagerDbContext.cs
│ │ └── ModelBuilderExtensions.cs
│ ├── Models
│ │ ├── OutgoingRequest.cs
│ │ ├── IncomingRequest.cs
│ │ ├── LimitSetting.cs
│ │ └── CacheSetting.cs
│ ├── DataConfigurationExtensions.cs
│ └── Store
│ │ └── IGenericStore.cs
├── Logging
│ ├── ILoggerFactory.cs
│ ├── MethodTimeLogger.cs
│ ├── SerilogLogger
│ │ ├── SerilogLoggerFactory.cs
│ │ ├── SerilogOptions.cs
│ │ └── SerilogLogger.cs
│ ├── LogProperty.cs
│ ├── LogLevel.cs
│ ├── LogConstants.cs
│ └── ILogger.cs
├── Services
│ ├── Contracts
│ │ ├── ICacheSettingService.cs
│ │ ├── ILimitSettingService.cs
│ │ └── ISearchHistoryService.cs
│ ├── ServiceConfigurationExtensions.cs
│ ├── CacheSettingService.cs
│ ├── LimitSettingService.cs
│ ├── CacheSearchHistoryService.cs
│ └── DatabaseSearchHistoryService.cs
├── FodyWeavers.xsd
├── Utilities
│ ├── SystemClock.cs
│ ├── EnumHelper.cs
│ ├── Check.cs
│ ├── SerilogExtensions.cs
│ ├── CommonHelper.cs
│ ├── AsyncLock.cs
│ ├── ObjectComparer.cs
│ └── SqlHelper.cs
├── SmartCacheManager.csproj
└── ConfigurationExtensions.cs
├── SmartCacheManager.Tests
├── SmartCacheManager.Tests.csproj
├── TestBase.cs
├── SmartCacheManagerTests.cs
└── FlightCacheManager.cs
├── .github
└── workflows
│ └── dotnet-core.yml
├── SmartCacheManager.sln
├── .gitattributes
├── .gitignore
└── README.md
/SmartCacheManager.Demo/Views/_ViewStart.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | Layout = "_Layout";
3 | }
4 |
--------------------------------------------------------------------------------
/SmartCacheManager.Demo/wwwroot/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mjebrahimi/SmartCacheManager/HEAD/SmartCacheManager.Demo/wwwroot/favicon.ico
--------------------------------------------------------------------------------
/.gitlab-ci.yml:
--------------------------------------------------------------------------------
1 | image: mcr.microsoft.com/dotnet/core/sdk:3.1
2 |
3 | stages:
4 | - build
5 |
6 | stage_build:
7 | stage: build
8 | script:
9 | - "dotnet build"
--------------------------------------------------------------------------------
/SmartCacheManager.Demo/Views/_ViewImports.cshtml:
--------------------------------------------------------------------------------
1 | @using SmartCacheManager.Demo
2 | @using SmartCacheManager.Demo.Models
3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
4 |
--------------------------------------------------------------------------------
/SmartCacheManager/FodyWeavers.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/SmartCacheManager.Demo/Views/Home/Privacy.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | ViewData["Title"] = "Privacy Policy";
3 | }
4 |
@ViewData["Title"]
5 |
6 | Use this page to detail your site's privacy policy.
7 |
--------------------------------------------------------------------------------
/SmartCacheManager.Demo/Views/Shared/_ValidationScriptsPartial.cshtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/SmartCacheManager.Demo/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft": "Warning",
6 | "Microsoft.Hosting.Lifetime": "Information"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/SmartCacheManager.Demo/Models/FlightSearchResult.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace SmartCacheManager.Demo.Models
4 | {
5 | public class FlightSearchResult
6 | {
7 | public DateTime Now { get; set; } = DateTime.Now;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/SmartCacheManager.Demo/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft": "Warning",
6 | "Microsoft.Hosting.Lifetime": "Information"
7 | }
8 | },
9 | "AllowedHosts": "*"
10 | }
11 |
--------------------------------------------------------------------------------
/SmartCacheManager.Demo/wwwroot/js/site.js:
--------------------------------------------------------------------------------
1 | // Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification
2 | // for details on configuring this project to bundle and minify static web assets.
3 |
4 | // Write your JavaScript code.
5 |
--------------------------------------------------------------------------------
/SmartCacheManager.Demo/Models/ErrorViewModel.cs:
--------------------------------------------------------------------------------
1 | namespace SmartCacheManager.Demo.Models
2 | {
3 | public class ErrorViewModel
4 | {
5 | public string RequestId { get; set; }
6 |
7 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/SmartCacheManager/Caching/EasyCaching/CachingProviderType.cs:
--------------------------------------------------------------------------------
1 | namespace SmartCacheManager.Caching.EasyCaching
2 | {
3 | ///
4 | /// Caching provider type.
5 | ///
6 | public enum CachingProviderType
7 | {
8 | InMemory,
9 | Redis,
10 | Disabled
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/SmartCacheManager/Caching/EasyCaching/CompressionType.cs:
--------------------------------------------------------------------------------
1 | namespace SmartCacheManager.Caching.EasyCaching
2 | {
3 | ///
4 | /// Compression type
5 | ///
6 | public enum CompressionType
7 | {
8 | Disabled,
9 | Brotli,
10 | GZip,
11 | Deflate,
12 | LZ4
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/SmartCacheManager.Demo/SmartCacheManager.Demo.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net5.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/SmartCacheManager.Demo/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Hosting;
2 | using Microsoft.Extensions.Hosting;
3 |
4 | namespace SmartCacheManager.Demo
5 | {
6 | public class Program
7 | {
8 | public static void Main(string[] args)
9 | {
10 | CreateHostBuilder(args).Build().Run();
11 | }
12 |
13 | public static IHostBuilder CreateHostBuilder(string[] args) =>
14 | Host.CreateDefaultBuilder(args)
15 | .ConfigureWebHostDefaults(webBuilder =>
16 | {
17 | webBuilder.UseStartup();
18 | });
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SmartCacheManager/Data/DbContext/SmartCacheManagerDbContext.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.EntityFrameworkCore;
2 |
3 | namespace SmartCacheManager.Data
4 | {
5 | ///
6 | /// DbContext for caching models
7 | ///
8 | public class SmartCacheManagerDbContext : DbContext
9 | {
10 | public SmartCacheManagerDbContext(DbContextOptions options)
11 | : base(options)
12 | {
13 | }
14 |
15 | protected override void OnModelCreating(ModelBuilder modelBuilder)
16 | {
17 | modelBuilder.AddCacheModels();
18 |
19 | base.OnModelCreating(modelBuilder);
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/SmartCacheManager.Demo/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) .NET Foundation. All rights reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use
4 | these files except in compliance with the License. You may obtain a copy of the
5 | License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software distributed
10 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
11 | CONDITIONS OF ANY KIND, either express or implied. See the License for the
12 | specific language governing permissions and limitations under the License.
13 |
--------------------------------------------------------------------------------
/SmartCacheManager.Tests/SmartCacheManager.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net5.0
5 | false
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/SmartCacheManager/Data/Models/OutgoingRequest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel.DataAnnotations.Schema;
3 |
4 | namespace SmartCacheManager.Data
5 | {
6 | ///
7 | /// Represent an outgoing request of search history
8 | ///
9 | public partial class OutgoingRequest
10 | {
11 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
12 | public int Id { get; set; }
13 |
14 | ///
15 | /// DateTime of creation
16 | ///
17 | public DateTime CreatedAt { get; set; } //CreatedAt
18 |
19 | ///
20 | /// Hash for indexing outgoing requests based on supplier type for limitation
21 | ///
22 | public long HashCode { get; set; }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/SmartCacheManager.Demo/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "iisSettings": {
3 | "windowsAuthentication": false,
4 | "anonymousAuthentication": true,
5 | "iisExpress": {
6 | "applicationUrl": "http://localhost:1961",
7 | "sslPort": 44303
8 | }
9 | },
10 | "profiles": {
11 | "IIS Express": {
12 | "commandName": "IISExpress",
13 | "launchBrowser": true,
14 | "environmentVariables": {
15 | "ASPNETCORE_ENVIRONMENT": "Development"
16 | }
17 | },
18 | "SmartCacheManager.Demo": {
19 | "commandName": "Project",
20 | "launchBrowser": true,
21 | "applicationUrl": "https://localhost:5001;http://localhost:5000",
22 | "environmentVariables": {
23 | "ASPNETCORE_ENVIRONMENT": "Development"
24 | }
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/SmartCacheManager/Logging/ILoggerFactory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace SmartCacheManager.Logging
4 | {
5 | ///
6 | /// Logger factory abstraction
7 | ///
8 | public interface ILoggerFactory
9 | {
10 | ///
11 | /// Create a logger that marks logs as being from the specified source type.
12 | ///
13 | /// Type of source context
14 | /// ILogger
15 | ILogger CreateLogger(Type source);
16 |
17 | ///
18 | /// Create a logger that marks logs as being from the specified source type.
19 | ///
20 | /// Type of source context
21 | /// ILogger
22 | ILogger CreateLogger();
23 | }
24 | }
--------------------------------------------------------------------------------
/SmartCacheManager.Demo/Views/Shared/Error.cshtml:
--------------------------------------------------------------------------------
1 | @model ErrorViewModel
2 | @{
3 | ViewData["Title"] = "Error";
4 | }
5 |
6 | Error.
7 | An error occurred while processing your request.
8 |
9 | @if (Model.ShowRequestId)
10 | {
11 |
12 | Request ID: @Model.RequestId
13 |
14 | }
15 |
16 | Development Mode
17 |
18 | Swapping to Development environment will display more detailed information about the error that occurred.
19 |
20 |
21 | The Development environment shouldn't be enabled for deployed applications.
22 | It can result in displaying sensitive information from exceptions to end users.
23 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development
24 | and restarting the app.
25 |
26 |
--------------------------------------------------------------------------------
/SmartCacheManager/Services/Contracts/ICacheSettingService.cs:
--------------------------------------------------------------------------------
1 | using SmartCacheManager.Data;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 |
5 | namespace SmartCacheManager.Services
6 | {
7 | ///
8 | /// Service for CacheSetting
9 | ///
10 | /// Type of CacheSetting
11 | public interface ICacheSettingService
12 | where TCacheSetting : CacheSetting, new()
13 | {
14 | ///
15 | /// Get CacheSetting from cache by specified SupplierType or if not exists create a global CacheSetting
16 | ///
17 | /// Type of supplier
18 | /// cancellationToken
19 | /// TCacheSetting
20 | Task GetFromCacheBySupplierTypeAsync(TSupplierType supplierType, CancellationToken cancellationToken = default);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/SmartCacheManager/Services/Contracts/ILimitSettingService.cs:
--------------------------------------------------------------------------------
1 | using SmartCacheManager.Data;
2 | using System.Collections.Generic;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 |
6 | namespace SmartCacheManager.Services
7 | {
8 | ///
9 | /// Service for LimitSetting
10 | ///
11 | /// Type of LimitSetting
12 | public interface ILimitSettingService
13 | where TLimitSetting : LimitSetting, new()
14 | {
15 | ///
16 | /// Get LimitSetting from cache by specified SupplierType or if not exists create a global LimitSetting
17 | ///
18 | /// Type of supplier
19 | /// cancellationToken
20 | /// List of TLimitSetting
21 | Task> GetFromCacheBySupplierTypeAsync(TSupplierType supplierType, CancellationToken cancellationToken = default);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/SmartCacheManager/Data/Models/IncomingRequest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel.DataAnnotations.Schema;
3 |
4 | namespace SmartCacheManager.Data
5 | {
6 | #region TODO
7 | //========= R&D to choose the following scenarios =========
8 | //Clasterd or non-clasterd indexing ?
9 | //AlternateKey or HasIndex multiple column or IncludeProperties ? https://docs.microsoft.com/en-us/ef/core/modeling/indexes
10 | //Datetime or Ticks for CreatedAt ?
11 | #endregion
12 |
13 | ///
14 | /// Represent an incomming request of search history
15 | ///
16 | public partial class IncomingRequest
17 | {
18 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
19 | public int Id { get; set; }
20 |
21 | ///
22 | /// DateTime of creation
23 | ///
24 | public DateTime CreatedAt { get; set; } //CreatedAt
25 |
26 | ///
27 | /// Hash for indexing incomming requests based on search model for RPM
28 | ///
29 | public long HashCode { get; set; }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/SmartCacheManager.Demo/Views/Home/Index.cshtml:
--------------------------------------------------------------------------------
1 | @model SmartCacheManager.Demo.Controllers.IndexViewModel
2 | @{
3 | ViewData["Title"] = "Home Page";
4 | }
5 |
6 |
7 |
8 | @Model.FlightSearchResult?.Now.ToString("yyyy/MM/dd HH:mm:ss")
9 |
10 |
Flush Cache Data (Admin Mode Required)
11 |
12 |
13 |
14 |
15 |
16 |
17 | Current RPM
18 | Calculated Cache Minutes
19 | Existed Cache Minutes
20 |
21 |
22 |
23 | @foreach (var item in Model.CacheDetails)
24 | {
25 |
26 | @item.CurrentRPM
27 | @item.CalculatedCacheMinutes
28 | (@Convert.ToInt32(item.ExistedCacheMinutes.TotalMinutes)) @item.ExistedCacheMinutes.ToString("hh\\:mm\\:ss")
29 |
30 | }
31 |
32 |
33 |
--------------------------------------------------------------------------------
/SmartCacheManager.Demo/wwwroot/lib/jquery-validation/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | =====================
3 |
4 | Copyright Jörn Zaefferer
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/SmartCacheManager/Data/DbContext/ModelBuilderExtensions.cs:
--------------------------------------------------------------------------------
1 | using SmartCacheManager.Utilities;
2 | using Microsoft.EntityFrameworkCore;
3 |
4 | namespace SmartCacheManager.Data
5 | {
6 | public static class ModelBuilderExtensions
7 | {
8 | ///
9 | /// Add caching models to model builder
10 | ///
11 | /// modelBuilder
12 | public static void AddCacheModels(this ModelBuilder modelBuilder)
13 | {
14 | modelBuilder.NotNull(nameof(modelBuilder));
15 |
16 | //Register base types
17 | modelBuilder.Entity();
18 | modelBuilder.Entity();
19 | modelBuilder.Entity(typeBuilder =>
20 | {
21 | //typeBuilder.HasIndex(p => p.CreatedAt);
22 | //typeBuilder.HasIndex(p => p.HashCode);
23 | typeBuilder.HasIndex(p => new { p.HashCode, p.CreatedAt });
24 | });
25 | modelBuilder.Entity(typeBuilder =>
26 | {
27 | typeBuilder.HasIndex(p => new { p.HashCode, p.CreatedAt });
28 | });
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/SmartCacheManager/Logging/MethodTimeLogger.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 |
3 | namespace SmartCacheManager.Logging
4 | {
5 | public static class MethodTimeLogger
6 | {
7 | public static ILogger Logger { get; set; }
8 |
9 | public static void Log(MethodBase methodBase, long milliseconds, string message)
10 | {
11 | #if RELEASE
12 | if (!LogConstants.LogTimingEnabled) return;
13 |
14 | var typeName = methodBase.DeclaringType.Name;
15 | var methodName = $"{typeName.Remove(typeName.IndexOf('`'))}.{methodBase.Name}";
16 | var methodHashCode = Utilities.CommonHelper.GetInvariantHashCode(methodName);
17 |
18 | var logProperties = new[]
19 | {
20 | new LogProperty(LogConstants.LogTiming, null),
21 | new LogProperty(LogConstants.MethodName, methodName),
22 | new LogProperty(LogConstants.ElapsedMilliSeconds, milliseconds),
23 | new LogProperty(LogConstants.MethodHashCode, methodHashCode),
24 | };
25 |
26 | Logger.Log(LogLevel.Information, null, $"The \"{methodName}\" method elapsed in {milliseconds} MS.", null, logProperties);
27 | #endif
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/SmartCacheManager.Demo/wwwroot/lib/bootstrap/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2011-2018 Twitter, Inc.
4 | Copyright (c) 2011-2018 The Bootstrap Authors
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/SmartCacheManager/FodyWeavers.xsd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.
12 |
13 |
14 |
15 |
16 | A comma-separated list of error codes that can be safely ignored in assembly verification.
17 |
18 |
19 |
20 |
21 | 'false' to turn off automatic generation of the XML Schema file.
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/SmartCacheManager/Utilities/SystemClock.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.DependencyInjection;
2 | using Microsoft.Extensions.DependencyInjection.Extensions;
3 | using System;
4 |
5 | namespace SmartCacheManager.Utilities
6 | {
7 | ///
8 | /// Abstracts the system clock to facilitate testing.
9 | ///
10 | public interface ISystemClock
11 | {
12 | DateTime DateTimeNow { get; }
13 | DateTimeOffset DateTimeOffsetNow { get; }
14 | }
15 |
16 | ///
17 | /// Default implementation for ISystemClock (return DateTime.Now)
18 | ///
19 | public class SystemClock : ISystemClock
20 | {
21 | public DateTime DateTimeNow => DateTime.Now; //or DateTime.UtcNow;
22 | public DateTimeOffset DateTimeOffsetNow => DateTimeOffset.Now;
23 | }
24 |
25 | public static class SystemClockConfigurationExtension
26 | {
27 | ///
28 | /// Add DefaultSystemClock as ISystemClock to specified IServiceCollection
29 | ///
30 | /// services
31 | /// IServiceCollection
32 | public static IServiceCollection AddSystemClock(this IServiceCollection services)
33 | {
34 | services.TryAddSingleton();
35 | return services;
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/SmartCacheManager.Tests/TestBase.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Builder.Internal;
2 | using Microsoft.EntityFrameworkCore;
3 | using Microsoft.Extensions.DependencyInjection;
4 | using NUnit.Framework;
5 | using SmartCacheManager.Caching;
6 | using SmartCacheManager.Caching.EasyCaching;
7 | using System;
8 | using System.Threading.Tasks;
9 |
10 | namespace SmartCacheManager.Tests
11 | {
12 | public class TestBase
13 | {
14 | protected IServiceProvider ServiceProvider;
15 | protected IServiceCollection Services;
16 |
17 | [SetUp]
18 | public void Setup()
19 | {
20 | var services = new ServiceCollection();
21 |
22 | services.AddSmartCacheManager(
23 | opt => opt.UseInMemoryDatabase("CacheManagerDb"),
24 | opt => opt.ProviderType = CachingProviderType.Redis,
25 | opt => opt.EnableSqlLog = false);
26 |
27 | services.AddScoped();
28 |
29 | Services = services;
30 | ServiceProvider = services.BuildServiceProvider();
31 |
32 | var app = new ApplicationBuilder(ServiceProvider);
33 | app.InitializeSmartCacheManager();
34 | }
35 |
36 | [TearDown]
37 | public async Task TearDown()
38 | {
39 | var cacheManager = ServiceProvider.GetRequiredService();
40 | await cacheManager.RemoveByPrefixAsync("SmartCacheManager");
41 | ((IDisposable)ServiceProvider).Dispose();
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/SmartCacheManager/Utilities/EnumHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Concurrent;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 |
6 | namespace SmartCacheManager.Utilities
7 | {
8 | public static class EnumHelper
9 | {
10 | private static readonly ConcurrentDictionary FlagableEnums = new ConcurrentDictionary();
11 |
12 | ///
13 | /// Determines whether enum is flagable
14 | ///
15 | /// Type of enum
16 | /// True if enum is flagable
17 | public static bool IsFlagable() where TEnum : Enum
18 | {
19 | return FlagableEnums.GetOrAdd(typeof(TEnum), type =>
20 | {
21 | return Enum.GetValues(type).Cast()
22 | .Select(p => Convert.ToInt32(p))
23 | .All(IsBinarySequence);
24 | });
25 |
26 | bool IsBinarySequence(int i) => (i & (i - 1)) == 0;
27 | }
28 |
29 | ///
30 | /// Retrieves an array of the flaged (HasFlag) values of the specified enum TEnum
31 | ///
32 | /// Type of enumeration
33 | /// List of enum vlaues
34 | public static List GetFlagedValues(this TEnum @enum) where TEnum : Enum
35 | {
36 | return Enum.GetValues(typeof(TEnum)).Cast().Where(p => @enum.HasFlag(p)).ToList();
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/.github/workflows/dotnet-core.yml:
--------------------------------------------------------------------------------
1 | name: .NET Core
2 |
3 | on:
4 | push:
5 | paths-ignore:
6 | - 'README.md'
7 | pull_request:
8 | paths-ignore:
9 | - 'README.md'
10 |
11 | jobs:
12 | build:
13 |
14 | runs-on: ubuntu-latest
15 |
16 | steps:
17 | - name: Checkout
18 | uses: actions/checkout@v2.3.4
19 |
20 | - name: Setup .NET Core 3.1
21 | uses: actions/setup-dotnet@v1.7.2
22 | with:
23 | dotnet-version: 3.1.101
24 |
25 | - name: Setup .NET Core 5.0
26 | uses: actions/setup-dotnet@v1.7.2
27 | with:
28 | dotnet-version: 5.0.100
29 |
30 | - name: Redis Server
31 | uses: supercharge/redis-github-action@1.1.0
32 |
33 | - name: Install Dependencies
34 | run: dotnet restore
35 |
36 | - name: Build (Release)
37 | run: dotnet build --configuration Release --no-restore
38 |
39 | - name: Test (Release)
40 | run: dotnet test --configuration Release --no-build
41 |
42 | - name: Pack (Release)
43 | run: dotnet pack SmartCacheManager --configuration Release
44 |
45 | - name: Publish Nuget Packages
46 | if: github.event_name == 'push'
47 | run: |
48 | if [[ ${{github.ref}} =~ ^refs/tags/[0-9]+\.[0-9]+\.[0-9]+$ ]]
49 | then
50 | dotnet nuget push SmartCacheManager/bin/Release/*.nupkg -s nuget.org -k ${{secrets.NUGET_TOKEN}} --no-symbols true
51 | else
52 | echo "publish is only enabled by tagging with a release tag"
53 | fi
54 |
--------------------------------------------------------------------------------
/SmartCacheManager.Demo/wwwroot/css/site.css:
--------------------------------------------------------------------------------
1 | /* Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification
2 | for details on configuring this project to bundle and minify static web assets. */
3 |
4 | a.navbar-brand {
5 | white-space: normal;
6 | text-align: center;
7 | word-break: break-all;
8 | }
9 |
10 | /* Provide sufficient contrast against white background */
11 | a {
12 | color: #0366d6;
13 | }
14 |
15 | .btn-primary {
16 | color: #fff;
17 | background-color: #1b6ec2;
18 | border-color: #1861ac;
19 | }
20 |
21 | .nav-pills .nav-link.active, .nav-pills .show > .nav-link {
22 | color: #fff;
23 | background-color: #1b6ec2;
24 | border-color: #1861ac;
25 | }
26 |
27 | /* Sticky footer styles
28 | -------------------------------------------------- */
29 | html {
30 | font-size: 14px;
31 | }
32 | @media (min-width: 768px) {
33 | html {
34 | font-size: 16px;
35 | }
36 | }
37 |
38 | .border-top {
39 | border-top: 1px solid #e5e5e5;
40 | }
41 | .border-bottom {
42 | border-bottom: 1px solid #e5e5e5;
43 | }
44 |
45 | .box-shadow {
46 | box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
47 | }
48 |
49 | button.accept-policy {
50 | font-size: 1rem;
51 | line-height: inherit;
52 | }
53 |
54 | /* Sticky footer styles
55 | -------------------------------------------------- */
56 | html {
57 | position: relative;
58 | min-height: 100%;
59 | }
60 |
61 | body {
62 | /* Margin bottom by footer height */
63 | margin-bottom: 60px;
64 | }
65 | .footer {
66 | position: absolute;
67 | bottom: 0;
68 | width: 100%;
69 | white-space: nowrap;
70 | line-height: 60px; /* Vertically center the text there */
71 | }
72 |
--------------------------------------------------------------------------------
/SmartCacheManager/Caching/EasyCaching/EasyCachingOptions.cs:
--------------------------------------------------------------------------------
1 | namespace SmartCacheManager.Caching.EasyCaching
2 | {
3 | ///
4 | /// Options to configure cache manager
5 | ///
6 | public class EasyCachingOptions
7 | {
8 | ///
9 | /// Get or set caching provider type. Default is CachingProviderType.Redis
10 | ///
11 | public CachingProviderType ProviderType { get; set; } = CachingProviderType.Redis;
12 |
13 | ///
14 | /// Get or set redis host. Default is '127.0.0.1'
15 | ///
16 | public string RedisHost { get; set; } = "127.0.0.1";
17 |
18 | ///
19 | /// Get or set redis port. Default is 6379
20 | ///
21 | public int RedisPort { get; set; } = 6379;
22 |
23 | ///
24 | /// Indicates ADMIN mode of redis (required for redis FLUSHDB command)
25 | ///
26 | public bool RedisAllowAdmin { get; set; } = true;
27 |
28 | ///
29 | /// Get or set default cache expiration time in minutes. Default is 15
30 | ///
31 | public int DefaultCacheMinutes { get; set; } = 15;
32 |
33 | ///
34 | /// Gets or sets a value indicating whether enable logging. Default is false
35 | ///
36 | public bool EnableLogging { get; set; } = false;
37 |
38 | ///
39 | /// Gets or sets compression type. Default is CompressionType.LZ4
40 | ///
41 | public CompressionType CompressionType { get; set; } = CompressionType.LZ4;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/SmartCacheManager/Logging/SerilogLogger/SerilogLoggerFactory.cs:
--------------------------------------------------------------------------------
1 | using SmartCacheManager.Utilities;
2 | using Serilog.Extensions.Hosting;
3 | using System;
4 |
5 | namespace SmartCacheManager.Logging.Serilog
6 | {
7 | ///
8 | /// Serilog implementation of ILoggerFactory
9 | ///
10 | public class SerilogLoggerFactory : ILoggerFactory
11 | {
12 | private readonly global::Serilog.ILogger _logger;
13 | private readonly DiagnosticContext _diagnosticContext;
14 |
15 | public SerilogLoggerFactory(global::Serilog.ILogger logger, DiagnosticContext diagnosticContext)
16 | {
17 | _diagnosticContext = diagnosticContext.NotNull(nameof(diagnosticContext));
18 | _logger = logger.NotNull(nameof(logger));
19 | }
20 |
21 | ///
22 | /// Create a logger that marks logs as being from the specified source type.
23 | ///
24 | /// Type of source context
25 | /// ILogger
26 | public ILogger CreateLogger(Type source)
27 | {
28 | var logger = _logger.ForContext(source);
29 | return new SerilogLogger(logger, _diagnosticContext);
30 | }
31 |
32 | ///
33 | /// Create a logger that marks logs as being from the specified source type.
34 | ///
35 | /// Type of source context
36 | /// ILogger
37 | public ILogger CreateLogger()
38 | {
39 | return new SerilogLogger(_logger, _diagnosticContext);
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/SmartCacheManager.Demo/wwwroot/lib/jquery/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright JS Foundation and other contributors, https://js.foundation/
2 |
3 | This software consists of voluntary contributions made by many
4 | individuals. For exact contribution history, see the revision history
5 | available at https://github.com/jquery/jquery
6 |
7 | The following license applies to all parts of this software except as
8 | documented below:
9 |
10 | ====
11 |
12 | Permission is hereby granted, free of charge, to any person obtaining
13 | a copy of this software and associated documentation files (the
14 | "Software"), to deal in the Software without restriction, including
15 | without limitation the rights to use, copy, modify, merge, publish,
16 | distribute, sublicense, and/or sell copies of the Software, and to
17 | permit persons to whom the Software is furnished to do so, subject to
18 | the following conditions:
19 |
20 | The above copyright notice and this permission notice shall be
21 | included in all copies or substantial portions of the Software.
22 |
23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 |
31 | ====
32 |
33 | All files located in the node_modules and external directories are
34 | externally maintained libraries used by this software which have their
35 | own licenses; we recommend you read them, as their terms may differ from
36 | the terms above.
37 |
--------------------------------------------------------------------------------
/SmartCacheManager/Logging/LogProperty.cs:
--------------------------------------------------------------------------------
1 | using SmartCacheManager.Utilities;
2 |
3 | namespace SmartCacheManager.Logging
4 | {
5 | ///
6 | /// Represent a log property.
7 | ///
8 | public class LogProperty
9 | {
10 | ///
11 | /// Construct a LogProperty.
12 | ///
13 | /// The name of the property. Must be non-empty.
14 | /// The value of the property.
15 | ///
16 | /// Determines whether it must be destruct.
17 | /// If true, and the value is a non-primitive, non-array type, then the value will be converted to a structure; otherwise, unknown types will be converted to scalars, which are generally stored as strings.
18 | ///
19 | public LogProperty(string name, object value, bool destructureObjects = false)
20 | {
21 | Name = name.NotNullOrWhiteSpace(nameof(name));
22 | Value = value;
23 | DestructureObjects = destructureObjects;
24 | }
25 |
26 | ///
27 | /// The name of the property.
28 | ///
29 | public string Name { get; }
30 |
31 | ///
32 | /// The value of the property.
33 | ///
34 | public object Value { get; }
35 |
36 | ///
37 | /// Determines whether it must be destruct.
38 | /// If true, and the value is a non-primitive, non-array type, then the value will be converted to a structure; otherwise, unknown types will be converted to scalars, which are generally stored as strings.
39 | ///
40 | public bool DestructureObjects { get; }
41 | }
42 | }
--------------------------------------------------------------------------------
/SmartCacheManager.Tests/SmartCacheManagerTests.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.DependencyInjection;
2 | using NUnit.Framework;
3 | using System;
4 | using System.Threading.Tasks;
5 |
6 | namespace SmartCacheManager.Tests
7 | {
8 | public class WebServiceCacheManagerTests : TestBase
9 | {
10 | [Test]
11 | public async Task WebServiceCacheManager_Test()
12 | {
13 | var flightCacheManager = ServiceProvider.GetRequiredService();
14 |
15 | var supplier = "SupplierType_1";
16 | var searchModel = new FlightSearchModel
17 | {
18 | Origin = "MUC",
19 | Destination = "HAM",
20 | SearchDate = DateTime.Now.AddDays(3)
21 | };
22 |
23 | var expireation1 = await flightCacheManager.GetExpirationAsync(searchModel, supplier);
24 | Assert.AreEqual(expireation1, TimeSpan.Zero);
25 |
26 | var callTimes = 0;
27 | var iteration = 3;
28 | for (int i = 0; i < iteration; i++)
29 | {
30 | var result = await flightCacheManager.SearchFromCacheAsync(searchModel, supplier, async () =>
31 | {
32 | callTimes++;
33 | return DateTime.Now;
34 | });
35 | }
36 | Assert.AreEqual(callTimes, 1);
37 |
38 | var currentRpm = await flightCacheManager.GetRpmAsync(searchModel, supplier);
39 | Assert.AreEqual(currentRpm, 1.5M);
40 |
41 | var expireation2 = await flightCacheManager.CalculateCacheMinutesAsync(searchModel, supplier);
42 | var expireation3 = await flightCacheManager.GetExpirationAsync(searchModel, supplier);
43 | Assert.AreEqual(expireation2, Convert.ToInt32(expireation3.TotalMinutes));
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/SmartCacheManager/Logging/LogLevel.cs:
--------------------------------------------------------------------------------
1 | namespace SmartCacheManager.Logging
2 | {
3 | //
4 | // Summary:
5 | // Defines logging severity levels.
6 | public enum LogLevel
7 | {
8 | //
9 | // Summary:
10 | // Logs that contain the most detailed messages. These messages may contain sensitive
11 | // application data. These messages are disabled by default and should never be
12 | // enabled in a production environment.
13 | Trace = 0,
14 | //
15 | // Summary:
16 | // Logs that are used for interactive investigation during development. These logs
17 | // should primarily contain information useful for debugging and have no long-term
18 | // value.
19 | Debug = 1,
20 | //
21 | // Summary:
22 | // Logs that track the general flow of the application. These logs should have long-term
23 | // value.
24 | Information = 2,
25 | //
26 | // Summary:
27 | // Logs that highlight an abnormal or unexpected event in the application flow,
28 | // but do not otherwise cause the application execution to stop.
29 | Warning = 3,
30 | //
31 | // Summary:
32 | // Logs that highlight when the current flow of execution is stopped due to a failure.
33 | // These should indicate a failure in the current activity, not an application-wide
34 | // failure.
35 | Error = 4,
36 | //
37 | // Summary:
38 | // Logs that describe an unrecoverable application or system crash, or a catastrophic
39 | // failure that requires immediate attention.
40 | Critical = 5,
41 | //
42 | // Summary:
43 | // Not used for writing log messages. Specifies that a logging category should not
44 | // write any messages.
45 | None = 6
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/SmartCacheManager/Logging/LogConstants.cs:
--------------------------------------------------------------------------------
1 | namespace SmartCacheManager.Logging
2 | {
3 | public static class LogConstants
4 | {
5 | public static bool LogCacheEnabled { get; set; } = true;
6 | public static bool LogTimingEnabled { get; set; } = true;
7 | public static bool LogErrorEnabled { get; set; } = true;
8 |
9 | public const string LogError = "LogError";
10 | public const string LogTiming = "LogTiming";
11 | public const string LogCache = "LogCache";
12 |
13 | public const string Exception = "Exception";
14 | public const string Level = "Level";
15 | public const string IsLimitationReached = "IsLimitationReached";
16 | public const string OutgoingRequestCount = "OutgoingRequestCount";
17 | public const string LimitSetting = "LimitSetting";
18 | public const string CurrentRPM = "CurrentRPM";
19 | public const string SearchModel = "SearchModel";
20 | public const string SupplierType = "SupplierType";
21 | public const string CacheSetting = "CacheSetting";
22 | public const string IncomingRequestHashCode = "IncomingRequestHashCode";
23 | public const string OutgoingRequestHashCode = "OutgoingRequestHashCode";
24 | public const string CurrentExpirationChanged = "CurrentExpirationChanged";
25 | public const string NowDateTime = "NowDateTime";
26 | public const string SearchDiffHours = "SearchDiffHours";
27 | public const string CacheKey = "CacheKey";
28 | public const string CacheMinutesByCurrentRPM = "CacheMinutesByCurrentRPM";
29 | public const string CacheMinutesBySearchDate = "CacheMinutesBySearchDate";
30 | public const string CurrentExpirationMinutes = "CurrentExpirationMinutes";
31 | public const string IncomingPattern = "IncomingPattern";
32 | public const string OutgoingPattern = "OutgoingPattern";
33 | public const string SearchDate = "SearchDate";
34 | public const string MethodName = "MethodName";
35 | public const string ElapsedMilliSeconds = "ElapsedMilliSeconds";
36 | public const string MethodHashCode = "MethodHashCode";
37 | public const string DataRetrieved = "DataRetrieved";
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/SmartCacheManager.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.29728.190
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SmartCacheManager", "SmartCacheManager\SmartCacheManager.csproj", "{63C5D207-EA57-4D54-9FA9-70D282952465}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SmartCacheManager.Demo", "SmartCacheManager.Demo\SmartCacheManager.Demo.csproj", "{5857EF7F-68DE-4E40-8562-AFB2F1731ADE}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SmartCacheManager.Tests", "SmartCacheManager.Tests\SmartCacheManager.Tests.csproj", "{994BF574-8D91-4BF9-B48C-710FB8D1594A}"
11 | EndProject
12 | Global
13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
14 | Debug|Any CPU = Debug|Any CPU
15 | Release|Any CPU = Release|Any CPU
16 | EndGlobalSection
17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
18 | {63C5D207-EA57-4D54-9FA9-70D282952465}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
19 | {63C5D207-EA57-4D54-9FA9-70D282952465}.Debug|Any CPU.Build.0 = Debug|Any CPU
20 | {63C5D207-EA57-4D54-9FA9-70D282952465}.Release|Any CPU.ActiveCfg = Release|Any CPU
21 | {63C5D207-EA57-4D54-9FA9-70D282952465}.Release|Any CPU.Build.0 = Release|Any CPU
22 | {5857EF7F-68DE-4E40-8562-AFB2F1731ADE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23 | {5857EF7F-68DE-4E40-8562-AFB2F1731ADE}.Debug|Any CPU.Build.0 = Debug|Any CPU
24 | {5857EF7F-68DE-4E40-8562-AFB2F1731ADE}.Release|Any CPU.ActiveCfg = Release|Any CPU
25 | {5857EF7F-68DE-4E40-8562-AFB2F1731ADE}.Release|Any CPU.Build.0 = Release|Any CPU
26 | {994BF574-8D91-4BF9-B48C-710FB8D1594A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27 | {994BF574-8D91-4BF9-B48C-710FB8D1594A}.Debug|Any CPU.Build.0 = Debug|Any CPU
28 | {994BF574-8D91-4BF9-B48C-710FB8D1594A}.Release|Any CPU.ActiveCfg = Release|Any CPU
29 | {994BF574-8D91-4BF9-B48C-710FB8D1594A}.Release|Any CPU.Build.0 = Release|Any CPU
30 | EndGlobalSection
31 | GlobalSection(SolutionProperties) = preSolution
32 | HideSolutionNode = FALSE
33 | EndGlobalSection
34 | GlobalSection(ExtensibilityGlobals) = postSolution
35 | SolutionGuid = {97E3E481-8547-480C-B591-7119B53D6AD2}
36 | EndGlobalSection
37 | EndGlobal
38 |
--------------------------------------------------------------------------------
/SmartCacheManager.Demo/Views/Shared/_Layout.cshtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | @ViewData["Title"] - SmartCacheManager.Demo
7 |
8 |
9 |
10 |
11 |
32 |
33 |
34 | @RenderBody()
35 |
36 |
37 |
38 |
43 |
44 |
45 |
46 | @RenderSection("Scripts", required: false)
47 |
48 |
49 |
--------------------------------------------------------------------------------
/SmartCacheManager.Tests/FlightCacheManager.cs:
--------------------------------------------------------------------------------
1 | using SmartCacheManager.Caching;
2 | using SmartCacheManager.Data;
3 | using SmartCacheManager.Logging;
4 | using SmartCacheManager.Services;
5 | using SmartCacheManager.Utilities;
6 | using System;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 |
10 | namespace SmartCacheManager.Tests
11 | {
12 | public interface IFlightCacheManager
13 | {
14 | Task SearchFromCacheAsync(FlightSearchModel searchModel, TSupplierType supplierType, Func> dataRetriever, CancellationToken cancellationToken = default);
15 | Task CalculateCacheMinutesAsync(FlightSearchModel searchModel, TSupplierType supplierType, CancellationToken cancellationToken = default);
16 | Task GetRpmAsync(FlightSearchModel searchModel, TSupplierType supplierType, CancellationToken cancellationToken = default);
17 | Task GetExpirationAsync(FlightSearchModel searchModel, TSupplierType supplierType, CancellationToken cancellationToken = default);
18 | }
19 |
20 | public class FlightCacheManager : SmartCacheManager, IFlightCacheManager
21 | {
22 | public FlightCacheManager(ICacheManager cacheManager, ILoggerFactory loggerFactory, ISystemClock systemClock,
23 | ICacheSettingService cacheSettingService, ISearchHistoryService searchHistoryService)
24 | : base(cacheManager, loggerFactory, systemClock, cacheSettingService, searchHistoryService)
25 | {
26 | }
27 |
28 | protected override string GenerateSearchHistoryKey(FlightSearchModel searchModel)
29 | {
30 | return GenerateSearchResultKey(searchModel);
31 | }
32 |
33 | protected override string GenerateSearchResultKey(FlightSearchModel searchModel)
34 | {
35 | return $"{searchModel.Origin}-{searchModel.Destination}-{searchModel.SearchDate.ToString("yyyy-MM-dd")}";
36 | }
37 |
38 | protected override DateTime GetSearchDate(FlightSearchModel searchModel)
39 | {
40 | return searchModel.SearchDate;
41 | }
42 | }
43 |
44 | public class FlightSearchModel
45 | {
46 | public string Origin { get; set; }
47 | public string Destination { get; set; }
48 | public DateTime SearchDate { get; set; }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/SmartCacheManager/Services/ServiceConfigurationExtensions.cs:
--------------------------------------------------------------------------------
1 | using SmartCacheManager.Utilities;
2 | using Microsoft.Extensions.DependencyInjection;
3 | using Microsoft.Extensions.DependencyInjection.Extensions;
4 |
5 | namespace SmartCacheManager.Services
6 | {
7 | public static class ServiceConfigurationExtensions
8 | {
9 | ///
10 | /// Add CacheSettingService and LimitSettingService services
11 | ///
12 | /// services
13 | /// IServiceCollection
14 | public static IServiceCollection AddSettingServices(this IServiceCollection services)
15 | {
16 | services.TryAddScoped(typeof(ICacheSettingService<>), typeof(CacheSettingService<>));
17 | services.TryAddScoped(typeof(ILimitSettingService<>), typeof(LimitSettingService<>));
18 | return services;
19 | }
20 |
21 | ///
22 | /// Add open generics SearchHistoryService
23 | ///
24 | /// services
25 | /// Determine use DatabaseSearchHistoryService[true] or CacheSearchHistoryService[false]
26 | /// IServiceCollection
27 | public static IServiceCollection AddSearchHistoryService(this IServiceCollection services, bool useDatabaseSearchHistory = false)
28 | {
29 | if (useDatabaseSearchHistory)
30 | services.TryAddScoped(typeof(ISearchHistoryService<,>), typeof(DatabaseSearchHistoryService<,>));
31 | else
32 | services.TryAddScoped(typeof(ISearchHistoryService<,>), typeof(CacheSearchHistoryService<,>));
33 |
34 | return services;
35 | }
36 |
37 | ///
38 | /// Add AsyncLock as scoped to services
39 | ///
40 | /// services
41 | /// Determine whether async lock is thread safety
42 | /// IServiceCollection
43 | public static IServiceCollection AddAsyncLockScoped(this IServiceCollection services, bool enableThreadSafety = false)
44 | {
45 | if (enableThreadSafety)
46 | services.TryAddScoped();
47 | else
48 | services.TryAddScoped();
49 |
50 | return services;
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/SmartCacheManager.Demo/Startup.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Builder;
2 | using Microsoft.AspNetCore.Hosting;
3 | using Microsoft.EntityFrameworkCore;
4 | using Microsoft.Extensions.Configuration;
5 | using Microsoft.Extensions.DependencyInjection;
6 | using Microsoft.Extensions.Hosting;
7 |
8 |
9 | namespace SmartCacheManager.Demo
10 | {
11 | public class Startup
12 | {
13 | public Startup(IConfiguration configuration)
14 | {
15 | Configuration = configuration;
16 | }
17 |
18 | public IConfiguration Configuration { get; }
19 |
20 | // This method gets called by the runtime. Use this method to add services to the container.
21 | public static void ConfigureServices(IServiceCollection services)
22 | {
23 | //services.AddSmartCacheManager(opt => opt.UseSqlServer("Data Source=.;Initial Catalog=CacheManageDb;Integrated Security=true"),
24 | // loggerConfigure: opt => { opt.SqlConnectionString = "Data Source=.;Initial Catalog=CacheManageDb;Integrated Security=true"; });
25 |
26 | services.AddSmartCacheManager(opt => opt.UseSqlServer("Data Source=.;Initial Catalog=CacheDbManager;Integrated Security=true"),
27 | enableThreadSafety: true, useDatabaseSearchHistory: true);
28 | services.AddScoped();
29 |
30 | services.AddControllersWithViews();
31 | }
32 |
33 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
34 | public static void Configure(IApplicationBuilder app, IWebHostEnvironment env)
35 | {
36 | app.InitializeSmartCacheManager();
37 |
38 | if (env.IsDevelopment())
39 | {
40 | app.UseDeveloperExceptionPage();
41 | }
42 | else
43 | {
44 | app.UseExceptionHandler("/Home/Error");
45 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
46 | app.UseHsts();
47 | }
48 | app.UseHttpsRedirection();
49 | app.UseStaticFiles();
50 |
51 |
52 | app.UseRouting();
53 |
54 | app.UseAuthorization();
55 |
56 | app.UseEndpoints(endpoints =>
57 | {
58 | endpoints.MapControllerRoute(
59 | name: "default",
60 | pattern: "{controller=Home}/{action=Index}/{id?}");
61 | });
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/SmartCacheManager.Demo/Models/FlightCacheManager.cs:
--------------------------------------------------------------------------------
1 | using SmartCacheManager.Caching;
2 | using SmartCacheManager.Data;
3 | using SmartCacheManager.Logging;
4 | using SmartCacheManager.Services;
5 | using SmartCacheManager.Utilities;
6 | using System;
7 | using System.Linq;
8 |
9 | namespace SmartCacheManager.Demo
10 | {
11 | public class FlightSearchModel
12 | {
13 | #pragma warning disable CA1819 // Properties should not return arrays
14 | public string[] Origins { get; set; }
15 |
16 | public string[] Destinations { get; set; }
17 |
18 | public DateTime[] DepartureDates { get; set; }
19 | #pragma warning restore CA1819 // Properties should not return arrays
20 |
21 | public int Adult { get; set; }
22 |
23 | public int Child { get; set; }
24 |
25 | public int Infant { get; set; }
26 | }
27 |
28 | ///
29 | /// Service for manager flight caching
30 | ///
31 | public class FlightCacheManager : SmartCacheManager
32 | {
33 | public FlightCacheManager(ICacheManager cacheManager, ILoggerFactory loggerFactory, ISystemClock systemClock,
34 | ICacheSettingService cacheSettingService, ISearchHistoryService searchHistoryService)
35 | : base(cacheManager, loggerFactory, systemClock, cacheSettingService, searchHistoryService)
36 | {
37 | }
38 |
39 | protected override string GenerateSearchResultKey(FlightSearchModel searchModel)
40 | {
41 | var departureDates = string.Join("|", searchModel.DepartureDates.Select(p => p.ToString("yyyy-MM-dd")));
42 | var origins = string.Join("|", searchModel.Origins);
43 | var destinations = string.Join("|", searchModel.Destinations);
44 | return $"{origins}-{destinations}-{departureDates}-{searchModel.Adult}-{searchModel.Child}-{searchModel.Infant}";
45 | }
46 |
47 | protected override string GenerateSearchHistoryKey(FlightSearchModel searchModel)
48 | {
49 | var departureDates = string.Join("|", searchModel.DepartureDates.Select(p => p.ToString("yyyy-MM-dd")));
50 | var origins = string.Join("|", searchModel.Origins);
51 | var destinations = string.Join("|", searchModel.Destinations);
52 | return $"{origins}-{destinations}-{departureDates}";
53 | }
54 |
55 | protected override DateTime GetSearchDate(FlightSearchModel searchModel)
56 | {
57 | return searchModel.DepartureDates.Min();
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/SmartCacheManager/Data/Models/LimitSetting.cs:
--------------------------------------------------------------------------------
1 | using SmartCacheManager.Utilities;
2 | using System;
3 | using System.ComponentModel.DataAnnotations;
4 | using System.ComponentModel.DataAnnotations.Schema;
5 |
6 | namespace SmartCacheManager.Data
7 | {
8 | ///
9 | /// Represent the settings of limitation
10 | ///
11 | public partial class LimitSetting
12 | {
13 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
14 | public int Id { get; set; }
15 |
16 | ///
17 | /// Count of request for limitation
18 | /// تعداد محدودیت درخواست ها
19 | ///
20 | public int RequestLimit { get; set; }
21 |
22 | ///
23 | /// Type of supplier
24 | /// نوع تامین کننده
25 | ///
26 | [MaxLength(50)]
27 | public string SupplierType { get; set; }
28 |
29 | ///
30 | /// Enable limitation mode
31 | /// فعال کردن قابلیت محدود سازی
32 | ///
33 | public bool Enabled { get; set; }
34 |
35 | ///
36 | /// Duration of limitation in hours
37 | /// مدت زمان محدودیت بر حسب ساعت
38 | ///
39 | public int LimitDurationHours { get; set; }
40 |
41 | ///
42 | /// Extended time in hours
43 | /// مدت زمان تمدید محدودیت پس از هر خرید
44 | ///
45 | public int ExtendedTimeHours { get; set; }
46 |
47 | ///
48 | /// Get limit duration in TimeSpan
49 | ///
50 | [NotMapped]
51 | public TimeSpan LimitDuration => TimeSpan.FromHours(LimitDurationHours);
52 |
53 | ///
54 | /// Get extended time in TimeSpan
55 | ///
56 | [NotMapped]
57 | public TimeSpan Extended => TimeSpan.FromHours(ExtendedTimeHours);
58 |
59 | ///
60 | /// Clone a shallow copay of this object
61 | ///
62 | ///
63 | ///
64 | public T ShallowCopy() where T : LimitSetting
65 | {
66 | return (T)MemberwiseClone(); // or DeepCopy with BinarySerializer or JsonSerializer
67 | }
68 |
69 | public TSupplierType GetSupplierType()
70 | {
71 | return SupplierType.ConvertTo();
72 | }
73 |
74 | public void SetSupplierType(TSupplierType supplierType)
75 | {
76 | SupplierType = supplierType.ConvertTo();
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/SmartCacheManager/Services/Contracts/ISearchHistoryService.cs:
--------------------------------------------------------------------------------
1 | using SmartCacheManager.Data;
2 | using System.Threading.Tasks;
3 | using System.Threading;
4 |
5 | namespace SmartCacheManager.Services
6 | {
7 | public interface ISearchHistoryService
8 | where TCacheSetting : CacheSetting, new()
9 | where TLimitSetting : LimitSetting, new()
10 | {
11 | ///
12 | /// Add an incoming search request
13 | ///
14 | /// Type of SupplierType
15 | /// Icoming pattern prefix
16 | /// Supplier type
17 | /// cancellationToken
18 | /// Task
19 | Task AddIncommingAsync(string incomingPrefix, TSupplierType supplierType, CancellationToken cancellationToken = default);
20 |
21 | ///
22 | /// Add an outgoing search request
23 | ///
24 | /// Type of SupplierType
25 | /// Outgoing pattern prefix
26 | /// Supplier type
27 | /// cancellationToken
28 | /// Task
29 | Task AddOutgoingAsync(string outgoingPrefix, TSupplierType supplierType, CancellationToken cancellationToken = default);
30 |
31 | ///
32 | /// Get current RPM based on specified prefix and supplier type
33 | ///
34 | /// Type of SupplierType
35 | /// Icoming pattern prefix
36 | /// Supplier type
37 | /// cancellationToken
38 | /// Current RPM
39 | Task GetRpmAsync(string incomingPrefix, TSupplierType supplierType, CancellationToken cancellationToken = default);
40 |
41 | ///
42 | /// Specifies whether or not the limitation is reached
43 | ///
44 | /// Type of SupplierType
45 | /// Outgoing pattern prefix
46 | /// Supplier type
47 | /// cancellationToken
48 | /// Return true if limitation is reached
49 | Task IsLimitationReachedAsync(string outgoingPrefix, TSupplierType supplierType, CancellationToken cancellationToken = default);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/SmartCacheManager/Utilities/Check.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Linq;
4 |
5 | namespace SmartCacheManager.Utilities
6 | {
7 | public static class Check
8 | {
9 | ///
10 | /// Validates that obj is not null , otherwise throws an exception.
11 | ///
12 | ///
13 | ///
14 | ///
15 | ///
16 | ///
17 | public static T NotNull(this T obj, string name, string message = null)
18 | {
19 | if (obj == null)
20 | throw new ArgumentNullException($"{name} ({typeof(T)}", message);
21 | return obj;
22 | }
23 |
24 | ///
25 | /// Validates that str is not null or white space , otherwise throws an exception.
26 | ///
27 | ///
28 | ///
29 | ///
30 | ///
31 | public static string NotNullOrWhiteSpace(this string str, string name, string message = null)
32 | {
33 | if (string.IsNullOrWhiteSpace(str))
34 | throw new ArgumentNullException(name, message);
35 | return str;
36 | }
37 |
38 | ///
39 | /// Validates that list is not null or empty , otherwise throws an exception.
40 | ///
41 | ///
42 | ///
43 | ///
44 | ///
45 | ///
46 | public static T NotNullOrEmpty(this T obj, string name, string message = null) where T : IEnumerable
47 | {
48 | if (obj == null)
49 | throw new ArgumentNullException($"{name} ({typeof(T)}", message);
50 |
51 | if (!obj.Cast().Any())
52 | throw new ArgumentException($"Argument {name} ({typeof(T)}) is empty. " + message, name);
53 |
54 | return obj;
55 | }
56 |
57 | ///
58 | /// Checks that the list is not empty (throw ArgumentException if list is empty)
59 | ///
60 | ///
61 | ///
62 | ///
63 | ///
64 | ///
65 | public static T NotEmpty(this T obj, string name, string message = null) where T : IEnumerable
66 | {
67 | if (!obj.Cast().Any())
68 | throw new ArgumentException($"Argument {name} ({typeof(T)}) is empty. " + message, name);
69 | return obj;
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/SmartCacheManager/Data/Models/CacheSetting.cs:
--------------------------------------------------------------------------------
1 | using SmartCacheManager.Utilities;
2 | using System.ComponentModel.DataAnnotations;
3 | using System.ComponentModel.DataAnnotations.Schema;
4 |
5 | namespace SmartCacheManager.Data
6 | {
7 | ///
8 | /// Represent the settings of cache
9 | ///
10 | public partial class CacheSetting
11 | {
12 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
13 | public int Id { get; set; }
14 |
15 | ///
16 | /// Minimum time difference of search related to now in hours.
17 | /// حداقل اختلاف ساعت جستجو نسبت به الان
18 | ///
19 | public int MinSearchDiffHours { get; set; }
20 |
21 | ///
22 | /// Maximum time difference of search related to now in hours.
23 | /// حداکثر اختلاف ساعت جستجو نسبت به الان
24 | ///
25 | public int MaxSearchDiffHours { get; set; }
26 |
27 | ///
28 | /// Over time difference of search related to now in hours that disable caching.
29 | /// لغو کش های بیشتر از زمان
30 | ///
31 | public int OverSearchDiffHours { get; set; }
32 |
33 | ///
34 | /// Minimum time of cache in minutes.
35 | /// حداقل زمان کش به دقیقه
36 | ///
37 | public int MinCacheMinutes { get; set; }
38 |
39 | ///
40 | /// Maximum time of cache in minutes.
41 | /// حداکثر زمان کش به دقیقه
42 | ///
43 | public int MaxCacheMinutes { get; set; }
44 |
45 | ///
46 | /// Minimum RPM of recent searchs.
47 | /// حداقل تعدد جستجو های اخیر
48 | ///
49 | public int RecentSearchMinimumRPM { get; set; }
50 |
51 | ///
52 | /// Maximum RPM of recent searchs.
53 | /// حداکثر تعدد جستجو های اخیر
54 | ///
55 | public int RecentSearchMaxmimumRPM { get; set; }
56 |
57 | ///
58 | /// Time duration to calculate RPM in minutes.
59 | /// بازه اندازه گیری تعدد سرچ های اخیر به دقیقه
60 | ///
61 | public int RpmDurationMinutes { get; set; }
62 |
63 | ///
64 | /// Type of supplier
65 | /// نوع تامین کننده
66 | ///
67 | [MaxLength(50)]
68 | public string SupplierType { get; set; }
69 |
70 | ///
71 | /// Clone a shallow copay of this object
72 | ///
73 | ///
74 | ///
75 | public T ShallowCopy() where T : CacheSetting
76 | {
77 | return (T)MemberwiseClone(); // or DeepCopy with BinarySerializer or JsonSerializer
78 | }
79 |
80 | public TSupplierType GetSupplierType()
81 | {
82 | return SupplierType.ConvertTo();
83 | }
84 |
85 | public void SetSupplierType(TSupplierType supplierType)
86 | {
87 | SupplierType = supplierType.ConvertTo();
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/SmartCacheManager/Utilities/SerilogExtensions.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Logging;
2 | using Serilog;
3 | using Serilog.Configuration;
4 | using Serilog.Events;
5 | using Serilog.Filters;
6 | using System.Linq;
7 |
8 | namespace SmartCacheManager.Utilities
9 | {
10 | public static class SerilogExtensions
11 | {
12 | ///
13 | /// Filter log events to include only those that has EventId with specified id
14 | ///
15 | /// configuration
16 | /// The id of EventId
17 | /// LoggerConfiguration
18 | public static LoggerConfiguration FilterByEventId(this LoggerFilterConfiguration configuration, int id)
19 | {
20 | var scalarValue = new ScalarValue(id);
21 | return configuration.ByIncludingOnly(logEvent =>
22 | {
23 | if (logEvent.Properties.TryGetValue(nameof(EventId), out var propertyValue) && propertyValue is StructureValue structureValue)
24 | {
25 | var idValue = structureValue.Properties.Where(cc => cc.Name == nameof(EventId.Id)).FirstOrDefault();
26 | return scalarValue.Equals(idValue.Value);
27 | }
28 | return false;
29 | });
30 | }
31 |
32 | ///
33 | /// Filter log events to include only those that has specified property
34 | ///
35 | /// configuration
36 | /// Name of property
37 | /// Expected property value
38 | /// LoggerConfiguration
39 | public static LoggerConfiguration FilterByProperty(this LoggerFilterConfiguration configuration, string propertyName, string propertyValue)
40 | {
41 | return configuration.ByIncludingOnly(Matching.WithProperty(propertyName, propertyValue)); //new ScalarValue(propertyValue)
42 |
43 | //var scalarValue = new ScalarValue(propertyValue);
44 | //return configuration.ByIncludingOnly(logEvent =>
45 | //{
46 | // if (logEvent.Properties.TryGetValue(propertyName, out var prop) && prop is ScalarValue value)
47 | // return scalarValue.Equals(value);
48 | // return false;
49 | //});
50 | }
51 |
52 | ///
53 | /// Filter log events to include only those that has specified property
54 | ///
55 | /// configuration
56 | /// Name of property
57 | /// Expected property value
58 | /// LoggerConfiguration
59 | public static LoggerConfiguration FilterByHasProperty(this LoggerFilterConfiguration configuration, string propertyName)
60 | {
61 | return configuration.ByIncludingOnly(Matching.WithProperty(propertyName));
62 |
63 | //return configuration.ByIncludingOnly(logEvent => logEvent.Properties.ContainsKey(propertyName));
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/SmartCacheManager/Logging/ILogger.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace SmartCacheManager.Logging
5 | {
6 | ///
7 | /// Logger abstraction with soruce context
8 | ///
9 | /// Type of source context
10 | public interface ILogger : ILogger
11 | {
12 | }
13 |
14 | ///
15 | /// Logger abstraction
16 | ///
17 | public interface ILogger
18 | {
19 | ///
20 | /// Write an event to the log.
21 | ///
22 | /// The level of the log.
23 | /// Exception related to the log.
24 | /// Message template describing the log.
25 | /// Objects positionally formatted into the message template.
26 | /// Properties associated with the log.
27 | void Log(LogLevel logLevel, Exception exception, string messageTemplate, IEnumerable propertyValues, IEnumerable logProperties);
28 |
29 | ///
30 | /// Create and begin a log scope to collecting properties to log, returning an IDisposable that must later be used to write log.
31 | ///
32 | /// The level of the log.
33 | /// Message template describing the event
34 | /// Objects positionally formatted into the message template.
35 | /// Properties associated with the log.
36 | /// An IDisposable that ends the log operation scope on dispose.
37 | IDisposable BeginLogScope(LogLevel logLevel, string messageTemplate, IEnumerable propertyValues, IEnumerable logProperties);
38 |
39 | ///
40 | /// Add a property to the log scope if not already present, otherwise, update its value.
41 | ///
42 | /// Property associated with the log
43 | void SetProperty(LogProperty logProperty);
44 |
45 | ///
46 | /// Push a property onto the context, returning an IDisposable that must later be used to remove the property,
47 | /// along with any others that may have been pushed on top of it and not yet popped.
48 | /// The property must be popped from the same thread/logical call context.
49 | ///
50 | /// Property associated with the log
51 | /// A handle to later remove the property from the context.
52 | IDisposable BeginScope(LogProperty logProperty);
53 |
54 | ///
55 | /// Remove all properties from LogContext for the current async scope.
56 | ///
57 | void ResetScope();
58 |
59 | ///
60 | /// Remove all properties from the LogContext, returning an IDisposable that must later be used to restore enrichers that were on the stack before Suspend() was called.
61 | ///
62 | /// A handle that must be disposed, in order, to restore properties back to the stack.
63 | IDisposable SuspendScope();
64 | }
65 | }
--------------------------------------------------------------------------------
/SmartCacheManager.Demo/Controllers/HomeController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Mvc;
6 | using Microsoft.Extensions.Logging;
7 | using SmartCacheManager.Caching;
8 | using SmartCacheManager.Demo.Models;
9 |
10 | namespace SmartCacheManager.Demo.Controllers
11 | {
12 | public class CacheDetail
13 | {
14 | public decimal CurrentRPM { get; set; }
15 | public int CalculatedCacheMinutes { get; set; }
16 | public TimeSpan ExistedCacheMinutes { get; set; }
17 | }
18 |
19 | public class IndexViewModel
20 | {
21 | public FlightSearchResult FlightSearchResult { get; set; }
22 | public List CacheDetails { get; set; }
23 | }
24 |
25 | public class HomeController : Controller
26 | {
27 | private readonly ILogger _logger;
28 | private readonly FlightCacheManager _flightCacheManager;
29 | private readonly ICacheManager _cacheManager;
30 | private static readonly List _cacheDetails = new List();
31 |
32 | public HomeController(ILogger logger, FlightCacheManager flightCacheManager, ICacheManager cacheManager)
33 | {
34 | _logger = logger;
35 | _flightCacheManager = flightCacheManager;
36 | _cacheManager = cacheManager;
37 | }
38 |
39 | public async Task Index()
40 | {
41 | var searchModel = new FlightSearchModel
42 | {
43 | Adult = 1,
44 | Child = 2,
45 | Infant = 3,
46 | Origins = new[] { "THR" },
47 | Destinations = new[] { "ISF" },
48 | DepartureDates = new[] { DateTime.Now.AddDays(29) }
49 | };
50 |
51 | var existed = await _flightCacheManager.GetExpirationAsync(searchModel, 1);
52 |
53 | var result = await _flightCacheManager.SearchFromCacheAsync(searchModel, 1, async () =>
54 | {
55 | return new FlightSearchResult();
56 | });
57 |
58 | var cacheDetail = new CacheDetail
59 | {
60 | CurrentRPM = await _flightCacheManager.GetRpmAsync(searchModel, 1),
61 | CalculatedCacheMinutes = await _flightCacheManager.CalculateCacheMinutesAsync(searchModel, 1),
62 | ExistedCacheMinutes = existed,
63 | };
64 | _cacheDetails.Insert(0, cacheDetail);
65 |
66 | var viewModel = new IndexViewModel
67 | {
68 | FlightSearchResult = result,
69 | CacheDetails = _cacheDetails
70 | };
71 |
72 | return View(viewModel);
73 | }
74 |
75 | public async Task FlushCacheDb()
76 | {
77 | await _cacheManager.FlushAsync();
78 | _cacheDetails.Clear();
79 | return RedirectToAction(nameof(Index));
80 | }
81 |
82 | public IActionResult Privacy()
83 | {
84 | return View();
85 | }
86 |
87 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
88 | public IActionResult Error()
89 | {
90 | return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/SmartCacheManager/SmartCacheManager.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.1
5 | SmartCacheManager
6 | SmartCacheManager
7 | SmartCacheManager
8 | SmartCacheManager
9 | 2.1.0
10 | 2.1.0
11 | 2.1.0
12 | Mohammad Javad Ebrahimi
13 | Mohammad Javad Ebrahimi
14 | Copyright © Mohammad Javad Ebrahimi 2020
15 | SmartCacheManager is a smart caching module to cache objects with resilient and variable expiration timing that is useful for caching result of web services.
16 | Cache Caching Flight IATA Train
17 | GPL-3.0-or-later
18 | https://github.com/mjebrahimi/SmartCacheManager
19 | git
20 | true
21 | true
22 | true
23 | false
24 | true
25 | embedded
26 | false
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 |
--------------------------------------------------------------------------------
/SmartCacheManager/Services/CacheSettingService.cs:
--------------------------------------------------------------------------------
1 | using SmartCacheManager.Caching;
2 | using SmartCacheManager.Data;
3 | using SmartCacheManager.Utilities;
4 | using MethodTimer;
5 | using Microsoft.EntityFrameworkCore;
6 | using System;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 | using SmartCacheManager.Logging;
10 |
11 | namespace SmartCacheManager.Services
12 | {
13 | ///
14 | /// Service for CacheSetting
15 | ///
16 | /// Type of CacheSetting
17 | public class CacheSettingService : ICacheSettingService
18 | where TCacheSetting : CacheSetting, new()
19 | {
20 | protected readonly IGenericStore Store;
21 | protected readonly ICacheManager CacheManager;
22 | protected readonly ILogger Logger;
23 | protected readonly IAsyncLock AsyncLock;
24 | protected static readonly string CacheKeySupplierType = GenericStore.CacheKeyPrefix + "_BySupplierType-{0}";
25 |
26 | public CacheSettingService(IGenericStore store, ICacheManager cacheManager, ILoggerFactory loggerFactory, IAsyncLock asyncLock)
27 | {
28 | Store = store.NotNull(nameof(store));
29 | CacheManager = cacheManager.NotNull(nameof(cacheManager));
30 | Logger = loggerFactory.NotNull(nameof(loggerFactory)).CreateLogger(GetType()).NotNull(nameof(Logger));
31 | AsyncLock = asyncLock.NotNull(nameof(asyncLock));
32 | }
33 |
34 | ///
35 | /// Get CacheSetting from cache by specified SupplierType or if not exists create a global CacheSetting
36 | ///
37 | /// Type of supplier
38 | /// cancellationToken
39 | /// TCacheSetting
40 | [Time]
41 | public async Task GetFromCacheBySupplierTypeAsync(TSupplierType supplierType, CancellationToken cancellationToken = default)
42 | {
43 | try
44 | {
45 | supplierType.NotNull(nameof(supplierType));
46 |
47 | var strSupplierType = supplierType.ConvertTo();
48 |
49 | var cacheKey = string.Format(CacheKeySupplierType, strSupplierType);
50 |
51 | var setting = await CacheManager.GetAsync(cacheKey, async () =>
52 | {
53 | using (await AsyncLock.LockAsync())
54 | {
55 | var cacheSetting = await Store.TableNoTracking.SingleOrDefaultAsync(p => p.SupplierType == strSupplierType, cancellationToken);
56 |
57 | if (cacheSetting == null)
58 | {
59 | cacheSetting = (await Store.TableNoTracking.SingleAsync(p => p.SupplierType == null, cancellationToken)).ShallowCopy();
60 | cacheSetting.SupplierType = strSupplierType;
61 | }
62 |
63 | return cacheSetting;
64 | }
65 | }, 1440, cancellationToken).ConfigureAwait(false);
66 |
67 | Logger.SetProperty(LogConstants.CacheSetting, setting, true);
68 | return setting;
69 | }
70 | catch (Exception ex)
71 | when (Logger.LogErrorIfNotBefore(ex, "Exception ocurred in {MethodName}", nameof(GetFromCacheBySupplierTypeAsync)))
72 | {
73 | throw;
74 | }
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/SmartCacheManager/Services/LimitSettingService.cs:
--------------------------------------------------------------------------------
1 | using SmartCacheManager.Caching;
2 | using SmartCacheManager.Data;
3 | using SmartCacheManager.Utilities;
4 | using MethodTimer;
5 | using Microsoft.EntityFrameworkCore;
6 | using System;
7 | using System.Collections.Generic;
8 | using System.Linq;
9 | using System.Threading;
10 | using System.Threading.Tasks;
11 | using SmartCacheManager.Logging;
12 |
13 | namespace SmartCacheManager.Services
14 | {
15 | ///
16 | /// Service for LimitSetting
17 | ///
18 | /// Type of LimitSetting
19 | public class LimitSettingService : ILimitSettingService
20 | where TLimitSetting : LimitSetting, new()
21 | {
22 | protected readonly IGenericStore Store;
23 | protected readonly ICacheManager CacheManager;
24 | protected readonly ILogger Logger;
25 | protected readonly IAsyncLock AsyncLock;
26 | protected static readonly string CacheKeySupplierType = GenericStore.CacheKeyPrefix + "_BySupplierType-{0}";
27 |
28 | public LimitSettingService(IGenericStore store, ICacheManager cacheManager, ILoggerFactory loggerFactory, IAsyncLock asyncLock)
29 | {
30 | Store = store.NotNull(nameof(store));
31 | CacheManager = cacheManager.NotNull(nameof(cacheManager));
32 | Logger = loggerFactory.NotNull(nameof(loggerFactory)).CreateLogger(GetType()).NotNull(nameof(Logger));
33 | AsyncLock = asyncLock.NotNull(nameof(asyncLock));
34 | }
35 |
36 | ///
37 | /// Get LimitSetting from cache by specified SupplierType or if not exists create a global LimitSetting
38 | ///
39 | /// Type of supplier
40 | /// cancellationToken
41 | /// List of TLimitSetting
42 | [Time]
43 | public Task> GetFromCacheBySupplierTypeAsync(TSupplierType supplierType, CancellationToken cancellationToken = default)
44 | {
45 | try
46 | {
47 | supplierType.NotNull(nameof(supplierType));
48 |
49 | var strSupplierType = supplierType.ConvertTo();
50 |
51 | var cacheKey = string.Format(CacheKeySupplierType, strSupplierType);
52 |
53 | return CacheManager.GetAsync(cacheKey, async () =>
54 | {
55 | using (await AsyncLock.LockAsync())
56 | {
57 | var list = await Store.TableNoTracking.Where(p => p.Enabled && p.SupplierType == strSupplierType).ToListAsync(cancellationToken);
58 |
59 | if (list.Count == 0)
60 | {
61 | var limitSetting = (await Store.TableNoTracking.SingleOrDefaultAsync(p => p.Enabled && p.SupplierType == null, cancellationToken))?.ShallowCopy();
62 |
63 | if (limitSetting != null)
64 | {
65 | limitSetting.SupplierType = strSupplierType;
66 | list.Add(limitSetting);
67 | }
68 | }
69 |
70 | return list;
71 | }
72 | }, 1440, cancellationToken);
73 | }
74 | catch (Exception ex)
75 | when (Logger.LogErrorIfNotBefore(ex, "Exception ocurred in {MethodName}", nameof(GetFromCacheBySupplierTypeAsync)))
76 | {
77 | throw;
78 | }
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/SmartCacheManager.Demo/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap Reboot v4.3.1 (https://getbootstrap.com/)
3 | * Copyright 2011-2019 The Bootstrap Authors
4 | * Copyright 2011-2019 Twitter, Inc.
5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
7 | */*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}
8 | /*# sourceMappingURL=bootstrap-reboot.min.css.map */
--------------------------------------------------------------------------------
/SmartCacheManager/Utilities/CommonHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel;
3 | using System.Globalization;
4 | using System.Security.Cryptography;
5 | using System.Text;
6 |
7 | namespace SmartCacheManager.Utilities
8 | {
9 | public static class CommonHelper
10 | {
11 | ///
12 | /// Converts a value to a destination type.
13 | ///
14 | /// The value to convert.
15 | /// The type to convert the value to.
16 | /// The converted value.
17 | public static T ConvertTo(this object value)
18 | {
19 | return (T)ConvertTo(value, typeof(T));
20 | }
21 |
22 | ///
23 | /// Converts a value to a destination type.
24 | ///
25 | /// The value to convert.
26 | /// The type to convert the value to.
27 | /// The converted value.
28 | public static object ConvertTo(this object value, Type type)
29 | {
30 | return value.ConvertTo(type, CultureInfo.InvariantCulture);
31 | }
32 |
33 | ///
34 | /// Converts a value to a destination type.
35 | ///
36 | /// The value to convert.
37 | /// The type to convert the value to.
38 | /// Culture
39 | /// The converted value.
40 | public static object ConvertTo(this object value, Type destinationType, CultureInfo culture)
41 | {
42 | if (value == null)
43 | return null;
44 |
45 | destinationType.NotNull(nameof(destinationType));
46 | culture.NotNull(nameof(culture));
47 |
48 | //return Convert.ChangeType(value, type, CultureInfo.InvariantCulture);
49 |
50 | var destinationConverter = TypeDescriptor.GetConverter(destinationType);
51 | if (destinationConverter.CanConvertFrom(value.GetType()))
52 | return destinationConverter.ConvertFrom(null, culture, value);
53 |
54 | var sourceType = value.GetType();
55 | var sourceConverter = TypeDescriptor.GetConverter(sourceType);
56 | if (sourceConverter.CanConvertTo(destinationType))
57 | return sourceConverter.ConvertTo(null, culture, value, destinationType);
58 |
59 | if (destinationType.IsEnum && value is int)
60 | return Enum.ToObject(destinationType, (int)value);
61 |
62 | if (value is IConvertible && !destinationType.IsInstanceOfType(value))
63 | return Convert.ChangeType(value, destinationType, culture);
64 |
65 | return value;
66 | }
67 |
68 | ///
69 | /// Returns the invariant hash code for this string.
70 | ///
71 | /// string to hash
72 | /// Hash code
73 | public static long GetInvariantHashCode(this string str)
74 | {
75 | str.NotNull(nameof(str));
76 | using (var md5 = MD5.Create())
77 | {
78 | var hashed = md5.ComputeHash(Encoding.UTF8.GetBytes(str));
79 | return BitConverter.ToInt64(hashed, 0);
80 | }
81 | }
82 |
83 | ///
84 | /// Mark an exception as logged.
85 | ///
86 | /// Exception to be mark
87 | public static void MarkAsLogged(this Exception exception)
88 | {
89 | exception.NotNull(nameof(exception));
90 | exception.Data.Add(nameof(MarkAsLogged), null);
91 | }
92 |
93 | ///
94 | /// Check this exception is logged before
95 | ///
96 | ///
97 | /// Determines whether it is logged before
98 | public static bool IsLoggedBefore(this Exception exception)
99 | {
100 | exception.NotNull(nameof(exception));
101 | return exception.Data.Contains(nameof(MarkAsLogged));
102 | }
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/SmartCacheManager/Utilities/AsyncLock.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 |
5 | namespace SmartCacheManager.Utilities
6 | {
7 | ///
8 | /// Abstraction for lock with async/await support
9 | ///
10 | public interface IAsyncLock
11 | {
12 | ///
13 | /// Asynchronously waits to enter the lock
14 | ///
15 | /// cancellationToken
16 | /// IDisposable
17 | Task LockAsync(CancellationToken cancellationToken = default);
18 |
19 | ///
20 | /// Synchronously waits to enter the lock
21 | ///
22 | /// IDisposable
23 | IDisposable Lock();
24 | }
25 |
26 | ///
27 | /// A lock with async/await support
28 | /// Source from EF Core Repository:
29 | /// https://github.com/dotnet/efcore/blob/0d76bbf45a42148924b413ef8f37bf49c1ce10d3/src/EFCore/Internal/AsyncLock.cs
30 | /// Alternatives :
31 | /// https://github.com/bmbsqd/AsyncLock
32 | /// https://github.com/StephenCleary/AsyncEx
33 | /// Benchmark in Release/NoDebugging
34 | /// 1- EFCoreAsyncLock : 550
35 | /// 2- Bmbsqd.AsyncLock : 600
36 | /// 3- Nito.AsyncEx : 3300
37 | ///
38 | public sealed class SemaphoreSlimAsyncLock : IAsyncLock
39 | {
40 | //Instantiate a Semaphore with a value of 1. This means that only 1 thread can be granted access at a time.
41 | private readonly SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1);
42 |
43 | //Releaser instance for sync method to Release() inner AsyncLock._semaphoreSlim
44 | private readonly IDisposable _releaser;
45 |
46 | //Releaser instance for async method to Release() inner AsyncLock._semaphoreSlim
47 | private readonly Task _releaserTask;
48 |
49 | public SemaphoreSlimAsyncLock()
50 | {
51 | _releaser = new Releaser(this);
52 | _releaserTask = Task.FromResult(_releaser);
53 | }
54 |
55 | ///
56 | /// Asynchronously waits to enter the lock
57 | ///
58 | /// cancellationToken
59 | /// IDisposable
60 | public Task LockAsync(CancellationToken cancellationToken = default)
61 | {
62 | var wait = _semaphoreSlim.WaitAsync(cancellationToken);
63 |
64 | return wait.IsCompleted
65 | ? _releaserTask
66 | : wait.ContinueWith(
67 | (_, state) => ((SemaphoreSlimAsyncLock)state)._releaser,
68 | this, CancellationToken.None,
69 | TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
70 | }
71 |
72 | ///
73 | /// Synchronously waits to enter the lock
74 | ///
75 | /// IDisposable
76 | public IDisposable Lock()
77 | {
78 | _semaphoreSlim.Wait();
79 |
80 | return _releaser;
81 | }
82 |
83 | private readonly struct Releaser : IDisposable
84 | {
85 | private readonly SemaphoreSlimAsyncLock _asyncLock;
86 |
87 | internal Releaser(SemaphoreSlimAsyncLock asyncLock)
88 | {
89 | _asyncLock = asyncLock.NotNull(nameof(asyncLock));
90 | }
91 |
92 | public void Dispose()
93 | {
94 | _asyncLock._semaphoreSlim.Release();
95 | }
96 | }
97 | }
98 |
99 | ///
100 | /// Null async lock implementation of IAsyncLock
101 | ///
102 | public sealed class NullAsyncLock : IAsyncLock
103 | {
104 | private static readonly NullDisposable nullDisposable = new NullDisposable();
105 |
106 | ///
107 | public IDisposable Lock()
108 | {
109 | return nullDisposable;
110 | }
111 |
112 | ///
113 | public Task LockAsync(CancellationToken cancellationToken = default)
114 | {
115 | return Task.FromResult(nullDisposable);
116 | }
117 | }
118 |
119 | internal readonly struct NullDisposable : IDisposable
120 | {
121 | public void Dispose()
122 | {
123 | }
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/SmartCacheManager/Utilities/ObjectComparer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Linq;
4 |
5 | namespace SmartCacheManager.Utilities
6 | {
7 | ///
8 | /// Attribute to mark properties for deep comparison
9 | ///
10 | [AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
11 | public sealed class CompareAttribute : Attribute
12 | {
13 | }
14 |
15 | public static class ObjectComparer
16 | {
17 | ///
18 | /// Deep comparison
19 | /// Exported it from https://gist.github.com/danielkillyevo/5443439 and imporved
20 | ///
21 | ///
22 | ///
23 | ///
24 | public static bool DeepEquals(object left, object right)
25 | {
26 | //Compare the references
27 | if (ReferenceEquals(left, right) || Equals(left, right))
28 | return true;
29 |
30 | if (left is null || right is null)
31 | return false;
32 |
33 | //Compare the types
34 | if (left.GetType() != right.GetType())
35 | return false;
36 |
37 | //Get all property infos of the right object
38 | var propertyInfos = right.GetType().GetProperties()
39 | .Where(p => p.GetCustomAttributes(typeof(CompareAttribute), false).Any()).ToList();
40 |
41 | //Compare the property values of the left and right object
42 | foreach (var propertyInfo in propertyInfos)
43 | {
44 | var othersValue = propertyInfo.GetValue(right);
45 | var currentsValue = propertyInfo.GetValue(left);
46 |
47 | //Compare the references
48 | if (ReferenceEquals(othersValue, currentsValue) || Equals(othersValue, currentsValue))
49 | continue;
50 |
51 | if (othersValue is null || currentsValue is null)
52 | return false;
53 |
54 | if (othersValue.GetType() != currentsValue.GetType())
55 | return false;
56 |
57 | //Comparison if the property is an IEnumerable
58 | if (propertyInfo.PropertyType.IsEnumerable())
59 | {
60 | foreach (var currentsItem in (IEnumerable)currentsValue)
61 | {
62 | var oneMatchFound = false;
63 | foreach (var othersItem in (IEnumerable)othersValue)
64 | {
65 | //Recursively call the Equal method
66 | var areEqual = DeepEquals(currentsItem, othersItem);
67 | if (areEqual)
68 | {
69 | oneMatchFound = true;
70 | break;
71 | }
72 | }
73 | if (!oneMatchFound)
74 | return false;
75 | }
76 | }
77 | else
78 | {
79 | //Comparison for properties of a non collection type
80 |
81 | if (currentsValue.GetType().IsCustomType())
82 | {
83 | //values are complex/classes
84 | //that's why we have to recursively call the DeepEquals methods
85 | var areEqual = DeepEquals(currentsValue, othersValue);
86 | if (!areEqual)
87 | return false;
88 | }
89 | else
90 | {
91 | //values are primitive types
92 | var areEqual = currentsValue.Equals(othersValue);
93 | if (!areEqual)
94 | return false;
95 | }
96 | }
97 | }
98 |
99 | return true;
100 | }
101 |
102 | public static bool IsEnumerable(this Type type)
103 | {
104 | return type != typeof(string) && typeof(IEnumerable).IsAssignableFrom(type);
105 | }
106 |
107 | public static bool IsCustomType(this Type type)
108 | {
109 | //return type.Assembly.GetName().Name != "mscorlib";
110 | return type.IsCustomValueType() || type.IsCustomReferenceType();
111 | }
112 |
113 | public static bool IsCustomValueType(this Type type)
114 | {
115 | return type.IsValueType && !type.IsPrimitive && type.Namespace?.StartsWith("System", StringComparison.Ordinal) == false;
116 | }
117 |
118 | public static bool IsCustomReferenceType(this Type type)
119 | {
120 | return !type.IsValueType && !type.IsPrimitive && type.Namespace?.StartsWith("System", StringComparison.Ordinal) == false;
121 | }
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | bld/
21 | [Bb]in/
22 | [Oo]bj/
23 | [Ll]og/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | # Uncomment if you have tasks that create the project's static files in wwwroot
28 | #wwwroot/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # NUNIT
35 | *.VisualState.xml
36 | TestResult.xml
37 |
38 | # Build Results of an ATL Project
39 | [Dd]ebugPS/
40 | [Rr]eleasePS/
41 | dlldata.c
42 |
43 | # DNX
44 | project.lock.json
45 | project.fragment.lock.json
46 | artifacts/
47 |
48 | *_i.c
49 | *_p.c
50 | *_i.h
51 | *.ilk
52 | *.meta
53 | *.obj
54 | *.pch
55 | *.pdb
56 | *.pgc
57 | *.pgd
58 | *.rsp
59 | *.sbr
60 | *.tlb
61 | *.tli
62 | *.tlh
63 | *.tmp
64 | *.tmp_proj
65 | *.log
66 | *.vspscc
67 | *.vssscc
68 | .builds
69 | *.pidb
70 | *.svclog
71 | *.scc
72 |
73 | # Chutzpah Test files
74 | _Chutzpah*
75 |
76 | # Visual C++ cache files
77 | ipch/
78 | *.aps
79 | *.ncb
80 | *.opendb
81 | *.opensdf
82 | *.sdf
83 | *.cachefile
84 | *.VC.db
85 | *.VC.VC.opendb
86 |
87 | # Visual Studio profiler
88 | *.psess
89 | *.vsp
90 | *.vspx
91 | *.sap
92 |
93 | # TFS 2012 Local Workspace
94 | $tf/
95 |
96 | # Guidance Automation Toolkit
97 | *.gpState
98 |
99 | # ReSharper is a .NET coding add-in
100 | _ReSharper*/
101 | *.[Rr]e[Ss]harper
102 | *.DotSettings.user
103 |
104 | # JustCode is a .NET coding add-in
105 | .JustCode
106 |
107 | # TeamCity is a build add-in
108 | _TeamCity*
109 |
110 | # DotCover is a Code Coverage Tool
111 | *.dotCover
112 |
113 | # NCrunch
114 | _NCrunch_*
115 | .*crunch*.local.xml
116 | nCrunchTemp_*
117 |
118 | # MightyMoose
119 | *.mm.*
120 | AutoTest.Net/
121 |
122 | # Web workbench (sass)
123 | .sass-cache/
124 |
125 | # Installshield output folder
126 | [Ee]xpress/
127 |
128 | # DocProject is a documentation generator add-in
129 | DocProject/buildhelp/
130 | DocProject/Help/*.HxT
131 | DocProject/Help/*.HxC
132 | DocProject/Help/*.hhc
133 | DocProject/Help/*.hhk
134 | DocProject/Help/*.hhp
135 | DocProject/Help/Html2
136 | DocProject/Help/html
137 |
138 | # Click-Once directory
139 | publish/
140 |
141 | # Publish Web Output
142 | *.[Pp]ublish.xml
143 | *.azurePubxml
144 | # TODO: Comment the next line if you want to checkin your web deploy settings
145 | # but database connection strings (with potential passwords) will be unencrypted
146 | #*.pubxml
147 | *.publishproj
148 |
149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
150 | # checkin your Azure Web App publish settings, but sensitive information contained
151 | # in these scripts will be unencrypted
152 | PublishScripts/
153 |
154 | # NuGet Packages
155 | *.nupkg
156 | # The packages folder can be ignored because of Package Restore
157 | **/packages/*
158 | # except build/, which is used as an MSBuild target.
159 | !**/packages/build/
160 | # Uncomment if necessary however generally it will be regenerated when needed
161 | #!**/packages/repositories.config
162 | # NuGet v3's project.json files produces more ignoreable files
163 | *.nuget.props
164 | *.nuget.targets
165 |
166 | # Microsoft Azure Build Output
167 | csx/
168 | *.build.csdef
169 |
170 | # Microsoft Azure Emulator
171 | ecf/
172 | rcf/
173 |
174 | # Windows Store app package directories and files
175 | AppPackages/
176 | BundleArtifacts/
177 | Package.StoreAssociation.xml
178 | _pkginfo.txt
179 |
180 | # Visual Studio cache files
181 | # files ending in .cache can be ignored
182 | *.[Cc]ache
183 | # but keep track of directories ending in .cache
184 | !*.[Cc]ache/
185 |
186 | # Others
187 | ClientBin/
188 | ~$*
189 | *~
190 | *.dbmdl
191 | *.dbproj.schemaview
192 | *.jfm
193 | *.pfx
194 | *.publishsettings
195 | node_modules/
196 | orleans.codegen.cs
197 |
198 | # Since there are multiple workflows, uncomment next line to ignore bower_components
199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
200 | #bower_components/
201 |
202 | # RIA/Silverlight projects
203 | Generated_Code/
204 |
205 | # Backup & report files from converting an old project file
206 | # to a newer Visual Studio version. Backup files are not needed,
207 | # because we have git ;-)
208 | _UpgradeReport_Files/
209 | Backup*/
210 | UpgradeLog*.XML
211 | UpgradeLog*.htm
212 |
213 | # SQL Server files
214 | *.mdf
215 | *.ldf
216 |
217 | # Business Intelligence projects
218 | *.rdl.data
219 | *.bim.layout
220 | *.bim_*.settings
221 |
222 | # Microsoft Fakes
223 | FakesAssemblies/
224 |
225 | # GhostDoc plugin setting file
226 | *.GhostDoc.xml
227 |
228 | # Node.js Tools for Visual Studio
229 | .ntvs_analysis.dat
230 |
231 | # Visual Studio 6 build log
232 | *.plg
233 |
234 | # Visual Studio 6 workspace options file
235 | *.opt
236 |
237 | # Visual Studio LightSwitch build output
238 | **/*.HTMLClient/GeneratedArtifacts
239 | **/*.DesktopClient/GeneratedArtifacts
240 | **/*.DesktopClient/ModelManifest.xml
241 | **/*.Server/GeneratedArtifacts
242 | **/*.Server/ModelManifest.xml
243 | _Pvt_Extensions
244 |
245 | # Paket dependency manager
246 | .paket/paket.exe
247 | paket-files/
248 |
249 | # FAKE - F# Make
250 | .fake/
251 |
252 | # JetBrains Rider
253 | .idea/
254 | *.sln.iml
255 |
256 | # CodeRush
257 | .cr/
258 |
259 | # Python Tools for Visual Studio (PTVS)
260 | __pycache__/
261 | *.pyc
--------------------------------------------------------------------------------
/SmartCacheManager.Demo/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js:
--------------------------------------------------------------------------------
1 | // Unobtrusive validation support library for jQuery and jQuery Validate
2 | // Copyright (c) .NET Foundation. All rights reserved.
3 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
4 | // @version v3.2.11
5 | !function(a){"function"==typeof define&&define.amd?define("jquery.validate.unobtrusive",["jquery-validation"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery-validation")):jQuery.validator.unobtrusive=a(jQuery)}(function(a){function e(a,e,n){a.rules[e]=n,a.message&&(a.messages[e]=a.message)}function n(a){return a.replace(/^\s+|\s+$/g,"").split(/\s*,\s*/g)}function t(a){return a.replace(/([!"#$%&'()*+,.\/:;<=>?@\[\\\]^`{|}~])/g,"\\$1")}function r(a){return a.substr(0,a.lastIndexOf(".")+1)}function i(a,e){return 0===a.indexOf("*.")&&(a=a.replace("*.",e)),a}function o(e,n){var r=a(this).find("[data-valmsg-for='"+t(n[0].name)+"']"),i=r.attr("data-valmsg-replace"),o=i?a.parseJSON(i)!==!1:null;r.removeClass("field-validation-valid").addClass("field-validation-error"),e.data("unobtrusiveContainer",r),o?(r.empty(),e.removeClass("input-validation-error").appendTo(r)):e.hide()}function d(e,n){var t=a(this).find("[data-valmsg-summary=true]"),r=t.find("ul");r&&r.length&&n.errorList.length&&(r.empty(),t.addClass("validation-summary-errors").removeClass("validation-summary-valid"),a.each(n.errorList,function(){a(" ").html(this.message).appendTo(r)}))}function s(e){var n=e.data("unobtrusiveContainer");if(n){var t=n.attr("data-valmsg-replace"),r=t?a.parseJSON(t):null;n.addClass("field-validation-valid").removeClass("field-validation-error"),e.removeData("unobtrusiveContainer"),r&&n.empty()}}function l(e){var n=a(this),t="__jquery_unobtrusive_validation_form_reset";if(!n.data(t)){n.data(t,!0);try{n.data("validator").resetForm()}finally{n.removeData(t)}n.find(".validation-summary-errors").addClass("validation-summary-valid").removeClass("validation-summary-errors"),n.find(".field-validation-error").addClass("field-validation-valid").removeClass("field-validation-error").removeData("unobtrusiveContainer").find(">*").removeData("unobtrusiveContainer")}}function u(e){var n=a(e),t=n.data(v),r=a.proxy(l,e),i=f.unobtrusive.options||{},u=function(n,t){var r=i[n];r&&a.isFunction(r)&&r.apply(e,t)};return t||(t={options:{errorClass:i.errorClass||"input-validation-error",errorElement:i.errorElement||"span",errorPlacement:function(){o.apply(e,arguments),u("errorPlacement",arguments)},invalidHandler:function(){d.apply(e,arguments),u("invalidHandler",arguments)},messages:{},rules:{},success:function(){s.apply(e,arguments),u("success",arguments)}},attachValidation:function(){n.off("reset."+v,r).on("reset."+v,r).validate(this.options)},validate:function(){return n.validate(),n.valid()}},n.data(v,t)),t}var m,f=a.validator,v="unobtrusiveValidation";return f.unobtrusive={adapters:[],parseElement:function(e,n){var t,r,i,o=a(e),d=o.parents("form")[0];d&&(t=u(d),t.options.rules[e.name]=r={},t.options.messages[e.name]=i={},a.each(this.adapters,function(){var n="data-val-"+this.name,t=o.attr(n),s={};void 0!==t&&(n+="-",a.each(this.params,function(){s[this]=o.attr(n+this)}),this.adapt({element:e,form:d,message:t,params:s,rules:r,messages:i}))}),a.extend(r,{__dummy__:!0}),n||t.attachValidation())},parse:function(e){var n=a(e),t=n.parents().addBack().filter("form").add(n.find("form")).has("[data-val=true]");n.find("[data-val=true]").each(function(){f.unobtrusive.parseElement(this,!0)}),t.each(function(){var a=u(this);a&&a.attachValidation()})}},m=f.unobtrusive.adapters,m.add=function(a,e,n){return n||(n=e,e=[]),this.push({name:a,params:e,adapt:n}),this},m.addBool=function(a,n){return this.add(a,function(t){e(t,n||a,!0)})},m.addMinMax=function(a,n,t,r,i,o){return this.add(a,[i||"min",o||"max"],function(a){var i=a.params.min,o=a.params.max;i&&o?e(a,r,[i,o]):i?e(a,n,i):o&&e(a,t,o)})},m.addSingleVal=function(a,n,t){return this.add(a,[n||"val"],function(r){e(r,t||a,r.params[n])})},f.addMethod("__dummy__",function(a,e,n){return!0}),f.addMethod("regex",function(a,e,n){var t;return!!this.optional(e)||(t=new RegExp(n).exec(a),t&&0===t.index&&t[0].length===a.length)}),f.addMethod("nonalphamin",function(a,e,n){var t;return n&&(t=a.match(/\W/g),t=t&&t.length>=n),t}),f.methods.extension?(m.addSingleVal("accept","mimtype"),m.addSingleVal("extension","extension")):m.addSingleVal("extension","extension","accept"),m.addSingleVal("regex","pattern"),m.addBool("creditcard").addBool("date").addBool("digits").addBool("email").addBool("number").addBool("url"),m.addMinMax("length","minlength","maxlength","rangelength").addMinMax("range","min","max","range"),m.addMinMax("minlength","minlength").addMinMax("maxlength","minlength","maxlength"),m.add("equalto",["other"],function(n){var o=r(n.element.name),d=n.params.other,s=i(d,o),l=a(n.form).find(":input").filter("[name='"+t(s)+"']")[0];e(n,"equalTo",l)}),m.add("required",function(a){"INPUT"===a.element.tagName.toUpperCase()&&"CHECKBOX"===a.element.type.toUpperCase()||e(a,"required",!0)}),m.add("remote",["url","type","additionalfields"],function(o){var d={url:o.params.url,type:o.params.type||"GET",data:{}},s=r(o.element.name);a.each(n(o.params.additionalfields||o.element.name),function(e,n){var r=i(n,s);d.data[r]=function(){var e=a(o.form).find(":input").filter("[name='"+t(r)+"']");return e.is(":checkbox")?e.filter(":checked").val()||e.filter(":hidden").val()||"":e.is(":radio")?e.filter(":checked").val()||"":e.val()}}),e(o,"remote",d)}),m.add("password",["min","nonalphamin","regex"],function(a){a.params.min&&e(a,"minlength",a.params.min),a.params.nonalphamin&&e(a,"nonalphamin",a.params.nonalphamin),a.params.regex&&e(a,"regex",a.params.regex)}),m.add("fileextensions",["extensions"],function(a){e(a,"extension",a.params.extensions)}),a(function(){f.unobtrusive.parse(document)}),f.unobtrusive});
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://www.nuget.org/packages/SmartCacheManager)
2 | [](https://opensource.org/licenses/MIT)
3 | [](https://github.com/mjebrahimi/SmartCacheManager)
4 |
5 | # SmartCacheManager
6 | **SmartCacheManager** is a response caching module which cache objects with resilient and variable expiration time that is useful for caching the result of web services and other calculations.
7 |
8 | ## Features
9 | - • Caching objects using [EasyCaching](https://github.com/dotnetcore/EasyCaching)
10 | - Supports various caching providers such as **In-Memory**, **Redis**, **Memcached**, **Disk**, **Sqlite**, ...
11 | - • Serializing data
12 | - Supports various caching serializers such as **BinaryFormatter**, **MessagePack**, **Json**, **Protobuf**, ...
13 | - • Settings for cache expiration time by specifying the **minimum** and **maximum** duration
14 | - • Increasing and decreasing cache expiration time automatically based on the date of search and RPM
15 | - • Calculates the **RPM (request per minute)** of a search
16 | - • Ability to **limit** searches based on maximum limit count in a specified time range
17 | - • Ability to configuring cache-key dynamically based on your needs
18 | - • **Async** support with cancellation tokens
19 | - • **Thread-safety** support
20 | - • Flexible, lightweight and **highly customizable**
21 | - • Logs **Sensitive Data** using [Serilog](https://github.com/serilog/serilog)
22 | - Logs all errors occures in services
23 | - Logs all steps of the search and caching process
24 | - Logs all incomming requests to this module
25 | - Logs all outgoing request from this module to external services
26 | - Logs the execution time of service methods using [MethodTimer.Fody](https://github.com/Fody/MethodTimer)
27 | - Logs to the **Sql-Server** (by default) and supports **Console**, **Debug**, **File**, **EventViwer**, **Seq**, **ElasticSearch** and ...
28 |
29 |
30 | ## Getting Started
31 |
32 | ### 1. Install Package
33 |
34 | > For the .NET Core 2.2 use [v1.0.0](https://www.nuget.org/packages/SmartCacheManager/1.0.0) and for the .NET Core 3.1 use [v2.0.0](https://www.nuget.org/packages/SmartCacheManager/2.0.0) of SmartCacheManager:
35 | ```
36 | PM> Install-Package SmartCacheManager
37 | ```
38 |
39 | ### 2. Implement your own cache-manager service
40 |
41 | For example: A simple implementation of a FlightCacheManager could be like this:
42 |
43 | ```csharp
44 | public class FlightCacheManager : SmartCacheManager, IFlightCacheManager
45 | {
46 | public FlightCacheManager(ICacheManager cacheManager, ILoggerFactory loggerFactory, ISystemClock systemClock,
47 | ICacheSettingService cacheSettingService, ISearchHistoryService searchHistoryService)
48 | : base(cacheManager, loggerFactory, systemClock, cacheSettingService, searchHistoryService)
49 | {
50 | }
51 |
52 | protected override string GenerateSearchHistoryKey(FlightSearchModel searchModel)
53 | {
54 | return GenerateSearchResultKey(searchModel);
55 | }
56 |
57 | protected override string GenerateSearchResultKey(FlightSearchModel searchModel)
58 | {
59 | return $"{searchModel.Origin}-{searchModel.Destination}-{searchModel.SearchDate.ToString("yyyy-MM-dd")}";
60 | }
61 |
62 | protected override DateTime GetSearchDate(FlightSearchModel searchModel)
63 | {
64 | return searchModel.SearchDate;
65 | }
66 | }
67 |
68 | public class FlightSearchModel
69 | {
70 | public string Origin { get; set; }
71 | public string Destination { get; set; }
72 | public DateTime SearchDate { get; set; }
73 | }
74 | ```
75 |
76 | ### 3. Register services
77 |
78 | Register SmartCacheManager in your DI container by calling `services.AddSmartCacheManager()` and also, register your own cache-manager implementation:
79 |
80 | ```csharp
81 | public static void ConfigureServices(IServiceCollection services)
82 | {
83 | services.AddSmartCacheManager(opt => opt.UseSqlServer("Data Source=.;Initial Catalog=CacheManageDb;Integrated Security=true");
84 | services.AddScoped();
85 | //...
86 | }
87 | ```
88 |
89 | ### 4. Initialize it
90 |
91 | Initialize SmartCacheManager using `app.InitialSmartCacheManager()` in the `Configure` method of `Startup.cs`:
92 |
93 | ```csharp
94 | public static void Configure(IApplicationBuilder app, IWebHostEnvironment env)
95 | {
96 | app.InitialSmartCacheManager();
97 | //...
98 | ```
99 |
100 | ### 5. Use it
101 |
102 | Inject your own implementation of cache-manager to your services and use it by calling `SearchFromCacheAsync()` method:
103 |
104 | ```csharp
105 | var supplier = FlightSupplierts.Amadeus; // Supplier could be any type: string, enum or ...
106 | var searchModel = new FlightSearchModel
107 | {
108 | Origin = "AMS",
109 | Destination = "HAM",
110 | SearchDate = DateTime.Now.AddDays(10)
111 | };
112 |
113 | var result = await _flightCacheManager.SearchFromCacheAsync(searchModel, supplier, () =>
114 | {
115 | return _amadeusFlightService.SearchAsync(searchModel);
116 | });
117 | ```
118 |
119 | ## Contributing
120 |
121 | Create an [issue]() if you find a bug or have a suggestion or question. If you want to develop this project:
122 |
123 | 1. Fork it!
124 | 2. Create your feature branch: `git checkout -b my-new-feature`
125 | 3. Commit your changes: `git commit -am 'Add some feature'`
126 | 4. Push to the branch: `git push origin my-new-feature`
127 | 5. Submit a pull request
128 |
129 | ## Give a Star! ⭐️
130 |
131 | If you find this repository useful, please give it a star. Thanks!
132 |
133 | ## License/Copyright
134 |
135 | SmartCacheManager is Copyright © 2020 [Mohammd Javad Ebrahimi](https://github.com/mjebrahimi) under the [GNU GPLv3 License](/LICENSE).
136 |
--------------------------------------------------------------------------------
/SmartCacheManager/Data/DataConfigurationExtensions.cs:
--------------------------------------------------------------------------------
1 | using SmartCacheManager.Caching;
2 | using SmartCacheManager.Utilities;
3 | using Microsoft.EntityFrameworkCore;
4 | using Microsoft.Extensions.DependencyInjection;
5 | using Microsoft.Extensions.DependencyInjection.Extensions;
6 | using System;
7 | using NeuroSpeech.EFCoreLiveMigration;
8 | using System.Linq;
9 |
10 | namespace SmartCacheManager.Data
11 | {
12 | public static class DataConfigurationExtensions
13 | {
14 | ///
15 | /// Add SmartCacheManager DbContext and Stores to services
16 | ///
17 | /// services
18 | /// Action to configure SmartCacheManagerDbContext
19 | /// IServiceCollection
20 | public static IServiceCollection AddSmartCacheManagerDbContext(this IServiceCollection services, Action optionsAction)
21 | {
22 | return services.AddSmartCacheManagerDbContext(optionsAction);
23 | }
24 |
25 | ///
26 | /// Add SmartCacheManager DbContext and Stores to services
27 | ///
28 | /// Type of cache manager dbContext
29 | /// services
30 | /// Action to configure SmartCacheManagerDbContext
31 | /// IServiceCollection
32 | public static IServiceCollection AddSmartCacheManagerDbContext(this IServiceCollection services, Action optionsAction)
33 | where TDbContext : DbContext
34 | {
35 | services.NotNull(nameof(services));
36 | optionsAction.NotNull(nameof(optionsAction));
37 |
38 | services.AddDbContext(optionsAction);
39 |
40 | return services.AddSmartCacheManagerStores();
41 | }
42 |
43 | ///
44 | /// Add stores for specified TDbContext to services
45 | ///
46 | /// TDbContext
47 | /// services
48 | /// IServiceCollection
49 | public static IServiceCollection AddSmartCacheManagerStores(this IServiceCollection services) where TDbContext : DbContext
50 | {
51 | services.NotNull(nameof(services));
52 |
53 | services.AddSmartCacheManagerStore();
54 | services.AddSmartCacheManagerStore();
55 | services.AddSmartCacheManagerStore();
56 | services.AddSmartCacheManagerStore();
57 |
58 | return services;
59 | }
60 |
61 | ///
62 | /// Add a store for specified TEntity and TDbContext to services
63 | ///
64 | /// TDbContext
65 | ///
66 | /// services
67 | /// IServiceCollection
68 | public static IServiceCollection AddSmartCacheManagerStore(this IServiceCollection services)
69 | where TDbContext : DbContext
70 | where TEntity : class, new()
71 | {
72 | services.NotNull(nameof(services));
73 |
74 | services.TryAddScoped>(serviceProvider =>
75 | {
76 | var dbContext = serviceProvider.GetRequiredService();
77 | var cacheManager = serviceProvider.GetRequiredService();
78 |
79 | return new GenericStore(dbContext, cacheManager);
80 | });
81 |
82 | return services;
83 | }
84 |
85 | ///
86 | /// Migrate database to latest and seed data
87 | ///
88 | /// Type of cache manager dbContext
89 | /// serviceProvider
90 | public static void MigrateAndSeedData(this IServiceProvider serviceProvider)
91 | where TDbContext : DbContext
92 | {
93 | serviceProvider.NotNull(nameof(serviceProvider));
94 | using (var scope = serviceProvider.CreateScope())
95 | {
96 | using (var dbContext = scope.ServiceProvider.GetRequiredService())
97 | {
98 | //Auto migrate dbContext to the latest version of model changes on the fly
99 | if (dbContext.Database.IsSqlServer())
100 | MigrationHelper.ForSqlServer(dbContext).Migrate();
101 |
102 | //Seed initial data
103 | var cacheSettingStore = scope.ServiceProvider.GetRequiredService>();
104 | if (!cacheSettingStore.Table.Any())
105 | {
106 | cacheSettingStore.Add(new CacheSetting
107 | {
108 | MinSearchDiffHours = 1 * 24, //24 (1 day)
109 | MaxSearchDiffHours = 30 * 24, //720 (30 day)
110 | MinCacheMinutes = 5,
111 | MaxCacheMinutes = 24 * 60, //1440 (1 day)
112 | OverSearchDiffHours = 90 * 24, //2160 (90 day)
113 | RecentSearchMinimumRPM = 1,
114 | RecentSearchMaxmimumRPM = 20,
115 | RpmDurationMinutes = 2,
116 | SupplierType = null,
117 | });
118 | }
119 | }
120 | }
121 | }
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/SmartCacheManager/ConfigurationExtensions.cs:
--------------------------------------------------------------------------------
1 | using SmartCacheManager.Caching.EasyCaching;
2 | using SmartCacheManager.Data;
3 | using SmartCacheManager.Services;
4 | using SmartCacheManager.Utilities;
5 | using Microsoft.AspNetCore.Builder;
6 | using Microsoft.EntityFrameworkCore;
7 | using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal;
8 | using Microsoft.Extensions.DependencyInjection;
9 | using System;
10 | using SmartCacheManager.Logging.Serilog;
11 |
12 | namespace SmartCacheManager
13 | {
14 | public static class ConfigurationExtensions
15 | {
16 | ///
17 | /// Add all required services for SmartCacheManager like Data/Stores and EasyCaching and Serilog, ...
18 | ///
19 | /// services
20 | /// Action to configure CacheManagerDbContext
21 | /// Action to configure EasyCachingOptions
22 | /// Action to configure SerilogOptions
23 | /// Determine whether async lock is thread safety
24 | /// Determine use DatabaseSearchHistoryService[false] or CacheSearchHistoryService[true]
25 | /// IServiceCollection
26 | public static IServiceCollection AddSmartCacheManager(this IServiceCollection services, Action dbContextConfigure,
27 | Action cachingConfigure = null, Action loggerConfigure = null, bool enableThreadSafety = false, bool useDatabaseSearchHistory = false)
28 | {
29 | return services.AddSmartCacheManager(dbContextConfigure, cachingConfigure, loggerConfigure, enableThreadSafety, useDatabaseSearchHistory);
30 | }
31 |
32 | ///
33 | /// Add all required services for SmartCacheManager like Data/Stores and EasyCaching and Serilog, ...
34 | ///
35 | /// Type of cache manager dbContext
36 | /// services
37 | /// Action to configure CacheManagerDbContext
38 | /// Action to configure EasyCachingOptions
39 | /// Action to configure SerilogOptions
40 | /// Determine whether async lock is thread safety
41 | /// Determine use DatabaseSearchHistoryService[false] or CacheSearchHistoryService[true]
42 | /// IServiceCollection
43 | public static IServiceCollection AddSmartCacheManager(this IServiceCollection services, Action dbContextConfigure,
44 | Action cachingConfigure = null, Action loggerConfigure = null, bool enableThreadSafety = false, bool useDatabaseSearchHistory = false)
45 | where TDbContext : DbContext
46 | {
47 | services.NotNull(nameof(services));
48 | services.NotNull(nameof(dbContextConfigure));
49 |
50 | services.AddSystemClock();
51 | services.AddSmartCacheManagerDbContext(dbContextConfigure);
52 |
53 | services.AddSettingServices();
54 | services.AddSearchHistoryService(useDatabaseSearchHistory);
55 | services.AddAsyncLockScoped(enableThreadSafety);
56 |
57 | services.AddEasyCachingCacheManager(cachingConfigure);
58 |
59 | var serilogConfigure = SetDefaultSqlServerConnectionForSerilog(dbContextConfigure, loggerConfigure);
60 | services.AddSerilogLogger(serilogConfigure);
61 |
62 | return services;
63 | }
64 |
65 | ///
66 | /// Set default sql connection string of serilog configure from dbContext configure if SqlServer used;
67 | ///
68 | /// Action to configure CacheManagerDbContext
69 | /// Action to configure SerilogOptions
70 | ///
71 | private static Action SetDefaultSqlServerConnectionForSerilog(Action dbContextConfigure, Action loggerConfigure = null)
72 | {
73 | return opt =>
74 | {
75 | var optionsBuilder = new DbContextOptionsBuilder();
76 | dbContextConfigure(optionsBuilder);
77 |
78 | var sqlServerOptionsExtension = optionsBuilder.Options.FindExtension();
79 | if (sqlServerOptionsExtension != null && opt.SqlConnectionString == null)
80 | opt.SqlConnectionString = sqlServerOptionsExtension.ConnectionString;
81 |
82 | loggerConfigure?.Invoke(opt);
83 | };
84 | }
85 |
86 | ///
87 | /// Initialize database and seed initial data
88 | ///
89 | /// ApplicationBuilder
90 | public static void InitializeSmartCacheManager(this IApplicationBuilder app)
91 | {
92 | app.InitializeSmartCacheManager();
93 | }
94 |
95 | ///
96 | /// Initialize database and seed initial data
97 | ///
98 | /// Type of cache manager dbContext
99 | /// ApplicationBuilder
100 | public static void InitializeSmartCacheManager(this IApplicationBuilder app)
101 | where TDbContext : DbContext
102 | {
103 | app.ApplicationServices.MigrateAndSeedData();
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/SmartCacheManager.Demo/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap Reboot v4.3.1 (https://getbootstrap.com/)
3 | * Copyright 2011-2019 The Bootstrap Authors
4 | * Copyright 2011-2019 Twitter, Inc.
5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
7 | */
8 | *,
9 | *::before,
10 | *::after {
11 | box-sizing: border-box;
12 | }
13 |
14 | html {
15 | font-family: sans-serif;
16 | line-height: 1.15;
17 | -webkit-text-size-adjust: 100%;
18 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
19 | }
20 |
21 | article, aside, figcaption, figure, footer, header, hgroup, main, nav, section {
22 | display: block;
23 | }
24 |
25 | body {
26 | margin: 0;
27 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
28 | font-size: 1rem;
29 | font-weight: 400;
30 | line-height: 1.5;
31 | color: #212529;
32 | text-align: left;
33 | background-color: #fff;
34 | }
35 |
36 | [tabindex="-1"]:focus {
37 | outline: 0 !important;
38 | }
39 |
40 | hr {
41 | box-sizing: content-box;
42 | height: 0;
43 | overflow: visible;
44 | }
45 |
46 | h1, h2, h3, h4, h5, h6 {
47 | margin-top: 0;
48 | margin-bottom: 0.5rem;
49 | }
50 |
51 | p {
52 | margin-top: 0;
53 | margin-bottom: 1rem;
54 | }
55 |
56 | abbr[title],
57 | abbr[data-original-title] {
58 | text-decoration: underline;
59 | -webkit-text-decoration: underline dotted;
60 | text-decoration: underline dotted;
61 | cursor: help;
62 | border-bottom: 0;
63 | -webkit-text-decoration-skip-ink: none;
64 | text-decoration-skip-ink: none;
65 | }
66 |
67 | address {
68 | margin-bottom: 1rem;
69 | font-style: normal;
70 | line-height: inherit;
71 | }
72 |
73 | ol,
74 | ul,
75 | dl {
76 | margin-top: 0;
77 | margin-bottom: 1rem;
78 | }
79 |
80 | ol ol,
81 | ul ul,
82 | ol ul,
83 | ul ol {
84 | margin-bottom: 0;
85 | }
86 |
87 | dt {
88 | font-weight: 700;
89 | }
90 |
91 | dd {
92 | margin-bottom: .5rem;
93 | margin-left: 0;
94 | }
95 |
96 | blockquote {
97 | margin: 0 0 1rem;
98 | }
99 |
100 | b,
101 | strong {
102 | font-weight: bolder;
103 | }
104 |
105 | small {
106 | font-size: 80%;
107 | }
108 |
109 | sub,
110 | sup {
111 | position: relative;
112 | font-size: 75%;
113 | line-height: 0;
114 | vertical-align: baseline;
115 | }
116 |
117 | sub {
118 | bottom: -.25em;
119 | }
120 |
121 | sup {
122 | top: -.5em;
123 | }
124 |
125 | a {
126 | color: #007bff;
127 | text-decoration: none;
128 | background-color: transparent;
129 | }
130 |
131 | a:hover {
132 | color: #0056b3;
133 | text-decoration: underline;
134 | }
135 |
136 | a:not([href]):not([tabindex]) {
137 | color: inherit;
138 | text-decoration: none;
139 | }
140 |
141 | a:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus {
142 | color: inherit;
143 | text-decoration: none;
144 | }
145 |
146 | a:not([href]):not([tabindex]):focus {
147 | outline: 0;
148 | }
149 |
150 | pre,
151 | code,
152 | kbd,
153 | samp {
154 | font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
155 | font-size: 1em;
156 | }
157 |
158 | pre {
159 | margin-top: 0;
160 | margin-bottom: 1rem;
161 | overflow: auto;
162 | }
163 |
164 | figure {
165 | margin: 0 0 1rem;
166 | }
167 |
168 | img {
169 | vertical-align: middle;
170 | border-style: none;
171 | }
172 |
173 | svg {
174 | overflow: hidden;
175 | vertical-align: middle;
176 | }
177 |
178 | table {
179 | border-collapse: collapse;
180 | }
181 |
182 | caption {
183 | padding-top: 0.75rem;
184 | padding-bottom: 0.75rem;
185 | color: #6c757d;
186 | text-align: left;
187 | caption-side: bottom;
188 | }
189 |
190 | th {
191 | text-align: inherit;
192 | }
193 |
194 | label {
195 | display: inline-block;
196 | margin-bottom: 0.5rem;
197 | }
198 |
199 | button {
200 | border-radius: 0;
201 | }
202 |
203 | button:focus {
204 | outline: 1px dotted;
205 | outline: 5px auto -webkit-focus-ring-color;
206 | }
207 |
208 | input,
209 | button,
210 | select,
211 | optgroup,
212 | textarea {
213 | margin: 0;
214 | font-family: inherit;
215 | font-size: inherit;
216 | line-height: inherit;
217 | }
218 |
219 | button,
220 | input {
221 | overflow: visible;
222 | }
223 |
224 | button,
225 | select {
226 | text-transform: none;
227 | }
228 |
229 | select {
230 | word-wrap: normal;
231 | }
232 |
233 | button,
234 | [type="button"],
235 | [type="reset"],
236 | [type="submit"] {
237 | -webkit-appearance: button;
238 | }
239 |
240 | button:not(:disabled),
241 | [type="button"]:not(:disabled),
242 | [type="reset"]:not(:disabled),
243 | [type="submit"]:not(:disabled) {
244 | cursor: pointer;
245 | }
246 |
247 | button::-moz-focus-inner,
248 | [type="button"]::-moz-focus-inner,
249 | [type="reset"]::-moz-focus-inner,
250 | [type="submit"]::-moz-focus-inner {
251 | padding: 0;
252 | border-style: none;
253 | }
254 |
255 | input[type="radio"],
256 | input[type="checkbox"] {
257 | box-sizing: border-box;
258 | padding: 0;
259 | }
260 |
261 | input[type="date"],
262 | input[type="time"],
263 | input[type="datetime-local"],
264 | input[type="month"] {
265 | -webkit-appearance: listbox;
266 | }
267 |
268 | textarea {
269 | overflow: auto;
270 | resize: vertical;
271 | }
272 |
273 | fieldset {
274 | min-width: 0;
275 | padding: 0;
276 | margin: 0;
277 | border: 0;
278 | }
279 |
280 | legend {
281 | display: block;
282 | width: 100%;
283 | max-width: 100%;
284 | padding: 0;
285 | margin-bottom: .5rem;
286 | font-size: 1.5rem;
287 | line-height: inherit;
288 | color: inherit;
289 | white-space: normal;
290 | }
291 |
292 | progress {
293 | vertical-align: baseline;
294 | }
295 |
296 | [type="number"]::-webkit-inner-spin-button,
297 | [type="number"]::-webkit-outer-spin-button {
298 | height: auto;
299 | }
300 |
301 | [type="search"] {
302 | outline-offset: -2px;
303 | -webkit-appearance: none;
304 | }
305 |
306 | [type="search"]::-webkit-search-decoration {
307 | -webkit-appearance: none;
308 | }
309 |
310 | ::-webkit-file-upload-button {
311 | font: inherit;
312 | -webkit-appearance: button;
313 | }
314 |
315 | output {
316 | display: inline-block;
317 | }
318 |
319 | summary {
320 | display: list-item;
321 | cursor: pointer;
322 | }
323 |
324 | template {
325 | display: none;
326 | }
327 |
328 | [hidden] {
329 | display: none !important;
330 | }
331 | /*# sourceMappingURL=bootstrap-reboot.css.map */
--------------------------------------------------------------------------------
/SmartCacheManager/Logging/SerilogLogger/SerilogOptions.cs:
--------------------------------------------------------------------------------
1 | using Serilog;
2 | using Serilog.Events;
3 | using Serilog.Sinks.MSSqlServer;
4 |
5 | namespace SmartCacheManager.Logging.Serilog
6 | {
7 | ///
8 | /// Options to configure Serilog
9 | ///
10 | public class SerilogOptions
11 | {
12 | ///
13 | /// Gets or sets a value indicating whether enable timing logging. Default is true
14 | ///
15 | public bool LogTimingEnabled { get; set; } = true;
16 |
17 | ///
18 | /// Gets or sets a value indicating whether enable cache logging. Default is true
19 | ///
20 | public bool LogCacheEnabled { get; set; } = true;
21 |
22 | ///
23 | /// Gets or sets a value indicating whether enable logging. Default is true
24 | ///
25 | public bool LoggingEnabled { get; set; } = true;
26 |
27 | ///
28 | /// Disable file and console sinks when evnironment is not development. Default false
29 | ///
30 | public bool IsEnvDevelopment { get; set; } = false;
31 |
32 | #region Enrichers
33 | ///
34 | /// Enable CorrelationId enricher. Default is true
35 | ///
36 | public bool WithCorrelationId { get; set; } = true;
37 |
38 | ///
39 | /// Enable MemoryUsage enricher. Default is true
40 | ///
41 | public bool WithMemoryUsage { get; set; } = true;
42 |
43 | ///
44 | /// Enable ExceptionStackTraceHash enricher. Default is true
45 | ///
46 | public bool WithExceptionStackTraceHash { get; set; } = true;
47 |
48 | ///
49 | /// Enable ThreadId enricher. Default is true
50 | ///
51 | public bool WithThreadId { get; set; } = true;
52 |
53 | #endregion
54 |
55 | #region Console Sink
56 | ///
57 | /// Enable Console sink. Default is false
58 | ///
59 | public bool EnableConsole { get; set; } = false;
60 |
61 | ///
62 | /// The minimum level for events passed through the sink. Default is LogEventLevel.Information
63 | ///
64 | public LogEventLevel ConsoleMinimumLevel { get; set; } = LogEventLevel.Information;
65 | #endregion
66 |
67 | #region Debug Sink
68 | ///
69 | /// Enable Debug sink. Default is false
70 | ///
71 | public bool EnableDebug { get; set; } = false;
72 |
73 | ///
74 | /// The minimum level for events passed through the sink. Default is LogEventLevel.Information
75 | ///
76 | public LogEventLevel DebugMinimumLevel { get; set; } = LogEventLevel.Information;
77 | #endregion
78 |
79 | #region File Sink
80 | ///
81 | /// Enable File sink
82 | ///
83 | public bool EnableFile { get; set; } = false;
84 |
85 | ///
86 | /// Directory for log files. Default is 'C:\Serilog'
87 | ///
88 | public string FileDirectory { get; set; } = @"C:\\Serilog";
89 |
90 | ///
91 | /// The minimum level for events passed through the sink. Default is LogEventLevel.Warning
92 | ///
93 | public LogEventLevel FileMinimumLevel { get; set; } = LogEventLevel.Warning;
94 |
95 | ///
96 | /// The interval at which logging will roll over to a new file. (Example : log20190702.txt)
97 | ///
98 | public RollingInterval FileRollingInterval { get; set; } = RollingInterval.Day;
99 |
100 | ///
101 | /// The approximate maximum size, in bytes, to which a log file will be allowed to grow. Default is 300MB
102 | ///
103 | public long FileSizeLimitBytes { get; set; } = 314572800; //Default : 300MB
104 | #endregion
105 |
106 | #region EventLog Sink
107 | ///
108 | /// Enable EventLog (windows event viewer) sink
109 | ///
110 | public bool EnableEventLog { get; set; } = false;
111 |
112 | ///
113 | /// The source name by which the application is registered on the local computer. Default is 'SmartCacheManager'
114 | ///
115 | public string EventLogApplicationName { get; set; } = "SmartCacheManager"; // Assembly.GetExecutingAssembly().GetName().Name;
116 |
117 | ///
118 | /// The minimum level for events passed through the sink. Default is LogEventLevel.Warning
119 | ///
120 | public LogEventLevel EventLogMinimumLevel { get; set; } = LogEventLevel.Warning;
121 | #endregion
122 |
123 | #region Sql Sink
124 | ///
125 | /// Enable MSSQLServer sink. Default is true
126 | ///
127 | public bool EnableSqlLog { get; set; } = true;
128 |
129 | ///
130 | /// The connection string to the database where to store the events
131 | ///
132 | public string SqlConnectionString { get; set; }
133 |
134 | ///
135 | /// Name of the table to store the events in. Default is 'Logs'
136 | ///
137 | public string SqlTableName { get; set; } = "Logs";
138 |
139 | ///
140 | /// The minimum level for events passed through the sink. Default is LogEventLevel.Warning
141 | ///
142 | public LogEventLevel SqlMinimumLevel { get; set; } = LogEventLevel.Information;
143 |
144 | ///
145 | /// Default group of columns in database
146 | ///
147 | public ColumnOptions SqlColumns
148 | {
149 | get
150 | {
151 | if (_sqlColumns == null)
152 | {
153 | var columnOptions = new ColumnOptions();
154 | columnOptions.Store.Remove(StandardColumn.Properties);
155 | columnOptions.Store.Add(StandardColumn.LogEvent);
156 | columnOptions.LogEvent.ExcludeAdditionalProperties = true;
157 | columnOptions.LogEvent.ExcludeStandardColumns = true;
158 | //columnOptions.TimeStamp.ConvertToUtc = true;
159 | _sqlColumns = columnOptions;
160 | }
161 | return _sqlColumns;
162 | }
163 | set
164 | {
165 | _sqlColumns = value;
166 | }
167 | }
168 | private ColumnOptions _sqlColumns;
169 | #endregion
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/SmartCacheManager/Caching/EasyCaching/EasyCachingConfigurationExtensions.cs:
--------------------------------------------------------------------------------
1 | using SmartCacheManager.Utilities;
2 | using EasyCaching.Core.Configurations;
3 | using Microsoft.Extensions.DependencyInjection;
4 | using Microsoft.Extensions.DependencyInjection.Extensions;
5 | using Microsoft.Extensions.Options;
6 | using System;
7 | using MessagePack.Resolvers;
8 |
9 | namespace SmartCacheManager.Caching.EasyCaching
10 | {
11 | public static class EasyCachingConfigurationExtensions
12 | {
13 | ///
14 | /// Add EasyCaching services and EasyCachingOptions options to IServiceCollection
15 | ///
16 | /// services
17 | /// Action to configure EasyCachingOptions
18 | /// IServiceCollection
19 | public static IServiceCollection AddEasyCachingCacheManager(this IServiceCollection services, Action configure = null)
20 | {
21 | services.NotNull(nameof(services));
22 |
23 | services.AddEasyCaching(configure);
24 |
25 | return services.AddEasyCachingAsCacheManager();
26 | }
27 |
28 | ///
29 | /// Add EasyCaching services to IServiceCollection and configure that based on EasyCachingOptions (InMemory or Redis or Hybrid)
30 | ///
31 | /// services
32 | /// Action to configure EasyCachingOptions
33 | /// IServiceCollection
34 | public static IServiceCollection AddEasyCaching(this IServiceCollection services, Action configure = null)
35 | {
36 | services.NotNull(nameof(services));
37 |
38 |
39 | if (configure == null)
40 | configure = _ => { };
41 | services.Configure(configure);
42 |
43 | //using IOptions<> instead of IOptionsSnapshot<> because IOptions is Singleton while IOptionsSnapshot is scoped
44 | var optionsMonitor = services.BuildServiceProvider().GetRequiredService>();
45 | var options = optionsMonitor.Value;
46 |
47 | if (options.ProviderType == CachingProviderType.Disabled)
48 | return services;
49 |
50 | var compressorName = options.CompressionType.ToString();
51 | if (options.ProviderType == CachingProviderType.Redis)
52 | {
53 | switch (options.CompressionType)
54 | {
55 | case CompressionType.Brotli:
56 | services.AddBrotliCompressor(compressorName);
57 | break;
58 | case CompressionType.GZip:
59 | services.AddGZipCompressor(compressorName);
60 | break;
61 | case CompressionType.Deflate:
62 | services.AddDeflateCompressor(compressorName);
63 | break;
64 | case CompressionType.LZ4:
65 | services.AddLZ4Compressor(compressorName);
66 | break;
67 | }
68 | }
69 |
70 | services.AddEasyCaching(easyCachingOptions =>
71 | {
72 | switch (options.ProviderType)
73 | {
74 | case CachingProviderType.InMemory:
75 | var inMemory = "DefaultInMemory";
76 |
77 | var memoryCache = easyCachingOptions.UseInMemory(config =>
78 | {
79 | // whether enable logging, default is false
80 | config.EnableLogging = options.EnableLogging;
81 | //config.DBConfig = new InMemoryCachingOptions
82 | //{
83 | // // scan time, default value is 60s
84 | // ExpirationScanFrequency = 60,
85 | // // total count of cache items, default value is 10000
86 | // SizeLimit = 100
87 | //};
88 | //// the max random second will be added to cache's expiration, default value is 120
89 | //config.MaxRdSecond = 120;
90 | //// mutex key's alive time(ms), default is 5000
91 | //config.LockMs = 5000;
92 | //// when mutex key alive, it will sleep some time, default is 300
93 | //config.SleepMs = 300;
94 | }, inMemory);
95 |
96 | break;
97 | case CachingProviderType.Redis:
98 | var redis = "DefaultRedis";
99 | var msgpack = "MessagePack";
100 |
101 | var redisCache = easyCachingOptions.UseRedis(config =>
102 | {
103 | // whether enable logging, default is false
104 | config.EnableLogging = options.EnableLogging;
105 | //redis database endpoint (host:port)
106 | config.DBConfig.Endpoints.Add(new ServerEndPoint(options.RedisHost, options.RedisPort));
107 | config.SerializerName = msgpack;
108 | //Access for redis FLUSHDB command
109 | config.DBConfig.AllowAdmin = options.RedisAllowAdmin;
110 | }, redis);
111 |
112 | //set binary serializer
113 | //https://easycaching.readthedocs.io/en/latest/MessagePack/
114 | redisCache.WithMessagePack(opt =>
115 | {
116 | opt.EnableCustomResolver = true;
117 | opt.CustomResolvers = CompositeResolver.Create(
118 | // This can solve DateTime time zone problem
119 | NativeDateTimeResolver.Instance,
120 | ContractlessStandardResolver.Instance
121 | );
122 | }, msgpack);
123 |
124 | if (options.CompressionType != CompressionType.Disabled)
125 | redisCache.WithCompressor(msgpack, compressorName);
126 | break;
127 | case CachingProviderType.Disabled:
128 | default:
129 | throw new ArgumentOutOfRangeException(nameof(options.ProviderType), "CachingProviderType is not valid.");
130 | }
131 | });
132 |
133 | return services;
134 | }
135 |
136 | ///
137 | /// Register EasyCachingCacheManager as ICacheManager as Scoped lifetime
138 | ///
139 | /// services
140 | /// IServiceCollection
141 | private static IServiceCollection AddEasyCachingAsCacheManager(this IServiceCollection services)
142 | {
143 | services.NotNull(nameof(services));
144 |
145 | services.TryAddScoped();
146 | return services;
147 | }
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/SmartCacheManager/Data/Store/IGenericStore.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 |
6 | namespace SmartCacheManager.Data
7 | {
8 | ///
9 | /// Interface of service for store (CRUD) of an Entity
10 | ///
11 | /// Type of Entity
12 | public interface IGenericStore where TEntity : class, new()
13 | {
14 | ///
15 | /// Query all entities AsTracking
16 | ///
17 | IQueryable Table { get; }
18 |
19 | ///
20 | /// Query all entities AsNoTracking
21 | ///
22 | IQueryable TableNoTracking { get; }
23 |
24 | ///
25 | /// Insert enitites
26 | ///
27 | /// Entities to insert
28 | void Add(IEnumerable entities);
29 |
30 | ///
31 | /// Insert an entity
32 | ///
33 | /// Entity to insert
34 | void Add(TEntity entity);
35 |
36 | ///
37 | /// Insert enitites async
38 | ///
39 | /// Entities to insert
40 | /// cancellationToken
41 | /// Task
42 | Task AddAsync(IEnumerable entities, CancellationToken cancellationToken = default);
43 |
44 | ///
45 | /// Insert an entity async
46 | ///
47 | /// Entity to insert
48 | /// cancellationToken
49 | /// Task
50 | Task AddAsync(TEntity entity, CancellationToken cancellationToken = default);
51 |
52 | ///
53 | /// Delete entities
54 | ///
55 | /// Entities to delete
56 | void Delete(IEnumerable entities);
57 |
58 | ///
59 | /// Delete an entity
60 | ///
61 | /// Entity to delete
62 | void Delete(TEntity entity);
63 |
64 | ///
65 | /// Delete an entity by Id (without additional select)
66 | ///
67 | /// Type of key
68 | /// Id of entity
69 | void Delete(TKey id);
70 |
71 | ///
72 | /// Delete entities async
73 | ///
74 | /// Entities to delete
75 | /// cancellationToken
76 | /// Task
77 | Task DeleteAsync(IEnumerable entities, CancellationToken cancellationToken = default);
78 |
79 | ///
80 | /// Delete an entity async
81 | ///
82 | /// Entity to delete
83 | /// cancellationToken
84 | /// Task
85 | Task DeleteAsync(TEntity entity, CancellationToken cancellationToken = default);
86 |
87 | ///
88 | /// Delete an entity by Id async (without additional select)
89 | ///
90 | /// Type of key
91 | /// Id of entity
92 | /// cancellationToken
93 | /// Task
94 | Task DeleteAsync(TKey id, CancellationToken cancellationToken = default);
95 |
96 | ///
97 | /// Get all entities from cache or fetch from database
98 | ///
99 | /// Cache expiration in minutes
100 | /// List of entities
101 | List GetAllFromCache(int? expirationMinutes = null);
102 |
103 | ///
104 | /// Get all entities from cache or fetch from database async
105 | ///
106 | /// Cache expiration in minutes
107 | /// cancellationToken
108 | /// List of entities
109 | Task> GetAllFromCacheAsync(int? expirationMinutes = null, CancellationToken cancellationToken = default);
110 |
111 | ///
112 | /// Get and entity by ids
113 | ///
114 | /// The values of the primary key for the entity to be found
115 | /// The entity found, or null
116 | TEntity GetById(params object[] ids);
117 |
118 | ///
119 | /// Get and entity by ids async
120 | ///
121 | /// The values of the primary key for the entity to be found.
122 | /// cancellationToken
123 | /// The entity found, or null
124 | ValueTask GetByIdAsync(IEnumerable ids, CancellationToken cancellationToken = default);
125 |
126 | ///
127 | /// Get and entity by Id from cache or fetch from database
128 | ///
129 | /// The values of the primary key for the entity to be found.
130 | /// Cache expiration in minutes
131 | /// The entity found, or null from cache
132 | TEntity GetByIdFromCache(IEnumerable ids, int? expirationMinutes = null);
133 |
134 | ///
135 | /// Get and entity by Id from cache or fetch from database async
136 | ///
137 | /// The values of the primary key for the entity to be found.
138 | /// Cache expiration in minutes
139 | /// cancellationToken
140 | /// The entity found, or null from cache
141 | Task GetByIdFromCacheAsync(IEnumerable ids, int? expirationMinutes = null, CancellationToken cancellationToken = default);
142 |
143 | ///
144 | /// Update entities
145 | ///
146 | /// Entities to update
147 | void Update(IEnumerable entities);
148 |
149 | ///
150 | /// Update an entity
151 | ///
152 | /// Entity to update
153 | void Update(TEntity entity);
154 |
155 | ///
156 | /// Update entities async
157 | ///
158 | /// Entities to update
159 | /// cancellationToken
160 | /// Task
161 | Task UpdateAsync(IEnumerable entities, CancellationToken cancellationToken = default);
162 |
163 | ///
164 | /// Update an entity async
165 | ///
166 | /// Entity to update
167 | /// cancellationToken
168 | /// Task
169 | Task UpdateAsync(TEntity entity, CancellationToken cancellationToken = default);
170 | }
171 | }
--------------------------------------------------------------------------------
/SmartCacheManager/Services/CacheSearchHistoryService.cs:
--------------------------------------------------------------------------------
1 | using SmartCacheManager.Caching;
2 | using SmartCacheManager.Data;
3 | using SmartCacheManager.Utilities;
4 | using MethodTimer;
5 | using System;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 | using SmartCacheManager.Logging;
9 |
10 | namespace SmartCacheManager.Services
11 | {
12 | ///
13 | /// Cache implementation of ISearchHistoryService
14 | ///
15 | /// Type of CacheSetting
16 | /// Type of LimitSetting
17 | public class CacheSearchHistoryService : ISearchHistoryService
18 | where TCacheSetting : CacheSetting, new()
19 | where TLimitSetting : LimitSetting, new()
20 | {
21 | protected readonly ICacheManager CacheManager;
22 | protected readonly ILogger Logger;
23 | protected readonly ICacheSettingService CacheSettingService;
24 | protected readonly ILimitSettingService LimitSettingService;
25 |
26 | public CacheSearchHistoryService(ICacheManager cacheManager, ILoggerFactory loggerFactory,
27 | ICacheSettingService cacheSettingService, ILimitSettingService limitSettingService)
28 | {
29 | CacheManager = cacheManager.NotNull(nameof(cacheManager));
30 | Logger = loggerFactory.NotNull(nameof(loggerFactory)).CreateLogger(GetType()).NotNull(nameof(Logger));
31 | CacheSettingService = cacheSettingService.NotNull(nameof(cacheSettingService));
32 | LimitSettingService = limitSettingService.NotNull(nameof(limitSettingService));
33 | }
34 |
35 | ///
36 | /// Add an incoming search request
37 | ///
38 | /// Type of SupplierType
39 | /// Icoming pattern prefix
40 | /// Supplier type
41 | /// cancellationToken
42 | /// Task
43 | [Time]
44 | public async Task AddIncommingAsync(string incomingPrefix, TSupplierType supplierType, CancellationToken cancellationToken = default)
45 | {
46 | try
47 | {
48 | incomingPrefix.NotNullOrWhiteSpace(nameof(incomingPrefix));
49 | supplierType.NotNull(nameof(supplierType));
50 |
51 | var strSupplierType = supplierType.ConvertTo();
52 | Logger.SetProperty(LogConstants.SupplierType, strSupplierType);
53 |
54 | var cacheSetting = await CacheSettingService.GetFromCacheBySupplierTypeAsync(strSupplierType, cancellationToken).ConfigureAwait(false);
55 |
56 | var key = incomingPrefix + "-" + Guid.NewGuid().ToString("N");
57 | await CacheManager.SetAsync(key, string.Empty, cacheSetting.RpmDurationMinutes, cancellationToken).ConfigureAwait(false);
58 |
59 | ////3 times to try
60 | //if (await TrySet().ConfigureAwait(false) == false)
61 | // if (await TrySet().ConfigureAwait(false) == false)
62 | // if (await TrySet().ConfigureAwait(false) == false)
63 | // throw new Exception($"Faild to set cache {nameof(AddSearchHistoryAsync)}");
64 | //Task TrySet() => CacheManager.TrySetAsync(key, string.Empty, cacheSetting.RpmDurationMinutes, cancellationToken);
65 | }
66 | catch (Exception ex)
67 | when (Logger.LogErrorIfNotBefore(ex, "Exception ocurred in {MethodName}", nameof(AddIncommingAsync)))
68 | {
69 | throw;
70 | }
71 | }
72 |
73 | ///
74 | /// Add an outgoing search request
75 | ///
76 | /// Type of SupplierType
77 | /// Outgoing pattern prefix
78 | /// Supplier type
79 | /// cancellationToken
80 | /// Task
81 | [Time]
82 | public async Task AddOutgoingAsync(string outgoingPrefix, TSupplierType supplierType, CancellationToken cancellationToken = default)
83 | {
84 | try
85 | {
86 | outgoingPrefix.NotNullOrWhiteSpace(nameof(outgoingPrefix));
87 | supplierType.NotNull(nameof(supplierType));
88 |
89 | var strSupplierType = supplierType.ConvertTo();
90 | Logger.SetProperty(LogConstants.SupplierType, strSupplierType);
91 |
92 | var limitSettings = await LimitSettingService.GetFromCacheBySupplierTypeAsync(strSupplierType, cancellationToken).ConfigureAwait(false); //Can be null
93 |
94 | foreach (var limitSetting in limitSettings)
95 | {
96 | var key = outgoingPrefix + $"{limitSetting.Id}_{Guid.NewGuid().ToString("N")}";
97 | var expirationMinutes = Convert.ToInt32(limitSetting.LimitDuration.TotalMinutes);
98 | await CacheManager.SetAsync(key, string.Empty, expirationMinutes, cancellationToken).ConfigureAwait(false);
99 | }
100 | }
101 | catch (Exception ex)
102 | when (Logger.LogErrorIfNotBefore(ex, "Exception ocurred in {MethodName}", nameof(AddOutgoingAsync)))
103 | {
104 | throw;
105 | }
106 | }
107 |
108 | ///
109 | /// Get current RPM based on specified prefix and supplier type
110 | ///
111 | /// Type of SupplierType
112 | /// Icoming pattern prefix
113 | /// Supplier type
114 | /// cancellationToken
115 | /// Current RPM
116 | [Time]
117 | public async Task GetRpmAsync(string incomingPrefix, TSupplierType supplierType, CancellationToken cancellationToken = default)
118 | {
119 | try
120 | {
121 | incomingPrefix.NotNullOrWhiteSpace(nameof(incomingPrefix));
122 | supplierType.NotNull(nameof(supplierType));
123 |
124 | var strSupplierType = supplierType.ConvertTo();
125 | Logger.SetProperty(LogConstants.SupplierType, strSupplierType);
126 |
127 | var cacheSetting = await CacheSettingService.GetFromCacheBySupplierTypeAsync(strSupplierType, cancellationToken).ConfigureAwait(false);
128 |
129 | var count = await CacheManager.GetCountAsync(incomingPrefix, cancellationToken).ConfigureAwait(false);
130 | var rpm = (decimal)count / cacheSetting.RpmDurationMinutes;
131 |
132 | Logger.SetProperty(LogConstants.CurrentRPM, rpm);
133 | return rpm;
134 | }
135 | catch (Exception ex)
136 | when (Logger.LogErrorIfNotBefore(ex, "Exception ocurred in {MethodName}", nameof(GetRpmAsync)))
137 | {
138 | throw;
139 | }
140 | }
141 |
142 | ///
143 | /// Specifies whether or not the limitation is reached
144 | ///
145 | /// Type of SupplierType
146 | /// Outgoing pattern prefix
147 | /// Supplier type
148 | /// cancellationToken
149 | /// Return true if limitation is reached
150 | [Time]
151 | public async Task IsLimitationReachedAsync(string outgoingPrefix, TSupplierType supplierType, CancellationToken cancellationToken = default)
152 | {
153 | try
154 | {
155 | outgoingPrefix.NotNullOrWhiteSpace(nameof(outgoingPrefix));
156 | supplierType.NotNull(nameof(supplierType));
157 |
158 | var strSupplierType = supplierType.ConvertTo();
159 | Logger.SetProperty(LogConstants.SupplierType, strSupplierType);
160 |
161 | var limitSettings = await LimitSettingService.GetFromCacheBySupplierTypeAsync(strSupplierType, cancellationToken).ConfigureAwait(false);
162 |
163 | foreach (var limitSetting in limitSettings)
164 | {
165 | var prefix = outgoingPrefix + $"{limitSetting.Id}_";
166 | var count = CacheManager.GetCount(prefix);
167 |
168 | var isLimitationReached = count >= limitSetting.RequestLimit;
169 | if (isLimitationReached)
170 | {
171 | Logger.SetProperty(LogConstants.IsLimitationReached, true);
172 | Logger.SetProperty(LogConstants.OutgoingRequestCount, count);
173 | Logger.SetProperty(LogConstants.LimitSetting, limitSetting, true);
174 | return true;
175 | }
176 | }
177 |
178 | Logger.SetProperty(LogConstants.IsLimitationReached, false);
179 | return false;
180 | }
181 | catch (Exception ex)
182 | when (Logger.LogErrorIfNotBefore(ex, "Exception ocurred in {MethodName}", nameof(IsLimitationReachedAsync)))
183 | {
184 | throw;
185 | }
186 | }
187 | }
188 | }
189 |
--------------------------------------------------------------------------------
/SmartCacheManager/Utilities/SqlHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.Data.SqlClient;
3 | using System.IO;
4 |
5 | namespace SmartCacheManager.Utilities
6 | {
7 | public static class SqlHelper
8 | {
9 | ///
10 | /// Create database if not exists
11 | /// Example 1 : databaseName: null(optional) and connectionString: "Data Source=.;Initial Catalog=MyDatabaseName;Integrated Security=true"
12 | /// Example 1 : databaseName: "MyDatabaseName" and connectionString: "Data Source=.;Initial Catalog=master;Integrated Security=true"
13 | ///
14 | /// Connection string to connect
15 | /// Database name to check or create
16 | /// Folder path to create database
17 | public static void CreateDatabaseIfNotExists(string connectionString, string databaseName = null, string folderPath = null)
18 | {
19 | if (!DatabaseExists(connectionString, databaseName))
20 | CreateDatabase(connectionString, databaseName, folderPath);
21 | }
22 |
23 | ///
24 | /// Drop database and recreate
25 | /// Example 1 : databaseName: null(optional) and connectionString: "Data Source=.;Initial Catalog=MyDatabaseName;Integrated Security=true"
26 | /// Example 1 : databaseName: "MyDatabaseName" and connectionString: "Data Source=.;Initial Catalog=master;Integrated Security=true"
27 | ///
28 | /// Connection string to connect
29 | /// Database name to check or create
30 | /// Folder path to create database
31 | /// Force to close existing connections
32 | public static void DropDatabaseAndRecreate(string connectionString, string databaseName = null, string folderPath = null, bool force = false)
33 | {
34 | if (DatabaseExists(connectionString, databaseName))
35 | DropDatabase(connectionString, databaseName);
36 | CreateDatabase(connectionString, databaseName, folderPath);
37 | }
38 |
39 | ///
40 | /// Drop database
41 | /// Example 1 : databaseName: null(optional) and connectionString: "Data Source=.;Initial Catalog=MyDatabaseName;Integrated Security=true"
42 | /// Example 1 : databaseName: "MyDatabaseName" and connectionString: "Data Source=.;Initial Catalog=master;Integrated Security=true"
43 | ///
44 | /// Connection string to connect
45 | /// Database name to check
46 | /// Force to close existing connections
47 | public static void DropDatabase(string connectionString, string databaseName = null, bool force = false)
48 | {
49 | var builder = new SqlConnectionStringBuilder(connectionString);
50 |
51 | var isMaster = builder.InitialCatalog.Equals("master", StringComparison.OrdinalIgnoreCase);
52 | if (string.IsNullOrWhiteSpace(databaseName))
53 | {
54 | if (isMaster)
55 | throw new InvalidOperationException($"If {nameof(databaseName)} hasn't value then current InitialCatalog shouldn't be 'master'");
56 |
57 | databaseName = builder.InitialCatalog;
58 | builder.InitialCatalog = "master";
59 | }
60 | else
61 | {
62 | if (!isMaster)
63 | throw new InvalidOperationException($"If {nameof(databaseName)} has value ({databaseName}) then current InitialCatalog should be 'master'");
64 | }
65 |
66 | var command = $"DROP DATABASE {databaseName};";
67 | if (force)
68 | command = $"ALTER DATABASE {databaseName} SET SINGLE_USER WITH ROLLBACK IMMEDIATE; " + command;
69 |
70 | using (var sqlConnection = new SqlConnection(builder.ConnectionString))
71 | {
72 | #pragma warning disable CA2100 // Review SQL queries for security vulnerabilities
73 | using (var sqlCommand = new SqlCommand(command, sqlConnection))
74 | #pragma warning restore CA2100 // Review SQL queries for security vulnerabilities
75 | {
76 | sqlConnection.Open();
77 | sqlCommand.ExecuteNonQuery();
78 | }
79 | }
80 | }
81 |
82 | ///
83 | /// Checks the existence of a database
84 | /// Example 1 : databaseName: null(optional) and connectionString: "Data Source=.;Initial Catalog=MyDatabaseName;Integrated Security=true"
85 | /// Example 1 : databaseName: "MyDatabaseName" and connectionString: "Data Source=.;Initial Catalog=master;Integrated Security=true"
86 | ///
87 | /// Connection string to connect
88 | /// Database name to check
89 | /// Whether or not the database is exist
90 | public static bool DatabaseExists(string connectionString, string databaseName = null)
91 | {
92 | var builder = new SqlConnectionStringBuilder(connectionString);
93 |
94 | var isMaster = builder.InitialCatalog.Equals("master", StringComparison.OrdinalIgnoreCase);
95 | if (string.IsNullOrWhiteSpace(databaseName))
96 | {
97 | if (isMaster)
98 | throw new InvalidOperationException($"If {nameof(databaseName)} hasn't value then current InitialCatalog shouldn't be 'master'");
99 |
100 | databaseName = builder.InitialCatalog;
101 | builder.InitialCatalog = "master";
102 | }
103 | else
104 | {
105 | if (!isMaster)
106 | throw new InvalidOperationException($"If {nameof(databaseName)} has value ({databaseName}) then current InitialCatalog should be 'master'");
107 | }
108 |
109 | var command = "select count(*) from master.dbo.sysdatabases where name=@database";
110 | using (var sqlConnection = new SqlConnection(builder.ConnectionString))
111 | {
112 | using (var sqlCommand = new SqlCommand(command, sqlConnection))
113 | {
114 | sqlCommand.Parameters.Add("@database", System.Data.SqlDbType.NVarChar).Value = databaseName;
115 | sqlConnection.Open();
116 | return Convert.ToInt32(sqlCommand.ExecuteScalar()) == 1;
117 | }
118 | }
119 | }
120 |
121 | ///
122 | /// Create database
123 | /// Example 1 : databaseName: null(optional) and connectionString: "Data Source=.;Initial Catalog=MyDatabaseName;Integrated Security=true"
124 | /// Example 1 : databaseName: "MyDatabaseName" and connectionString: "Data Source=.;Initial Catalog=master;Integrated Security=true"
125 | ///
126 | /// Connection string to connect
127 | /// Database name to create
128 | /// Folder path to create database
129 | public static void CreateDatabase(string connectionString, string databaseName = null, string folderPath = null)
130 | {
131 | var builder = new SqlConnectionStringBuilder(connectionString);
132 |
133 | var isMaster = builder.InitialCatalog.Equals("master", StringComparison.OrdinalIgnoreCase);
134 | if (string.IsNullOrWhiteSpace(databaseName))
135 | {
136 | if (isMaster)
137 | throw new InvalidOperationException($"If {nameof(databaseName)} hasn't value then current InitialCatalog shouldn't be 'master'");
138 |
139 | databaseName = builder.InitialCatalog;
140 | builder.InitialCatalog = "master";
141 | }
142 | else
143 | {
144 | if (!isMaster)
145 | throw new InvalidOperationException($"If {nameof(databaseName)} has value ({databaseName}) then current InitialCatalog should be 'master'");
146 | }
147 |
148 | var command = $"CREATE DATABASE {databaseName}";
149 | if (string.IsNullOrWhiteSpace(folderPath) == false && Directory.Exists(folderPath))
150 | command += $" ON PRIMARY ( NAME = {databaseName}, FILENAME = '{Path.Combine(folderPath, databaseName + ".mdf")}' )";
151 |
152 | #region More info
153 | //https://stackoverflow.com/questions/39499810/how-to-create-database-if-not-exist-in-c-sharp-winforms
154 | //https://www.codeproject.com/Questions/666651/How-to-create-a-database-using-csharp-code-in-net
155 | //https://support.microsoft.com/en-us/help/307283/how-to-create-a-sql-server-database-programmatically-by-using-ado-net
156 |
157 | // ConnectionString examples
158 | //"server=(local)\\SQLEXPRESS;Trusted_Connection=yes"
159 | //"Server=localhost;Integrated security=SSPI;database=master"
160 |
161 | //var command = $"CREATE DATABASE {"MyDatabase"} ON PRIMARY " +
162 | // $"(NAME = {"MyDatabase_Data"}, " +
163 | // $"FILENAME = '{"C:\\MyDatabaseData.mdf"}', " +
164 | // $"SIZE = {2}MB, " +
165 | // $"MAXSIZE = {10}MB, " +
166 | // $"FILEGROWTH = {10}%) " +
167 | // $"LOG ON (NAME = {"MyDatabase_Log"}, " +
168 | // $"FILENAME = '{"C:\\MyDatabaseLog.ldf"}', " +
169 | // $"SIZE = {1}MB, " +
170 | // $"MAXSIZE = {5}MB, " +
171 | // $"FILEGROWTH = {10}%)";
172 | #endregion
173 |
174 | using (var sqlConnection = new SqlConnection(builder.ConnectionString))
175 | {
176 | #pragma warning disable CA2100 // Review SQL queries for security vulnerabilities
177 | using (var sqlCommand = new SqlCommand(command, sqlConnection))
178 | #pragma warning restore CA2100 // Review SQL queries for security vulnerabilities
179 | {
180 | sqlConnection.Open();
181 | sqlCommand.ExecuteNonQuery();
182 | }
183 | }
184 | }
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/SmartCacheManager/Services/DatabaseSearchHistoryService.cs:
--------------------------------------------------------------------------------
1 | using SmartCacheManager.Data;
2 | using SmartCacheManager.Utilities;
3 | using MethodTimer;
4 | using Microsoft.EntityFrameworkCore;
5 | using System;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 | using SmartCacheManager.Logging;
9 |
10 | namespace SmartCacheManager.Services
11 | {
12 | ///
13 | /// Database implementation of ISearchHistoryService
14 | ///
15 | /// Type of CacheSetting
16 | /// Type of LimitSetting
17 | public class DatabaseSearchHistoryService : ISearchHistoryService
18 | where TCacheSetting : CacheSetting, new()
19 | where TLimitSetting : LimitSetting, new()
20 | {
21 | protected readonly IGenericStore IncomingRequestStore;
22 | protected readonly IGenericStore OutgoingRequestStore;
23 | protected readonly ICacheSettingService CacheSettingService;
24 | protected readonly ILimitSettingService LimitSettingService;
25 | protected readonly ILogger Logger;
26 | protected readonly ISystemClock SystemClock;
27 | protected readonly IAsyncLock AsyncLock;
28 |
29 | public DatabaseSearchHistoryService(IGenericStore incomingRequestStore, IGenericStore outgoingRequestStore,
30 | ICacheSettingService cacheSettingService, ILimitSettingService limitSettingService,
31 | ILoggerFactory loggerFactory, ISystemClock systemClock, IAsyncLock asyncLock)
32 | {
33 | IncomingRequestStore = incomingRequestStore.NotNull(nameof(incomingRequestStore));
34 | OutgoingRequestStore = outgoingRequestStore.NotNull(nameof(outgoingRequestStore));
35 | CacheSettingService = cacheSettingService.NotNull(nameof(cacheSettingService));
36 | LimitSettingService = limitSettingService.NotNull(nameof(limitSettingService));
37 | Logger = loggerFactory.NotNull(nameof(loggerFactory)).CreateLogger(GetType()).NotNull(nameof(Logger));
38 | SystemClock = systemClock.NotNull(nameof(systemClock));
39 | AsyncLock = asyncLock.NotNull(nameof(asyncLock));
40 | }
41 |
42 | ///
43 | /// Add an incoming search request
44 | ///
45 | /// Type of SupplierType
46 | /// Icoming pattern prefix
47 | /// Supplier type
48 | /// cancellationToken
49 | /// Task
50 | [Time]
51 | public async Task AddIncommingAsync(string incomingPrefix, TSupplierType supplierType, CancellationToken cancellationToken = default)
52 | {
53 | try
54 | {
55 | incomingPrefix.NotNullOrWhiteSpace(nameof(incomingPrefix));
56 | supplierType.NotNull(nameof(supplierType));
57 |
58 | var strSupplierType = supplierType.ConvertTo();
59 | var hashCode = incomingPrefix.GetInvariantHashCode();
60 |
61 | Logger.SetProperty(LogConstants.IncomingRequestHashCode, hashCode);
62 | Logger.SetProperty(LogConstants.SupplierType, strSupplierType);
63 |
64 | var incomingRequest = new IncomingRequest
65 | {
66 | CreatedAt = SystemClock.DateTimeNow,
67 | HashCode = hashCode
68 | };
69 |
70 | using (await AsyncLock.LockAsync())
71 | await IncomingRequestStore.AddAsync(incomingRequest, cancellationToken);
72 | }
73 | catch (Exception ex)
74 | when (Logger.LogErrorIfNotBefore(ex, "Exception ocurred in {MethodName}", nameof(AddIncommingAsync)))
75 | {
76 | throw;
77 | }
78 | }
79 |
80 | ///
81 | /// Add an outgoing search request
82 | ///
83 | /// Type of SupplierType
84 | /// Outgoing pattern prefix
85 | /// Supplier type
86 | /// cancellationToken
87 | /// Task
88 | [Time]
89 | public async Task AddOutgoingAsync(string outgoingPrefix, TSupplierType supplierType, CancellationToken cancellationToken = default)
90 | {
91 | try
92 | {
93 | outgoingPrefix.NotNullOrWhiteSpace(nameof(outgoingPrefix));
94 | supplierType.NotNull(nameof(supplierType));
95 |
96 | var strSupplierType = supplierType.ConvertTo();
97 | var hashCode = outgoingPrefix.GetInvariantHashCode();
98 |
99 | Logger.SetProperty(LogConstants.OutgoingRequestHashCode, hashCode);
100 | Logger.SetProperty(LogConstants.SupplierType, strSupplierType);
101 |
102 | var outgoingRequest = new OutgoingRequest
103 | {
104 | CreatedAt = SystemClock.DateTimeNow,
105 | HashCode = hashCode
106 | };
107 |
108 | using (await AsyncLock.LockAsync())
109 | await OutgoingRequestStore.AddAsync(outgoingRequest, cancellationToken);
110 | }
111 | catch (Exception ex)
112 | when (Logger.LogErrorIfNotBefore(ex, "Exception ocurred in {MethodName}", nameof(AddOutgoingAsync)))
113 | {
114 | throw;
115 | }
116 | }
117 |
118 | ///
119 | /// Get current RPM based on specified prefix and supplier type
120 | ///
121 | /// Type of SupplierType
122 | /// Icoming pattern prefix
123 | /// Supplier type
124 | /// cancellationToken
125 | /// Current RPM
126 | [Time]
127 | public async Task GetRpmAsync(string incomingPrefix, TSupplierType supplierType, CancellationToken cancellationToken = default)
128 | {
129 | try
130 | {
131 | incomingPrefix.NotNullOrWhiteSpace(nameof(incomingPrefix));
132 | supplierType.NotNull(nameof(supplierType));
133 |
134 | var hashCode = incomingPrefix.GetInvariantHashCode();
135 |
136 | Logger.SetProperty(LogConstants.IncomingRequestHashCode, hashCode);
137 | Logger.SetProperty(LogConstants.SupplierType, supplierType.ConvertTo());
138 |
139 | var cacheSetting = await CacheSettingService.GetFromCacheBySupplierTypeAsync(supplierType, cancellationToken).ConfigureAwait(false);
140 | var rpmDurationDateTime = SystemClock.DateTimeNow.AddMinutes(-cacheSetting.RpmDurationMinutes);
141 |
142 | int count;
143 | using (await AsyncLock.LockAsync())
144 | count = await IncomingRequestStore.TableNoTracking.CountAsync(p => p.HashCode == hashCode && p.CreatedAt >= rpmDurationDateTime).ConfigureAwait(false);
145 |
146 | var rpm = (decimal)count / cacheSetting.RpmDurationMinutes;
147 | Logger.SetProperty(LogConstants.CurrentRPM, rpm);
148 |
149 | return rpm;
150 | }
151 | catch (Exception ex)
152 | when (Logger.LogErrorIfNotBefore(ex, "Exception ocurred in {MethodName}", nameof(GetRpmAsync)))
153 | {
154 | throw;
155 | }
156 | }
157 |
158 | ///
159 | /// Specifies whether or not the limitation is reached
160 | ///
161 | /// Type of SupplierType
162 | /// Outgoing pattern prefix
163 | /// Supplier type
164 | /// cancellationToken
165 | /// Return true if limitation is reached
166 | [Time]
167 | public async Task IsLimitationReachedAsync(string outgoingPrefix, TSupplierType supplierType, CancellationToken cancellationToken = default)
168 | {
169 | try
170 | {
171 | outgoingPrefix.NotNullOrWhiteSpace(nameof(outgoingPrefix));
172 | supplierType.NotNull(nameof(supplierType));
173 |
174 | var hashCode = outgoingPrefix.GetInvariantHashCode();
175 |
176 | Logger.SetProperty(LogConstants.OutgoingRequestHashCode, hashCode);
177 | Logger.SetProperty(LogConstants.SupplierType, supplierType.ConvertTo());
178 |
179 | var limitSettings = await LimitSettingService.GetFromCacheBySupplierTypeAsync(supplierType, cancellationToken).ConfigureAwait(false);
180 |
181 | foreach (var limitSetting in limitSettings)
182 | {
183 | var limitDurationDateTime = SystemClock.DateTimeNow.AddHours(-limitSetting.LimitDurationHours);
184 |
185 | int count;
186 | using (await AsyncLock.LockAsync())
187 | count = await OutgoingRequestStore.TableNoTracking.CountAsync(p => p.HashCode == hashCode && p.CreatedAt >= limitDurationDateTime).ConfigureAwait(false);
188 |
189 | var isLimitationReached = count >= limitSetting.RequestLimit;
190 | if (isLimitationReached)
191 | {
192 | Logger.SetProperty(LogConstants.IsLimitationReached, true);
193 | Logger.SetProperty(LogConstants.OutgoingRequestCount, count);
194 | Logger.SetProperty(LogConstants.LimitSetting, limitSetting, true);
195 | return true;
196 | }
197 | }
198 |
199 | Logger.SetProperty(LogConstants.IsLimitationReached, false);
200 | return false;
201 | }
202 | catch (Exception ex)
203 | when (Logger.LogErrorIfNotBefore(ex, "Exception ocurred in {MethodName}", nameof(IsLimitationReachedAsync)))
204 | {
205 | throw;
206 | }
207 | }
208 | }
209 | }
210 |
--------------------------------------------------------------------------------
/SmartCacheManager/Logging/SerilogLogger/SerilogLogger.cs:
--------------------------------------------------------------------------------
1 | using SmartCacheManager.Utilities;
2 | using Serilog.Context;
3 | using Serilog.Core;
4 | using Serilog.Events;
5 | using Serilog.Extensions.Hosting;
6 | using System;
7 | using System.Collections.Generic;
8 | using System.Linq;
9 |
10 | namespace SmartCacheManager.Logging.Serilog
11 | {
12 | ///
13 | /// Serilog implementation of ILogger with soruce context
14 | ///
15 | public class SerilogLogger : SerilogLogger, ILogger
16 | {
17 | public SerilogLogger(global::Serilog.ILogger logger, DiagnosticContext diagnosticContext)
18 | : base(logger.ForContext(), diagnosticContext)
19 | {
20 | }
21 | }
22 |
23 | ///
24 | /// Serilog implementation of ILogger
25 | ///
26 | public class SerilogLogger : ILogger
27 | {
28 | private readonly global::Serilog.ILogger _logger;
29 | private readonly DiagnosticContext _diagnosticContext;
30 | private static readonly LogEventProperty[] NoProperties = Array.Empty();
31 |
32 | public SerilogLogger(global::Serilog.ILogger logger, DiagnosticContext diagnosticContext)
33 | {
34 | _diagnosticContext = diagnosticContext.NotNull(nameof(diagnosticContext));
35 | _logger = logger.NotNull(nameof(logger));
36 | }
37 |
38 | ///
39 | /// Write an event to the log.
40 | ///
41 | /// The level of the log.
42 | /// Exception related to the log.
43 | /// Message template describing the log.
44 | /// Objects positionally formatted into the message template.
45 | /// Properties associated with the log.
46 | [MessageTemplateFormatMethod("messageTemplate")]
47 | public void Log(LogLevel logLevel, Exception exception, string messageTemplate, IEnumerable propertyValues, IEnumerable logProperties)
48 | {
49 | messageTemplate.NotNull(nameof(messageTemplate));
50 |
51 | if (logLevel == LogLevel.None) return;
52 | var level = ToSerilogLevel(logLevel);
53 |
54 | _logger.BindMessageTemplate(messageTemplate, GetPropertyArray(propertyValues), out var parsedTemplate, out var boundProperties);
55 |
56 | var logEventProperties = logProperties?.Select(p =>
57 | {
58 | if (_logger.BindProperty(p.Name, p.Value, p.DestructureObjects, out var property) == false)
59 | throw new InvalidOperationException($"Can not bind property to serilog logger. Name : {p.Name} - Value : {p.Value} - DestructureObjects : {p.DestructureObjects}");
60 | return property;
61 | }) ?? NoProperties;
62 |
63 | var properties = boundProperties.Concat(logEventProperties);
64 |
65 | var logEvent = new LogEvent(DateTimeOffset.Now, level, exception, parsedTemplate, properties);
66 | _logger.Write(logEvent);
67 | }
68 |
69 | ///
70 | /// Create and begin a log scope to collecting properties to log, returning an IDisposable that must later be used to write log.
71 | ///
72 | /// The level of the log.
73 | /// Message template describing the event
74 | /// Objects positionally formatted into the message template.
75 | /// Properties associated with the log.
76 | /// An IDisposable that ends the log operation scope on dispose.
77 | [MessageTemplateFormatMethod("messageTemplate")]
78 | public IDisposable BeginLogScope(LogLevel logLevel, string messageTemplate, IEnumerable propertyValues, IEnumerable logProperties)
79 | {
80 | messageTemplate.NotNull(nameof(messageTemplate));
81 |
82 | if (logLevel == LogLevel.None) return new NullDisposable();
83 | var level = ToSerilogLevel(logLevel);
84 |
85 | return new LogScope(_logger, _diagnosticContext, level, messageTemplate, GetPropertyArray(propertyValues), logProperties);
86 | }
87 |
88 | ///
89 | /// Push a property onto the context, returning an IDisposable that must later be used to remove the property,
90 | /// along with any others that may have been pushed on top of it and not yet popped.
91 | /// The property must be popped from the same thread/logical call context.
92 | ///
93 | /// Property associated with the log
94 | /// A handle to later remove the property from the context.
95 | public IDisposable BeginScope(LogProperty logProperty)
96 | {
97 | logProperty.NotNull(nameof(logProperty));
98 | if (LogEventProperty.IsValidName(logProperty.Name) == false)
99 | throw new ArgumentException($"The name of property is not valid '{logProperty.Name}'");
100 |
101 | return LogContext.PushProperty(logProperty.Name, logProperty.Value, logProperty.DestructureObjects);
102 | }
103 |
104 | ///
105 | /// Remove all properties from LogContext for the current async scope.
106 | ///
107 | public void ResetScope()
108 | {
109 | LogContext.Reset();
110 | }
111 |
112 | ///
113 | /// Remove all properties from the LogContext, returning an IDisposable that must later be used to restore enrichers that were on the stack before Suspend() was called.
114 | ///
115 | /// A handle that must be disposed, in order, to restore properties back to the stack.
116 | public IDisposable SuspendScope()
117 | {
118 | return LogContext.Suspend();
119 | }
120 |
121 | ///
122 | /// Add a property to the log scope if not already present, otherwise, update its value.
123 | ///
124 | /// Property associated with the log
125 | public void SetProperty(LogProperty logProperty)
126 | {
127 | logProperty.NotNull(nameof(logProperty));
128 | if (LogEventProperty.IsValidName(logProperty.Name) == false)
129 | throw new ArgumentException($"The name of property is not valid '{logProperty.Name}'");
130 |
131 | _diagnosticContext.Set(logProperty.Name, logProperty.Value, logProperty.DestructureObjects);
132 | }
133 |
134 | private object[] GetPropertyArray(IEnumerable propertyValues)
135 | {
136 | return propertyValues == null ? null :
137 | (propertyValues is object[] array ? array : propertyValues.ToArray());
138 | }
139 |
140 | ///
141 | /// Convert to the equivalent Serilog .
142 | ///
143 | /// A SmartCacheManager.Logging .
144 | /// The Serilog equivalent of .
145 | private LogEventLevel ToSerilogLevel(LogLevel logLevel)
146 | {
147 | switch (logLevel)
148 | {
149 | case LogLevel.Trace:
150 | return LogEventLevel.Verbose;
151 | case LogLevel.Debug:
152 | return LogEventLevel.Debug;
153 | case LogLevel.Information:
154 | return LogEventLevel.Information;
155 | case LogLevel.Warning:
156 | return LogEventLevel.Warning;
157 | case LogLevel.Error:
158 | return LogEventLevel.Error;
159 | case LogLevel.Critical:
160 | return LogEventLevel.Fatal;
161 | case LogLevel.None:
162 | default:
163 | throw new ArgumentOutOfRangeException(nameof(logLevel));
164 | }
165 | }
166 |
167 | #region LogScope class
168 | ///
169 | /// An log scope to collecting properties to log when disposed
170 | ///
171 | class LogScope : IDisposable
172 | {
173 | private readonly global::Serilog.ILogger _logger;
174 | private readonly DiagnosticContextCollector _collector;
175 | private readonly LogEventLevel _level;
176 | private readonly string _messageTemplate;
177 | private readonly object[] _propertyValues;
178 | private readonly IEnumerable _logProperties;
179 |
180 | public LogScope(global::Serilog.ILogger logger, DiagnosticContext diagnosticContext, LogEventLevel level, string messageTemplate,
181 | object[] propertyValues, IEnumerable logProperties)
182 | {
183 | _logger = logger.NotNull(nameof(logger));
184 | _collector = diagnosticContext.BeginCollection();
185 | _level = level;
186 | _messageTemplate = messageTemplate.NotNull(nameof(messageTemplate));
187 | _propertyValues = propertyValues;
188 | _logProperties = logProperties;
189 | }
190 |
191 | public void Dispose()
192 | {
193 | try
194 | {
195 | if (!_collector.TryComplete(out var collectedProperties))
196 | collectedProperties = NoProperties;
197 |
198 | _logger.BindMessageTemplate(_messageTemplate, _propertyValues, out var parsedTemplate, out var boundProperties);
199 |
200 | var logEventProperties = _logProperties?.Select(p =>
201 | {
202 | if (_logger.BindProperty(p.Name, p.Value, p.DestructureObjects, out var property) == false)
203 | throw new InvalidOperationException($"Can not bind property to serilog logger. Name : {p.Name} - Value : {p.Value} - DestructureObjects : {p.DestructureObjects}");
204 | return property;
205 | }) ?? NoProperties;
206 |
207 | var properties = boundProperties.Concat(logEventProperties).Concat(collectedProperties);
208 |
209 | var logEvent = new LogEvent(DateTimeOffset.Now, _level, null, parsedTemplate, properties);
210 | _logger.Write(logEvent);
211 | }
212 | finally
213 | {
214 | _collector.Dispose();
215 | }
216 | }
217 | }
218 | #endregion
219 | }
220 | }
221 |
--------------------------------------------------------------------------------
/SmartCacheManager/Caching/ICacheManager.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 |
6 | namespace SmartCacheManager.Caching
7 | {
8 | ///
9 | /// Caching abstraction
10 | ///
11 | public interface ICacheManager
12 | {
13 | ///
14 | /// Whether the cached value contains the specified cacheKey.
15 | ///
16 | /// Cache key.
17 | /// The exists.
18 | bool Exists(string cacheKey);
19 |
20 | ///
21 | /// Whether the cached value contains the specified cacheKey async.
22 | ///
23 | /// Cache key.
24 | /// cancellationToken
25 | /// The exists.
26 | Task ExistsAsync(string cacheKey, CancellationToken cancellationToken = default);
27 |
28 | ///
29 | /// Gets the specified cacheKey, dataRetriever and expiration.
30 | ///
31 | /// Type of cache value
32 | /// Cache key
33 | /// Cached value
34 | T Get(string cacheKey);
35 |
36 | ///
37 | /// Gets the specified cacheKey, dataRetriever and expiration.
38 | ///
39 | /// Type of cache value
40 | /// Cache key
41 | /// Function to retrive data
42 | /// Expiration in minutes
43 | /// Cached value
44 | T Get(string cacheKey, Func dataRetriever, int? expirationMinutes = null);
45 |
46 | ///
47 | /// Gets the specified cacheKey, dataRetriever and expiration async.
48 | ///
49 | /// Type of cache value
50 | /// Cache key
51 | /// cancellationToken
52 | /// Cached value
53 | Task GetAsync(string cacheKey, CancellationToken cancellationToken = default);
54 |
55 | ///
56 | /// Gets the specified cacheKey, dataRetriever and expiration async.
57 | ///
58 | /// Type of cache value
59 | /// Cache key
60 | /// Function to retrive data
61 | /// Expiration in minutes
62 | /// cancellationToken
63 | /// Cached value
64 | Task GetAsync(string cacheKey, Func> dataRetriever, int? expirationMinutes = null, CancellationToken cancellationToken = default);
65 |
66 | ///
67 | /// Gets the by prefix.
68 | ///
69 | /// Type of cache value
70 | /// Prefix of CacheKey.
71 | /// Dictionary of key/value cache items
72 | Dictionary GetByPrefix(string prefix);
73 |
74 | ///
75 | /// Gets the by prefix async.
76 | ///
77 | /// Type of cache value
78 | /// Prefix of CacheKey.
79 | /// cancellationToken
80 | /// Dictionary of key/value cache items
81 | Task> GetByPrefixAsync(string prefix, CancellationToken cancellationToken = default);
82 |
83 | ///
84 | /// Gets the count.
85 | ///
86 | /// Prefix of CacheKey.
87 | /// Count
88 | int GetCount(string prefix = "");
89 |
90 | ///
91 | /// Gets the count async.
92 | ///
93 | /// Prefix of CacheKey.
94 | /// cancellationToken
95 | /// Count
96 | Task GetCountAsync(string prefix = "", CancellationToken cancellationToken = default);
97 |
98 | ///
99 | /// Gets the exporation of specify cachekey async.
100 | ///
101 | /// Cache key.
102 | /// Expiration in TimeSpan
103 | TimeSpan GetExpiration(string cacheKey);
104 |
105 | ///
106 | /// Gets the exporation of specify cachekey async.
107 | ///
108 | /// Cache key.
109 | /// Expiration in TimeSpan
110 | /// cancellationToken
111 | Task GetExpirationAsync(string cacheKey, CancellationToken cancellationToken = default);
112 |
113 | ///
114 | /// Set the the exporation of specify cachekey.
115 | /// >
116 | /// Cache key.
117 | /// Expiration in minutes
118 | void SetExpiration(string cacheKey, int? expirationMinutes = null);
119 |
120 | ///
121 | /// Set the the exporation of specify cachekey async.
122 | /// >
123 | /// Cache key.
124 | /// Expiration in minutes
125 | /// cancellationToken
126 | /// Task
127 | Task SetExpirationAsync(string cacheKey, int? expirationMinutes = null, CancellationToken cancellationToken = default);
128 |
129 | ///
130 | /// Removes the specified cacheKey.
131 | ///
132 | /// Cache key.
133 | void Remove(string cacheKey);
134 |
135 | ///
136 | /// Removes all.
137 | ///
138 | /// Cache keys.
139 | void RemoveAll(IEnumerable cacheKeys);
140 |
141 | ///
142 | /// Removes all.
143 | ///
144 | /// Cache keys.
145 | /// cancellationToken
146 | /// Task
147 | Task RemoveAllAsync(IEnumerable cacheKeys, CancellationToken cancellationToken = default);
148 |
149 | ///
150 | /// Removes the specified cacheKey async.
151 | ///
152 | /// Cache key.
153 | /// cancellationToken
154 | /// Task
155 | Task RemoveAsync(string cacheKey, CancellationToken cancellationToken = default);
156 |
157 | ///
158 | /// Removes cached item by cachekey's prefix.
159 | ///
160 | /// Prefix of CacheKey.
161 | void RemoveByPrefix(string prefix);
162 |
163 | ///
164 | /// Removes cached item by cachekey's prefix async.
165 | ///
166 | /// Prefix of CacheKey.
167 | /// cancellationToken
168 | /// Task
169 | Task RemoveByPrefixAsync(string prefix, CancellationToken cancellationToken = default);
170 |
171 | ///
172 | /// Set the specified cacheKey, cacheValue and expiration.
173 | ///
174 | /// Type of cache value
175 | /// Cache key
176 | /// Cache value
177 | /// Expiration in minutes
178 | void Set(string cacheKey, T cacheValue, int? expirationMinutes = null);
179 |
180 | ///
181 | /// Sets all.
182 | ///
183 | /// Type of cache value
184 | /// Dictionary of key/value cache items
185 | /// Expiration in minutes
186 | void SetAll(IDictionary value, int? expirationMinutes = null);
187 |
188 | ///
189 | /// Sets all async.
190 | ///
191 | /// Type of cache value
192 | /// Dictionary of key/value cache items
193 | /// Expiration in minutes
194 | /// cancellationToken
195 | /// Task
196 | Task SetAllAsync(IDictionary value, int? expirationMinutes = null, CancellationToken cancellationToken = default);
197 |
198 | ///
199 | /// Sets the specified cacheKey, cacheValue and expiration async.
200 | ///
201 | /// Type of cache value
202 | /// Cache key
203 | /// Cache value
204 | /// Expiration in minutes
205 | /// cancellationToken
206 | /// Task
207 | Task SetAsync(string cacheKey, T cacheValue, int? expirationMinutes = null, CancellationToken cancellationToken = default);
208 |
209 | ///
210 | /// Tries the set.
211 | ///
212 | /// Type of cache value
213 | /// Cache key
214 | /// Cache value
215 | /// Expiration in minutes
216 | /// Determines whether it is successful
217 | bool TrySet(string cacheKey, T cacheValue, int? expirationMinutes = null);
218 |
219 | ///
220 | /// Tries the set async.
221 | ///
222 | /// Type of cache value
223 | /// Cache key
224 | /// Cache value
225 | /// Expiration in minutes
226 | /// cancellationToken
227 | /// Determines whether it is successful
228 | Task TrySetAsync(string cacheKey, T cacheValue, int? expirationMinutes = null, CancellationToken cancellationToken = default);
229 |
230 | ///
231 | /// Increments the number stored at key by value async.
232 | ///
233 | /// Cache key
234 | /// Increment value
235 | /// Increased value
236 | long IncrBy(string cacheKey, long value = 1);
237 |
238 | ///
239 | /// Increments the number stored at key by value.
240 | ///
241 | /// Cache key
242 | /// Increment value
243 | /// cancellationToken
244 | /// Increased value
245 | Task IncrByAsync(string cacheKey, long value = 1, CancellationToken cancellationToken = default);
246 |
247 | ///
248 | /// Flush All Cached Item
249 | ///
250 | void Flush();
251 |
252 | ///
253 | /// Flush All Cached Item async.
254 | ///
255 | /// cancellationToken
256 | /// Task
257 | Task FlushAsync(CancellationToken cancellationToken = default);
258 | }
259 | }
260 |
--------------------------------------------------------------------------------