基於 abp vNext 和 .NET Core 開發博客項目 - 使用Redis緩存數據

来源:https://www.cnblogs.com/meowv/archive/2020/05/26/12956696.html
-Advertisement-
Play Games

上一篇文章(https://www.cnblogs.com/meowv/p/12943699.html)完成了項目的全局異常處理和日誌記錄。 在日誌記錄中使用的靜態方法有人指出寫法不是很優雅,遂優化一下上一篇中日誌記錄的方法,具體操作如下: 在.ToolKits層中新建擴展方法Log4NetExte ...


上一篇文章(https://www.cnblogs.com/meowv/p/12943699.html)完成了項目的全局異常處理和日誌記錄。

在日誌記錄中使用的靜態方法有人指出寫法不是很優雅,遂優化一下上一篇中日誌記錄的方法,具體操作如下:

.ToolKits層中新建擴展方法Log4NetExtensions.cs

//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"));

            return hostBuilder;
        }
    }
}

配置log4net,然後我們直接返回IHostBuilder對象,便於在Main方法中鏈式調用。

//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)
        {
            await Host.CreateDefaultBuilder(args)
                      .UseLog4Net()
                      .ConfigureWebHostDefaults(builder =>
                      {
                          builder.UseIISIntegration()
                                 .UseStartup<Startup>();
                      }).UseAutofac().Build().RunAsync();
        }
    }
}

然後修改MeowvBlogExceptionFilter過濾器,代碼如下:

//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>
        /// 異常處理
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public void OnException(ExceptionContext context)
        {
            // 錯誤日誌記錄
            _log.Error($"{context.HttpContext.Request.Path}|{context.Exception.Message}", context.Exception);
        }
    }
}

可以刪掉之前添加的LoggerHelper.cs類,運行一下,同樣可以達到預期效果。


本篇將集成Redis,使用Redis來緩存數據,使用方法參考的微軟官方文檔:https://docs.microsoft.com/zh-cn/aspnet/core/performance/caching/distributed

關於Redis的介紹這裡就不多說了,這裡有一篇快速入門的文章:Redis快速入門及使用,對於不瞭解的同學可以看看。

直入主題,先在appsettings.json配置Redis的連接字元串。

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

對應的,在AppSettings.cs中讀取。

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

.Application.Caching層添加包Microsoft.Extensions.Caching.StackExchangeRedis,然後在模塊類MeowvBlogApplicationCachingModule中添加配置緩存實現。

//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
            });
        }
    }
}

options.Configuration是 Redis 的連接字元串。

options.InstanceNam是 Redis 實例名稱,這裡沒填。

options.ConfigurationOptions是 Redis 的配置屬性,如果配置了這個字,將優先於 Configuration 中的配置,同時它支持更多的選項。我這裡也沒填。

緊接著我們就可以直接使用了,直接將IDistributedCache介面依賴關係註入即可。

0

可以看到預設已經實現了這麼多常用的介面,已經夠我這個小項目用的了,同時在Microsoft.Extensions.Caching.Distributed.DistributedCacheExtensions中微軟還給我們提供了很多擴展方法。

於是,我們我就想到寫一個新的擴展方法,可以同時處理獲取和添加緩存的操作,當緩存存在時,直接返回,不存在時,添加緩存。

新建MeowvBlogApplicationCachingExtensions.cs擴展方法,如下:

//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>
        /// 獲取或添加緩存
        /// </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>();
            }

            return cacheItem;
        }
    }
}

我們可以在DistributedCacheEntryOptions中可以配置我們的緩存過期時間,其中有一個判斷條件,就是當minutes = -1的時候,不指定過期時間,那麼我們的緩存就不會過期了。

GetStringAsync()SetStringAsync()DistributedCacheExtensions的擴展方法,最終會將緩存項cacheItem轉換成JSON格式進行存儲。

CacheStrategy是在.Domain.Shared層定義的緩存過期時間策略常量。

//MeowvBlogConsts.cs
...
        /// <summary>
        /// 緩存過期時間策略
        /// </summary>
        public static class CacheStrategy
        {
            /// <summary>
            /// 一天過期24小時
            /// </summary>

            public const int ONE_DAY = 1440;

            /// <summary>
            /// 12小時過期
            /// </summary>

            public const int HALF_DAY = 720;

            /// <summary>
            /// 8小時過期
            /// </summary>

            public const int EIGHT_HOURS = 480;

            /// <summary>
            /// 5小時過期
            /// </summary>

            public const int FIVE_HOURS = 300;

            /// <summary>
            /// 3小時過期
            /// </summary>

            public const int THREE_HOURS = 180;

            /// <summary>
            /// 2小時過期
            /// </summary>

            public const int TWO_HOURS = 120;

            /// <summary>
            /// 1小時過期
            /// </summary>

            public const int ONE_HOURS = 60;

            /// <summary>
            /// 半小時過期
            /// </summary>

            public const int HALF_HOURS = 30;

            /// <summary>
            /// 5分鐘過期
            /// </summary>
            public const int FIVE_MINUTES = 5;

            /// <summary>
            /// 1分鐘過期
            /// </summary>
            public const int ONE_MINUTE = 1;

            /// <summary>
            /// 永不過期
            /// </summary>

            public const int NEVER = -1;
        }
...

接下來去創建緩存介面類和實現類,然後再我們的引用服務層.Application中進行調用,拿上一篇中接入GitHub的幾個介面來做新增緩存操作。

.Application層格式一樣,在.Application.Caching中新建Authorize文件夾,添加緩存介面IAuthorizeCacheService和實現類AuthorizeCacheService

註意命名規範,實現類肯定要繼承一個公共的CachingServiceBase基類。在.Application.Caching層根目錄添加MeowvBlogApplicationCachingServiceBase.cs,繼承ITransientDependency

//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; }
    }
}

然後使用屬性註入的方式,註入IDistributedCache。這樣我們只要繼承了基類:CachingServiceBase,就可以愉快的使用緩存了。

添加要緩存的介面到IAuthorizeCacheService,在這裡我們使用Func()方法,我們的介面返回什麼類型由Func()來決定,於是添加三個介面如下:

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

namespace Meowv.Blog.Application.Caching.Authorize
{
    public interface IAuthorizeCacheService
    {
        /// <summary>
        /// 獲取登錄地址(GitHub)
        /// </summary>
        /// <returns></returns>
        Task<ServiceResult<string>> GetLoginAddressAsync(Func<Task<ServiceResult<string>>> factory);

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

        /// <summary>
        /// 登錄成功,生成Token
        /// </summary>
        /// <param name="access_token"></param>
        /// <param name="factory"></param>
        /// <returns></returns>
        Task<ServiceResult<string>> GenerateTokenAsync(string access_token, Func<Task<ServiceResult<string>>> factory);
    }
}

是不是和IAuthorizeService代碼很像,的確,我就是直接複製過來改的。

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>
        /// 獲取登錄地址(GitHub)
        /// </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>
        /// 獲取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>
        /// 登錄成功,生成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);
        }
    }
}

代碼很簡單,每個緩存都有固定KEY值,根據參數生成KEY,然後調用前面寫的擴展方法,再給一個過期時間即可,可以看到KEY裡面包含了冒號 :,這個冒號 : 可以起到類似於文件夾的操作,在界面化管理工具中可以很友好的查看。

這樣我們的緩存就搞定了,然後在.Application層對應的Service中進行調用。代碼如下:

//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>
        /// 獲取登錄地址(GitHub)
        /// </summary>
        /// <returns></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>
        /// 獲取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為空");
                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.Client_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不正確");

                return result;
            });
        }

        /// <summary>
        /// 登錄成功,生成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為空");
                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("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.14 Safari/537.36 Edg/83.0.478.13");
                var httpResponse = await client.GetAsync(url);
                if (httpResponse.StatusCode != HttpStatusCode.OK)
                {
                    result.IsFailed("access_token不正確");
                    return result;
                }

                var content = await httpResponse.Content.ReadAsStringAsync();

                var user = content.FromJson<UserResponse>();
                if (user.IsNull())
                {
                    result.IsFailed("未獲取到用戶數據");
                    return result;
                }

                if (user.Id != GitHubConfig.UserId)
                {
                    result.IsFailed("當前賬號未授權");
                    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);
            });
        }
    }
}

直接return我們的緩存介面,當查詢到Redis中存在KEY值的緩存就不會再走我們的具體的實現方法了。

註意註意,千萬不要忘了在.Application層的模塊類中添加依賴緩存模塊MeowvBlogApplicationCachingModule,不然就會報錯報錯報錯(我就是忘了添加...)

//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;
            });
        }
    }
}

此時項目的層級目錄結構。

2

好的,編譯運行項目,現在去調用介面看看效果,為了真實,這裡我先將我redis緩存數據全部幹掉。

3

訪問介面,.../auth/url,成功返回數據,現在再去看看我們的redis。

4

成功將KEY為:Authorize:GetLoginAddress 添加進去了,這裡直接使用RedisDesktopManager進行查看。

5

那麼再次調用這個介面,只要沒有過期,就會直接返回數據了,調試圖如下:

6

可以看到,是可以直接取到緩存數據的,其他介面大家自己試試吧,一樣的效果。

是不是很簡單,用最少的代碼集成Redis進行數據緩存,你學會了嗎?

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 當用戶嚮應用程式發出請求時,伺服器將解析該請求,生成響應,然後將結果發送給客戶端。用戶可能會在伺服器處理請求的時候中止請求。就比如說用戶跳轉到另一個頁面中獲取說關閉頁面。在這種情況下,我們希望停止所有正在進行的工作,以浪費不必要的資源。例如我們可能要取消SQL請求、http調用請求、CPU密集型操作 ...
  • 一.相關介紹 Dockerfile:關於Dockerfile的使用說明,我在文章《讓.NetCore程式跑在任何有docker的地方》中有說到,這裡不在贅述,需要的可以先看下,本文主要介紹Jenkinsfile結合dockerfile配合使用,自動構建.NetCore應用程式。 Jenkinsfil ...
  • 一:背景 1. 講故事 去年阿裡聚石塔上的所有isv簡訊通道全部對接阿裡通信,我們就做了對接改造,使用阿裡提供的.net sdk。 網址:https://help.aliyun.com/document_detail/114480.html 同事當時使用的是ons-.net v1.1.3版本,程式上 ...
  • 藍牙設置相關界面,以下是通過C#方式打開的幾個方式,記錄一下 藍牙設置界面 1.控制面板命令bthprops.cpl 可以用控制面板 control+bthprops.cpl,也可以直接bthprops.cpl。更多的命令見:所有運行命令指令大全、CMD & CPL:快捷系統命令和控制面板命令 bt ...
  • 前言 之前我寫過一篇關於 Blazor WebAssembly 的文章瀏覽器中的 .Net Core —— Blazor WebAssembly 初體驗,如今已經更新到 RC-1,與預覽版有著較大的差異,為此補充這篇文章。 正文 與預覽版的主要差異 1、這次的候選版修改了大部分包名和命名空間,因此無 ...
  • 最新.net core 圖片合併生成二維碼合成圖片 圖片添加文字 先安裝幾個包 獲取地址如下 https://www.nuget.org/packages/QRCoder/ https://www.nuget.org/packages/SixLabors.Fonts/ https://www.nug ...
  • 0. 前言 前一篇我們詳細的介紹了SqlSugar的增刪改查,那些已經滿足我們在日常工程開發中的使用了。但是還有一點點在開發中並不常用,但是卻非常有用的方法。接下來讓我們一起來看看還有哪些有意思的內容。 1. 不同尋常的查詢 之前介紹了針對單個表的查詢,同樣也是相對簡單的查詢模式。雖然開發完全夠用, ...
  • 先安裝幾個包 獲取地址如下https://www.nuget.org/packages/QRCoder/https://www.nuget.org/packages/SixLabors.Fonts/https://www.nuget.org/packages/SixLabors.ImageSharp ...
一周排行
    -Advertisement-
    Play Games
  • 概述:在C#中,++i和i++都是自增運算符,其中++i先增加值再返回,而i++先返回值再增加。應用場景根據需求選擇,首碼適合先增後用,尾碼適合先用後增。詳細示例提供清晰的代碼演示這兩者的操作時機和實際應用。 在C#中,++i 和 i++ 都是自增運算符,但它們在操作上有細微的差異,主要體現在操作的 ...
  • 上次發佈了:Taurus.MVC 性能壓力測試(ap 壓測 和 linux 下wrk 壓測):.NET Core 版本,今天計劃準備壓測一下 .NET 版本,來測試並記錄一下 Taurus.MVC 框架在 .NET 版本的性能,以便後續持續優化改進。 為了方便對比,本文章的電腦環境和測試思路,儘量和... ...
  • .NET WebAPI作為一種構建RESTful服務的強大工具,為開發者提供了便捷的方式來定義、處理HTTP請求並返迴響應。在設計API介面時,正確地接收和解析客戶端發送的數據至關重要。.NET WebAPI提供了一系列特性,如[FromRoute]、[FromQuery]和[FromBody],用 ...
  • 原因:我之所以想做這個項目,是因為在之前查找關於C#/WPF相關資料時,我發現講解圖像濾鏡的資源非常稀缺。此外,我註意到許多現有的開源庫主要基於CPU進行圖像渲染。這種方式在處理大量圖像時,會導致CPU的渲染負擔過重。因此,我將在下文中介紹如何通過GPU渲染來有效實現圖像的各種濾鏡效果。 生成的效果 ...
  • 引言 上一章我們介紹了在xUnit單元測試中用xUnit.DependencyInject來使用依賴註入,上一章我們的Sample.Repository倉儲層有一個批量註入的介面沒有做單元測試,今天用這個示例來演示一下如何用Bogus創建模擬數據 ,和 EFCore 的種子數據生成 Bogus 的優 ...
  • 一、前言 在自己的項目中,涉及到實時心率曲線的繪製,項目上的曲線繪製,一般很難找到能直接用的第三方庫,而且有些還是定製化的功能,所以還是自己繪製比較方便。很多人一聽到自己畫就害怕,感覺很難,今天就分享一個完整的實時心率數據繪製心率曲線圖的例子;之前的博客也分享給DrawingVisual繪製曲線的方 ...
  • 如果你在自定義的 Main 方法中直接使用 App 類並啟動應用程式,但發現 App.xaml 中定義的資源沒有被正確載入,那麼問題可能在於如何正確配置 App.xaml 與你的 App 類的交互。 確保 App.xaml 文件中的 x:Class 屬性正確指向你的 App 類。這樣,當你創建 Ap ...
  • 一:背景 1. 講故事 上個月有個朋友在微信上找到我,說他們的軟體在客戶那邊隔幾天就要崩潰一次,一直都沒有找到原因,讓我幫忙看下怎麼回事,確實工控類的軟體環境複雜難搞,朋友手上有一個崩潰的dump,剛好丟給我來分析一下。 二:WinDbg分析 1. 程式為什麼會崩潰 windbg 有一個厲害之處在於 ...
  • 前言 .NET生態中有許多依賴註入容器。在大多數情況下,微軟提供的內置容器在易用性和性能方面都非常優秀。外加ASP.NET Core預設使用內置容器,使用很方便。 但是筆者在使用中一直有一個頭疼的問題:服務工廠無法提供請求的服務類型相關的信息。這在一般情況下並沒有影響,但是內置容器支持註冊開放泛型服 ...
  • 一、前言 在項目開發過程中,DataGrid是經常使用到的一個數據展示控制項,而通常表格的最後一列是作為操作列存在,比如會有編輯、刪除等功能按鈕。但WPF的原始DataGrid中,預設只支持固定左側列,這跟大家習慣性操作列放最後不符,今天就來介紹一種簡單的方式實現固定右側列。(這裡的實現方式參考的大佬 ...