series

  1. Develop blog projects based on ABP vNext and.NET Core – build projects using ABP CLI
  2. Develop blog projects based on ABP vNext and.NET Core – slim the project down and make it run
  3. Development blog project based on ABP vNext and.NET Core – Refinement and beautification, Swagger enter
  4. Develop blog project based on ABP vNext and.NET Core – data access and code priority
  5. Development blog project based on ABP vNext and.NET Core – add, delete, change and check custom warehouse
  6. Develop blog project based on ABP vNext and.NET Core – Uniform specification API, wrapper back model
  7. Develop blog projects based on ABP vNext and.NET Core – say Swagger, grouping, description, little green lock
  8. Develop blog projects based on ABP vNext and.NET Core – access GitHub and protect your API with JWT
  9. Develop blog project based on ABP vNext and.NET Core – exception handling and logging
  10. Develop blog projects based on ABP vNext and.NET Core – using Redis to cache data
  11. Develop blog project based on ABP vNext and.NET Core – integrate Hangfire for timed task processing
  12. Develop blog projects based on ABP vNext and.NET Core – Use AutoMapper to map objects
  13. Developing blog projects based on ABP vNext and.NET Core – Best Practices for Timed Tasks (Part 1)
  14. Developing blog projects based on ABP vNext and.NET Core – Best Practices for Timed Tasks (Part 2)
  15. Developing blog projects based on ABP vNext and.NET Core – Best Practices for Timed Tasks (PART 3)
  16. Blog Development project based on ABP vNext and.NET Core
  17. Abp vNext and.NET Core
  18. Blog Development project based on ABP vNext and.NET Core
  19. Blog Development project based on ABP vNext and.NET Core
  20. Blog Development project based on ABP vNext and.NET Core
  21. Abp vNext and.NET Core Development Blog Project – Blazor
  22. Abp vNext and.NET Core Development Blog Project – Blazor – Part 2
  23. Abp vNext and.NET Core Development Blog Project – Blazor
  24. Abp vNext and.NET Core Development Blog Project – Blazor
  25. Abp vNext and.NET Core Development Blog Project – Blazor
  26. Abp vNext and.NET Core Development Blog Project – Blazor – Part 6
  27. Abp vNext and.NET Core Development Blog Project – Blazor
  28. Abp vNext and.NET Core Development Blog Project – Blazor Series (8)
  29. Abp vNext and.NET Core Development Blog Project – Blazor Series (9)
  30. Abp vNext and.NET Core development blog project – Final release project

Previous article (juejin.cn/post/684490…) Global exception handling and logging of the project are completed.

The static method used in logging is pointed out that the writing method is not very elegant, so optimize the logging method in the previous article, the specific operation is as follows:

Create a new extension method log4NetExtensions.cs in the.ToolKits layer.

//Log4NetExtensions.cs
using log4net;
using log4net.Config;
using Microsoft.Extensions.Hosting;
using System.IO;
using System.Reflection;

namespace Meowv.Blog.ToolKits.Extensions
{
    public static class Log4NetExtensions
    {
        public static IHostBuilder UseLog4Net(this IHostBuilder hostBuilder)
        {
            var log4netRepository = LogManager.GetRepository(Assembly.GetEntryAssembly());
            XmlConfigurator.Configure(log4netRepository, new FileInfo("log4net.config"));

            returnhostBuilder; }}}Copy the code

Configure log4Net, and then we return the IHostBuilder object directly for easy chain calls in the Main method.

//Program.cs
using Meowv.Blog.ToolKits.Extensions;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using System.Threading.Tasks;

namespace Meowv.Blog.HttpApi.Hosting
{
    public class Program
    {
        public static async Task Main(string[] args)
        {
            awaitHost.CreateDefaultBuilder(args) .UseLog4Net() .ConfigureWebHostDefaults(builder => { builder.UseIISIntegration() .UseStartup<Startup>(); }).UseAutofac().Build().RunAsync(); }}}Copy the code

Then modify the MeowvBlogExceptionFilter as follows:

//MeowvBlogExceptionFilter.cs
using log4net;
using Microsoft.AspNetCore.Mvc.Filters;

namespace Meowv.Blog.HttpApi.Hosting.Filters
{
    public class MeowvBlogExceptionFilter : IExceptionFilter
    {
        private readonly ILog _log;

        public MeowvBlogExceptionFilter()
        {
            _log = LogManager.GetLogger(typeof(MeowvBlogExceptionFilter));
        }

        /// <summary>
        ///Exception handling
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public void OnException(ExceptionContext context)
        {
            // Error logging
            _log.Error($"{context.HttpContext.Request.Path}|{context.Exception.Message}", context.Exception); }}}Copy the code

You can delete the loggerHelper. cs class you added earlier and run it to get the same effect.


This article will integrate Redis, using Redis to cache data, using the method of reference Microsoft official documentation: docs.microsoft.com/zh-cn/aspne…

The introduction of Redis is not said here, there is a quick start article: Redis quick start and use, for students who do not know can have a look.

Go straight to the theme and configure the Redis connection string in appSettings. json.

//appsettings.json ... "Caching": {"RedisConnectionString": "127.0.0.1:6379,password=123456,ConnectTimeout=15000,SyncTimeout=5000"}...Copy the code

Corresponding, read in appSettings. cs.

//AppSettings.cs./// <summary>
        /// Caching
        /// </summary>
        public static class Caching
        {
            /// <summary>
            /// RedisConnectionString
            /// </summary>
            public static string RedisConnectionString => _config["Caching:RedisConnectionString"]; }...Copy the code

In. Application. Caching layer add packages Microsoft. Extensions. Caching. StackExchangeRedis, Then add in the module class MeowvBlogApplicationCachingModule configuration cache implementation.

//MeowvBlogApplicationCachingModule.cs
using Meowv.Blog.Domain;
using Meowv.Blog.Domain.Configurations;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Caching;
using Volo.Abp.Modularity;

namespace Meowv.Blog.Application.Caching
{
    [DependsOn(
        typeof(AbpCachingModule),
        typeof(MeowvBlogDomainModule)
    )]
    public class MeowvBlogApplicationCachingModule : AbpModule
    {
        public override void ConfigureServices(ServiceConfigurationContext context)
        {
            context.Services.AddStackExchangeRedisCache(options =>
            {
                options.Configuration = AppSettings.Caching.RedisConnectionString;
                //options.InstanceName
                //options.ConfigurationOptions}); }}}Copy the code

Options. Configuration is the connection string for Redis.

Options. InstanceNam is the Redis instance name.

Options. ConfigurationOptions is Redis Configuration properties, if configured with the words, will give priority to the Configuration of Configuration, it supports more options at the same time. I didn’t fill it in here either.

Next we can use it directly, injecting the IDistributedCache interface dependency.

Can see the default has achieved so much in common use interface, this small project I use enough, at the same time in Microsoft. Extensions. Caching. Distributed. DistributedCacheExtensions Microsoft also provides a lot of extension methods.

So we came up with the idea of writing a new extension method that can handle both fetching and adding caches, returning them when they exist and adding them when they don’t.

New MeowvBlogApplicationCachingExtensions. Cs extension methods, as follows:

//MeowvBlogApplicationCachingExtensions.cs
using Meowv.Blog.ToolKits.Extensions;
using Microsoft.Extensions.Caching.Distributed;
using System;
using System.Threading.Tasks;

namespace Meowv.Blog.Application.Caching
{
    public static class MeowvBlogApplicationCachingExtensions
    {
        /// <summary>
        ///Gets or adds a cache
        /// </summary>
        /// <typeparam name="TCacheItem"></typeparam>
        /// <param name="cache"></param>
        /// <param name="key"></param>
        /// <param name="factory"></param>
        /// <param name="minutes"></param>
        /// <returns></returns>
        public static async Task<TCacheItem> GetOrAddAsync<TCacheItem>(this IDistributedCache cache, string key, Func<Task<TCacheItem>> factory, int minutes)
        {
            TCacheItem cacheItem;

            var result = await cache.GetStringAsync(key);
            if (string.IsNullOrEmpty(result))
            {
                cacheItem = await factory.Invoke();

                var options = new DistributedCacheEntryOptions();
                if(minutes ! = CacheStrategy.NEVER) { options.AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(minutes); }await cache.SetStringAsync(key, cacheItem.ToJson(), options);
            }
            else
            {
                cacheItem = result.FromJson<TCacheItem>();
            }

            returncacheItem; }}}Copy the code

We can in DistributedCacheEntryOptions can configure our cache expiration time, one of the judgment conditions, is when you = 1, is not specified expiration time, then our cache will not out of date.

, SetStringAsync GetStringAsync () () method is an extension of the DistributedCacheExtensions, will eventually cache entry cacheItem converted to JSON format for storage.

CacheStrategy is in. Cache expiration time policy constant defined by the domain.shared layer.

//MeowvBlogConsts.cs./// <summary>
        ///Cache expiration time policy
        /// </summary>
        public static class CacheStrategy
        {
            /// <summary>
            ///A day is 24 hours overdue
            /// </summary>

            public const int ONE_DAY = 1440;

            /// <summary>
            ///12 hours expired
            /// </summary>

            public const int HALF_DAY = 720;

            /// <summary>
            ///8 hours expired
            /// </summary>

            public const int EIGHT_HOURS = 480;

            /// <summary>
            ///5 hours expired
            /// </summary>

            public const int FIVE_HOURS = 300;

            /// <summary>
            ///3 hours expired
            /// </summary>

            public const int THREE_HOURS = 180;

            /// <summary>
            ///2 hours expired
            /// </summary>

            public const int TWO_HOURS = 120;

            /// <summary>
            ///1 hour expired
            /// </summary>

            public const int ONE_HOURS = 60;

            /// <summary>
            ///Half an hour expired
            /// </summary>

            public const int HALF_HOURS = 30;

            /// <summary>
            ///Expiration in 5 minutes
            /// </summary>
            public const int FIVE_MINUTES = 5;

            /// <summary>
            ///Expiration in 1 minute
            /// </summary>
            public const int ONE_MINUTE = 1;

            /// <summary>
            ///Never expire
            /// </summary>

            public const int NEVER = - 1; }...Copy the code

Next we create the cache interface class and implementation class, and then our reference service layer. Application, take the previous article access to GitHub several interfaces to do new cache operations.

Same format as.Application layer, in. Create a Authorize folder in application.Caching, and add the cache interface IAuthorizeCacheService and the implementation class AuthorizeCacheService.

Note the naming conventions; the implementation class must inherit from a common CachingServiceBase class. In the Application. The root directory to add Caching layer MeowvBlogApplicationCachingServiceBase. Cs, ITransientDependency inheritance.

//MeowvBlogApplicationCachingServiceBase.cs
using Microsoft.Extensions.Caching.Distributed;
using Volo.Abp.DependencyInjection;

namespace Meowv.Blog.Application.Caching
{
    public class CachingServiceBase : ITransientDependency
    {
        public IDistributedCache Cache { get; set; }}}Copy the code

IDistributedCache is then injected using property injection. So we just inherit the base class, CachingServiceBase, and we’re happy to use caching.

Add the interface to be cached to IAuthorizeCacheService. Here we use the Func() method. Func() determines what type our interface returns, so add three interfaces as follows:

//IAuthorizeCacheService.cs
using Meowv.Blog.ToolKits.Base;
using System;
using System.Threading.Tasks;

namespace Meowv.Blog.Application.Caching.Authorize
{
    public interface IAuthorizeCacheService
    {
        /// <summary>
        ///Obtain the login address (GitHub)
        /// </summary>
        /// <returns></returns>
        Task<ServiceResult<string>> GetLoginAddressAsync(Func<Task<ServiceResult<string>>> factory);

        /// <summary>
        ///Get AccessToken
        /// </summary>
        /// <param name="code"></param>
        /// <param name="factory"></param>
        /// <returns></returns>
        Task<ServiceResult<string>> GetAccessTokenAsync(string code, Func<Task<ServiceResult<string>>> factory);

        /// <summary>
        ///The login succeeds, and the Token is generated
        /// </summary>
        /// <param name="access_token"></param>
        /// <param name="factory"></param>
        /// <returns></returns>
        Task<ServiceResult<string>> GenerateTokenAsync(string access_token, Func<Task<ServiceResult<string>>> factory); }}Copy the code

Is it similar to IAuthorizeService code? Yes, I just copied it and changed it.

Implement the interface in AuthorizeCacheService.

//AuthorizeCacheService.cs using Meowv.Blog.ToolKits.Base; using Meowv.Blog.ToolKits.Extensions; using System; using System.Threading.Tasks; using static Meowv.Blog.Domain.Shared.MeowvBlogConsts; namespace Meowv.Blog.Application.Caching.Authorize.Impl { public class AuthorizeCacheService : CachingServiceBase, IAuthorizeCacheService { private const string KEY_GetLoginAddress = "Authorize:GetLoginAddress"; private const string KEY_GetAccessToken = "Authorize:GetAccessToken-{0}"; private const string KEY_GenerateToken = "Authorize:GenerateToken-{0}"; // </summary> // <param name="factory"></param> /// <returns></returns> public async Task<ServiceResult<string>> GetLoginAddressAsync(Func<Task<ServiceResult<string>>> factory) { return await Cache.GetOrAddAsync(KEY_GetLoginAddress, factory, CacheStrategy.NEVER); } / / / < summary > / / / get AccessToken / / / < summary > / / / < param name = "code" > < param > / / / < param name = "factory" > < param > / / / <returns></returns> public async Task<ServiceResult<string>> GetAccessTokenAsync(string code, Func<Task<ServiceResult<string>>> factory) { return await Cache.GetOrAddAsync(KEY_GetAccessToken.FormatWith(code), factory, CacheStrategy.FIVE_MINUTES); } /// <summary> // login successful, To generate the Token / / / < / summary > / / / < param name = "access_token" > < param > / / / < param name = "factory" > < param > / / / <returns></returns> public async Task<ServiceResult<string>> GenerateTokenAsync(string access_token, Func<Task<ServiceResult<string>>> factory) { return await Cache.GetOrAddAsync(KEY_GenerateToken.FormatWith(access_token), factory, CacheStrategy.ONE_HOURS); }}}Copy the code

The code is very simple, each cache has a fixed KEY value, according to the parameter to generate the KEY, and then call the extension method written in front, and then give an expiration time, you can see that the KEY contains a colon:, this colon: can play a similar folder operation, in the interface management tool can be very friendly view.

So we’re done with our cache, and then in. Application layer corresponding to the Service call. The code is as follows:

//AuthorizeService.cs using Meowv.Blog.Application.Caching.Authorize; using Meowv.Blog.Domain.Configurations; using Meowv.Blog.ToolKits.Base; using Meowv.Blog.ToolKits.Extensions; using Meowv.Blog.ToolKits.GitHub; using Microsoft.IdentityModel.Tokens; using System; using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Security.Claims; using System.Threading.Tasks; namespace Meowv.Blog.Application.Authorize.Impl { public class AuthorizeService : ServiceBase, IAuthorizeService { private readonly IAuthorizeCacheService _authorizeCacheService; private readonly IHttpClientFactory _httpClient; public AuthorizeService(IAuthorizeCacheService authorizeCacheService, IHttpClientFactory httpClient) { _authorizeCacheService = authorizeCacheService; _httpClient = httpClient; /// </summary> /// </returns> public Async Task<ServiceResult<string>> GetLoginAddressAsync() { return await _authorizeCacheService.GetLoginAddressAsync(async () => { var result = new ServiceResult<string>(); var request = new AuthorizeRequest(); var address = string.Concat(new string[] { GitHubConfig.API_Authorize, "? client_id=", request.Client_ID, "&scope=", request.Scope, "&state=", request.State, "&redirect_uri=", request.Redirect_Uri }); result.IsSuccess(address); return await Task.FromResult(result); }); } /// <summary> // get AccessToken /// </summary> // <param name="code"></param> /// <returns></returns> public async Task<ServiceResult<string>> GetAccessTokenAsync(string code) { var result = new ServiceResult<string>(); If (string.IsNullOrEmpty(code)) {result.isfailed ("code is null "); return result; } return await _authorizeCacheService.GetAccessTokenAsync(code, async () => { var request = new AccessTokenRequest(); var content = new StringContent($"code={code}&client_id={request.Client_ID}&redirect_uri={request.Redirect_Uri}&client_secret={request.Cli ent_Secret}"); content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded"); using var client = _httpClient.CreateClient(); var httpResponse = await client.PostAsync(GitHubConfig.API_AccessToken, content); var response = await httpResponse.Content.ReadAsStringAsync(); if (response.StartsWith("access_token")) result.IsSuccess(response.Split("=")[1].Split("&").First()); Else result.IsFailed("code is incorrect "); return result; }); } /// <summary> // login successful, Generate Token /// </summary> // <param name="access_token"></param> /// <returns></returns> public async Task<ServiceResult<string>> GenerateTokenAsync(string access_token) { var result = new ServiceResult<string>(); If (string.IsNullOrEmpty(access_token)) {result.IsFailed("access_token is null "); return result; } return await _authorizeCacheService.GenerateTokenAsync(access_token, async () => { var url = $"{GitHubConfig.API_User}? access_token={access_token}"; using var client = _httpClient.CreateClient(); Client. DefaultRequestHeaders. Add (" the user-agent ", "Mozilla / 5.0 (Windows NT 10.0; Win64; X64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.14 Safari/ 537.36EDG /83.0.478.13"); var httpResponse = await client.GetAsync(url); if (httpResponse.StatusCode ! = httpStatuscode. OK) {result.IsFailed("access_token is incorrect "); return result; } var content = await httpResponse.Content.ReadAsStringAsync(); var user = content.FromJson<UserResponse>(); If (user.isnull ()) {result.isfailed (" Failed to get user data "); return result; } if (user.Id ! = githubconfig.userid) {result.IsFailed(" current account is not authorized "); return result; } var claims = new[] { new Claim(ClaimTypes.Name, user.Name), new Claim(ClaimTypes.Email, user.Email), new Claim(JwtRegisteredClaimNames.Exp, $"{new DateTimeOffset(DateTime.Now.AddMinutes(AppSettings.JWT.Expires)).ToUnixTimeSeconds()}"), new Claim(JwtRegisteredClaimNames.Nbf, $"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}") }; var key = new SymmetricSecurityKey(AppSettings.JWT.SecurityKey.SerializeUtf8()); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var securityToken = new JwtSecurityToken( issuer: AppSettings.JWT.Domain, audience: AppSettings.JWT.Domain, claims: claims, expires: DateTime.Now.AddMinutes(AppSettings.JWT.Expires), signingCredentials: creds); var token = new JwtSecurityTokenHandler().WriteToken(securityToken); result.IsSuccess(token); return await Task.FromResult(result); }); }}}Copy the code

Directly return our cache interface, when the query Redis exists in the cache of KEY value will not go to our specific implementation method.

Pay attention and don’t forget to. Application layer, dependent on the cache module MeowvBlogApplicationCachingModule is added to the module class or it will error error an error (I just forgot to add…

//MeowvBlogApplicationCachingModule.cs
using Meowv.Blog.Domain;
using Meowv.Blog.Domain.Configurations;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Caching;
using Volo.Abp.Modularity;

namespace Meowv.Blog.Application.Caching
{
    [DependsOn(
        typeof(AbpCachingModule),
        typeof(MeowvBlogDomainModule)
    )]
    public class MeowvBlogApplicationCachingModule : AbpModule
    {
        public override void ConfigureServices(ServiceConfigurationContext context)
        { context.Services.AddStackExchangeRedisCache(options => { options.Configuration = AppSettings.Caching.RedisConnectionString; }); }}}Copy the code

This is the hierarchical directory structure of the project.

Ok, compile and run the project, now go to call the interface to see the effect, in order to be real, here I first wipe out all my Redis cache data.

Access interface,… /auth/url, return data successfully, now go to our redis.

Will be the KEY to success: the Authorize: GetLoginAddress added, directly using RedisDesktopManager view here.

If you call this interface again, as long as it has not expired, it will return data directly.

As you can see, it is possible to fetch the cached data directly, so you can try other interfaces, the same effect.

Is it easy to integrate Redis for data caching with minimal code, did you learn? 😁 😁 😁

Open source: github.com/Meowv/Blog/…