主頁 > .NET開發 > [Abp vNext 原始碼分析] - 20. 電子郵件與短信支持

[Abp vNext 原始碼分析] - 20. 電子郵件與短信支持

2021-06-29 06:01:42 .NET開發

一、簡介

ABP vNext 使用 Volo.Abp.Sms 包和 Volo.Abp.Emailing 包將短信和電子郵件作為基礎設施進行了抽象,開發人員僅需要在使用的時候注入 ISmsSenderIEmailSender 即可實作短信發送和郵件發送,

二、原始碼分析

2.1 啟動模塊

短信發送的抽象層比較簡單,AbpSmsModule 模塊內部并無任何操作,僅作為空模塊進行定義,

電子郵件的 AbpEmailingModule 模塊內,主要添加了一些本地化資源支持,另一個動作就是添加了一個 BackgroundEmailSendingJob 后臺作業,這個后臺作業主要是用于后續發送電子郵件使用,因為郵件發送這個動作實時性要求并不高,在實際的業務實踐當中,我們基本會將其加入到一個后臺佇列慢慢發送,所以這里 ABP 為我們實作了 BackgroundEmailSendingJob

BackgroundEmailSendingJob.cs:

public class BackgroundEmailSendingJob : AsyncBackgroundJob<BackgroundEmailSendingJobArgs>, ITransientDependency
{
    protected IEmailSender EmailSender { get; }

    public BackgroundEmailSendingJob(IEmailSender emailSender)
    {
        EmailSender = emailSender;
    }

    public override async Task ExecuteAsync(BackgroundEmailSendingJobArgs args)
    {
        if (args.From.IsNullOrWhiteSpace())
        {
            await EmailSender.SendAsync(args.To, args.Subject, args.Body, args.IsBodyHtml);
        }
        else
        {
            await EmailSender.SendAsync(args.From, args.To, args.Subject, args.Body, args.IsBodyHtml);
        }
    }
}

這個后臺任務的邏輯也不復雜,就使用 IEmailSender 發送郵件,我們在任何地方需要后臺發送郵件的時,只需要注入 IBackgroundJobManager,使用 BackgroundEmailSendingJobArgs 作為引數添加入隊一個后臺作業即可,

使用 IBackgroundJobManager 添加一個新的郵件發送歡迎郵件:

public class DemoClass
{
    private readonly IBackgroundJobManager _backgroundJobManager;
    private readonly IUserInfoRepository _userRep;

    public DemoClass(IBackgroundJobManager backgroundJobManager,
        IUserInfoRepository userRep)
    {
        _backgroundJobManager = backgroundJobManager;
        _userRep = userRep;
    }

    public async Task SendWelcomeEmailAsync(Guid userId)
    {
        var userInfo = await _userRep.GetByIdAsync(userId);

        await _backgroundJobManager.EnqueueAsync(new BackgroundEmailSendingJobArgs
        {
            To = userInfo.EmailAddress,
            Subject = "Welcome",
            Body = "Welcome, Hello World!",
            IsBodyHtml = false;
        });
    }
}

注意

目前 BackgroundEmailSendingJobArgs 引數不支持發送附件,ABP 可能在以后的版本會進行實作,

2.2 Email 的核心組件

ABP 定義了一個 IEmailSender 介面,定義了多個 SendAsync() 方法多載,用于直接發送電子郵件,同時也提供了 QueueAsync() 方法,通過后臺任務佇列來發送郵件,

public interface IEmailSender
{
    Task SendAsync(
        string to,
        string subject,
        string body,
        bool isBodyHtml = true
    );

    Task SendAsync(
        string from,
        string to,
        string subject,
        string body,
        bool isBodyHtml = true
    );

    Task SendAsync(
        MailMessage mail,
        bool normalize = true
    );

    Task QueueAsync(
        string to,
        string subject,
        string body,
        bool isBodyHtml = true
    );

    Task QueueAsync(
        string from,
        string to,
        string subject,
        string body,
        bool isBodyHtml = true
    );

    //TODO: 準備添加的 QueueAsync 方法,目前存在的問題: MailMessage 不能夠被序列化,所以不能加入到后臺任務佇列當中,
}

ABP 實際擁有兩種 Email Sender 實作,分別是 SmtpEmailSenderMailkitEmailSender,各個型別的關系如下,

UML 類圖:

classDiagram class IEmailSender{ <<Interface>> +SendAsync(string,string,string,bool=true) Task +SendAsync(string,string,string,string,bool=true) Task +SendAsync(MailMessage,bool=true) Task +QueueAsync(string,string,string,bool=true) Task +QueueAsync(string,string,string,string,bool=true) Task } class ISmtpEmailSender{ <<Interface>> ...... +BuildClientAsync() Task~SmtpClient~ } class IMailKitSmtpEmailSemder{ <<Interface>> ...... +BuildClientAsync() Task~SmtpClient~ } class EmailSenderBase{ <<Abstract>> ...... } class SmtpEmailSender{ ...... } class MailKitSmtpEmailSender{ ...... } class NullEmailSender{ ...... } ISmtpEmailSender --|> IEmailSender: 繼承 IMailKitSmtpEmailSemder --|> IEmailSender: 繼承 EmailSenderBase ..|> IEmailSender: 實作 SmtpEmailSender ..|> ISmtpEmailSender: 實作 SmtpEmailSender --|> EmailSenderBase: 繼承 NullEmailSender --|> EmailSenderBase: 繼承 MailKitSmtpEmailSender ..|> IMailKitSmtpEmailSemder: 實作 MailKitSmtpEmailSender --|> EmailSenderBase: 繼承

可以從 UML 類圖看出,每個 EmailSender 實作都與一個 IXXXConfiguration 對應,這個配置類存盤了基于 Smtp 發件的必須配置,因為 MailKit 本身也是基于 Smtp 發送郵件的,所以沒有重新定義新的配置類,而是直接復用的 ISmtpEmailSenderConfiguration 介面與實作,

EmailSenderBase 基類當中,基本實作了 IEmailSender 介面的所有方法的邏輯,只留下了 SendEmailAsync(MailMessage mail) 作為一個抽象方法等待子類實作,也就是說其他的方法最終都是使用該方法來最終發送郵件,

public abstract class EmailSenderBase : IEmailSender
{
    protected IEmailSenderConfiguration Configuration { get; }

    protected IBackgroundJobManager BackgroundJobManager { get; }

    protected EmailSenderBase(IEmailSenderConfiguration configuration, IBackgroundJobManager backgroundJobManager)
    {
        Configuration = configuration;
        BackgroundJobManager = backgroundJobManager;
    }

    // ... 實作的介面方法

    protected abstract Task SendEmailAsync(MailMessage mail);

    // 使用 Configuration 里面的引數,統一處理郵件資料,
    protected virtual async Task NormalizeMailAsync(MailMessage mail)
    {
        if (mail.From == null || mail.From.Address.IsNullOrEmpty())
        {
            mail.From = new MailAddress(
                await Configuration.GetDefaultFromAddressAsync(),
                await Configuration.GetDefaultFromDisplayNameAsync(),
                Encoding.UTF8
                );
        }

        if (mail.HeadersEncoding == null)
        {
            mail.HeadersEncoding = Encoding.UTF8;
        }

        if (mail.SubjectEncoding == null)
        {
            mail.SubjectEncoding = Encoding.UTF8;
        }

        if (mail.BodyEncoding == null)
        {
            mail.BodyEncoding = Encoding.UTF8;
        }
    }
}

ABP 默認可用的郵件發送組件是 SmtpEmailSender,它使用的是 .NET 自帶的郵件發送組件,本質上就是構建了一個 SmtpClient 客戶端,然后呼叫它的發件方法進行郵件發送,

public class SmtpEmailSender : EmailSenderBase, ISmtpEmailSender, ITransientDependency
{
    // ... 省略的代碼,
    public async Task<SmtpClient> BuildClientAsync()
    {
        var host = await SmtpConfiguration.GetHostAsync();
        var port = await SmtpConfiguration.GetPortAsync();

        var smtpClient = new SmtpClient(host, port);

        // 從 SettingProvider 中獲取各個配置引數,構建 Client 進行發送,
        try
        {
            if (await SmtpConfiguration.GetEnableSslAsync())
            {
                smtpClient.EnableSsl = true;
            }

            if (await SmtpConfiguration.GetUseDefaultCredentialsAsync())
            {
                smtpClient.UseDefaultCredentials = true;
            }
            else
            {
                smtpClient.UseDefaultCredentials = false;

                var userName = await SmtpConfiguration.GetUserNameAsync();
                if (!userName.IsNullOrEmpty())
                {
                    var password = await SmtpConfiguration.GetPasswordAsync();
                    var domain = await SmtpConfiguration.GetDomainAsync();
                    smtpClient.Credentials = !domain.IsNullOrEmpty()
                        ? new NetworkCredential(userName, password, domain)
                        : new NetworkCredential(userName, password);
                }
            }

            return smtpClient;
        }
        catch
        {
            smtpClient.Dispose();
            throw;
        }
    }

    protected override async Task SendEmailAsync(MailMessage mail)
    {
        // 呼叫構建方法,構建 Client,用于發送 mail 資料,
        using (var smtpClient = await BuildClientAsync())
        {
            await smtpClient.SendMailAsync(mail);
        }
    }
}

針對屬性注入失敗的情況,ABP 提供了 NullEmailSender 作為默認實作,在發送郵件的時候會使用 Logger 列印具體的資訊,

public class NullEmailSender : EmailSenderBase
{
    public ILogger<NullEmailSender> Logger { get; set; }

    public NullEmailSender(IEmailSenderConfiguration configuration, IBackgroundJobManager backgroundJobManager)
        : base(configuration, backgroundJobManager)
    {
        Logger = NullLogger<NullEmailSender>.Instance;
    }

    protected override Task SendEmailAsync(MailMessage mail)
    {
        Logger.LogWarning("USING NullEmailSender!");
        Logger.LogDebug("SendEmailAsync:");
        LogEmail(mail);
        return Task.FromResult(0);
    }

    // ... 其他方法,
}

2.3 Email 的配置存盤

EmailSenderBase 里面可以看到,它從 IEmailSenderConfiguration 當中獲取發件人的郵箱地址和展示名稱,它的 UML 類圖關系如下,

classDiagram class IEmailSenderConfiguration{ <<Interface>> +GetDefaultFromAddressAsync() Task~string~ +GetDefaultFromDisplayNameAsync() Task~string~ } class ISmtpEmailSenderConfiguration{ <<Interface>> +GetHostAsync() Task~string~ +GetPortAsync() Task~int~ +GetUserNameAsync() Task~string~ +GetPasswordAsync() Task~string~ +GetDomainAsync() Task~string~ +GetEnableSslAsync() Task~bool~ +GetUseDefaultCredentialsAsync() Task~bool~ } class EmailSenderConfiguration{ #GetNotEmptySettingValueAsync(string name) Task~string~ } class SmtpEmailSenderConfiguration{ } class ISettingProvider{ <<Interface>> +GetOrNullAsync(string name) Task~string~ } ISmtpEmailSenderConfiguration --|> IEmailSenderConfiguration: 繼承 EmailSenderConfiguration ..|> IEmailSenderConfiguration: 實作 EmailSenderConfiguration ..> ISettingProvider: 依賴 SmtpEmailSenderConfiguration --|> EmailSenderConfiguration: 繼承 SmtpEmailSenderConfiguration ..|> ISmtpEmailSenderConfiguration: 實作

可以看到組態檔時通過 ISettingProvider 獲取的,這樣就可以保證從不同租戶甚至是用戶來獲取發件人的配置資訊,這里值得注意的是在 EmailSenderConfiguration 中,實作了一個 GetNotEmptySettingValueAsync(string name) 方法,該方法主要是封裝了獲取邏輯,當值不存在的時候拋出 AbpException 例外,

protected async Task<string> GetNotEmptySettingValueAsync(string name)
{
    var value = https://www.cnblogs.com/myzony/p/await SettingProvider.GetOrNullAsync(name);

    if (value.IsNullOrEmpty())
    {
        throw new AbpException($"Setting value for '{name}' is null or empty!");
    }

    return value;
}

至于 SmtpEmailSenderConfiguration,只是提供了其他的屬性獲取(密碼、埠等)而已,本質上還是呼叫的 GetNotEmptySettingValueAsync() 方法從 SettingProvider 中獲取具體的配置資訊,

sequenceDiagram 發送郵件 ->> Smtp 配置類: 1.GetHostAsync() Smtp 配置類 ->> Email 配置類: 2.GetNotEmptySettingValueAsync("HotsItem") Email 配置類 ->> Setting Provider: 3.GetOrNullAsync("HotsItem") Setting Provider -->> 發送郵件: 4.獲得主機資料,

關于配置名稱的常量,都在 EmailSettingNames 里面進行定義,并使用 EmailSettingProvider 將其注冊到 ABP 的配置模塊當中:

EmailSettingNames.cs

namespace Volo.Abp.Emailing
{
    public static class EmailSettingNames
    {
        public const string DefaultFromAddress = "Abp.Mailing.DefaultFromAddress";

        public const string DefaultFromDisplayName = "Abp.Mailing.DefaultFromDisplayName";

        public static class Smtp
        {
            public const string Host = "Abp.Mailing.Smtp.Host";

            public const string Port = "Abp.Mailing.Smtp.Port";

            // ... 其他常量定義,
        }
    }
}

EmailSettingProvider.cs

internal class EmailSettingProvider : SettingDefinitionProvider
{
    public override void Define(ISettingDefinitionContext context)
    {
        context.Add(
            new SettingDefinition(
                EmailSettingNames.Smtp.Host, 
                "127.0.0.1", 
                L("DisplayName:Abp.Mailing.Smtp.Host"), 
                L("Description:Abp.Mailing.Smtp.Host")),

            new SettingDefinition(EmailSettingNames.Smtp.Port, 
                "25", 
                L("DisplayName:Abp.Mailing.Smtp.Port"), 
                L("Description:Abp.Mailing.Smtp.Port")),
                // ... 其他配置引數,
        );
    }

    private static LocalizableString L(string name)
    {
        return LocalizableString.Create<EmailingResource>(name);
    }
}

2.4 郵件模板

文字模板是 ABP 后續提供的一個新的模塊,它可以讓開發人員預先定義文本模板,然后使用時根據物件資料替換模板中的內容,并且 ABP 提供的文本模板還支持本地化,關于文本模板的功能,我們后續單獨會寫一篇文章進行說明,在這里只是大概 Mail 是如何使用的,

在專案當中,ABP 僅定義了兩個 *.tpl 的模板檔案,分別是控制布局的 Layout.tpl,還有渲染具體訊息的 Message.tpl,同權限、Setting 一樣,模板也會使用一個 StandardEmailTemplates 型別定義模板的編碼常量,并且實作一個 XXXDefinitionProvider 型別將其注入到 ABP 框架當中,

StandardEmailTemplates.cs

public static class StandardEmailTemplates
{
    public const string Layout = "Abp.StandardEmailTemplates.Layout";
    public const string Message = "Abp.StandardEmailTemplates.Message";
}

StandardEmailTemplateDefinitionProvider.cs

public class StandardEmailTemplateDefinitionProvider : TemplateDefinitionProvider
{
    public override void Define(ITemplateDefinitionContext context)
    {
        context.Add(
            new TemplateDefinition(
                StandardEmailTemplates.Layout,
                displayName: LocalizableString.Create<EmailingResource>("TextTemplate:StandardEmailTemplates.Layout"),
                isLayout: true
            ).WithVirtualFilePath("/Volo/Abp/Emailing/Templates/Layout.tpl", true)
        );

        context.Add(
            new TemplateDefinition(
                StandardEmailTemplates.Message,
                displayName: LocalizableString.Create<EmailingResource>("TextTemplate:StandardEmailTemplates.Message"),
                layout: StandardEmailTemplates.Layout
            ).WithVirtualFilePath("/Volo/Abp/Emailing/Templates/Message.tpl", true)
        );
    }
}

2.5 MailKit 集成

MailKit 是一個優秀跨平臺的 .NET 郵件操作庫,它的官方 GitHub 地址為 https://github.com/jstedfast/MailKit ,支持很多高級特性,這里我就不再詳細介紹 MailKit 的其他特性,只是講解一下 MailKit 同 ABP 自帶的郵件模塊是如何集成的,

官方的 Volo.Abp.MailKit 包僅包含 4 個檔案,它們分別是 AbpMailKitModule.cs (空模塊,占位)、AbpMailKitOptions.cs (MailKit 的特殊配置)、IMailKitSmtpEmailSender.cs (實作了 IEmailSender 基類的一個介面)、MailKitSmtpEmailSender.cs (具體的發送邏輯實作),

需要注意一下,這里針對 MailKit 的特殊配置是使用的 IConfiguration 里面的資料(通常是 appsetting.json),而不是從 Abp.Settings 里面獲取的,

MailKitSmtpEmailSender.cs

[Dependency(ServiceLifetime.Transient, ReplaceServices = true)]
public class MailKitSmtpEmailSender : EmailSenderBase, IMailKitSmtpEmailSender
{
    protected AbpMailKitOptions AbpMailKitOptions { get; }

    protected ISmtpEmailSenderConfiguration SmtpConfiguration { get; }

    // ... 建構式,

    protected override async Task SendEmailAsync(MailMessage mail)
    {
        using (var client = await BuildClientAsync())
        {
            // 使用了 mail 引數來構造 MailKit 的物件,
            var message = MimeMessage.CreateFromMailMessage(mail);
            await client.SendAsync(message);
            await client.DisconnectAsync(true);
        }
    }

    // 構造 MailKit 所需要的 Client 物件,
    public async Task<SmtpClient> BuildClientAsync()
    {
        var client = new SmtpClient();

        try
        {
            await ConfigureClient(client);
            return client;
        }
        catch
        {
            client.Dispose();
            throw;
        }
    }

    // 進行一些基本配置,比如服務器資訊和密碼資訊等,
    protected virtual async Task ConfigureClient(SmtpClient client)
    {
        await client.ConnectAsync(
            await SmtpConfiguration.GetHostAsync(),
            await SmtpConfiguration.GetPortAsync(),
            await GetSecureSocketOption()
        );

        if (await SmtpConfiguration.GetUseDefaultCredentialsAsync())
        {
            return;
        }

        await client.AuthenticateAsync(
            await SmtpConfiguration.GetUserNameAsync(),
            await SmtpConfiguration.GetPasswordAsync()
        );
    }

    // 根據 Option 的值獲取一些安全配置,
    protected virtual async Task<SecureSocketOptions> GetSecureSocketOption()
    {
        if (AbpMailKitOptions.SecureSocketOption.HasValue)
        {
            return AbpMailKitOptions.SecureSocketOption.Value;
        }

        return await SmtpConfiguration.GetEnableSslAsync()
            ? SecureSocketOptions.SslOnConnect
            : SecureSocketOptions.StartTlsWhenAvailable;
    }
}

2.6 短信發送的核心組件

短信發送僅提供了一個 ISmsSender 介面,該介面有提供一個發送方法,ABP 官方提供了 Aliyun 的短信發送功能(Volo.Abp.Sms.Aliyun),

UML 圖:

classDiagram class ISmsSender{ <<Interface>> SendAsync(SmsMessage smsMessage) Task } class NullSmsSender{ } class SmsMessage{ +string PhoneNumber +string Text +IDictionary~string, object~ Properties } NullSmsSender ..|> ISmsSender: 實作 ISmsSender ..> SmsMessage: 依賴

功能比較簡單,重點是 SmsMessage 里面的引數,第一個是發送的號碼,第二個是發送的內容,僅憑上述引數肯定不夠,所以 ABP 提供了一個屬性字典,便于我們傳入一些特定的引數,

三、總結

ABP 將 Email 這塊功能封裝成了單獨的模塊,便于開發人員進行郵件發送,并且官方也提供了 MailKit 的支持,我們可以根據自己的需求來替換不同的實作,只不過針對于一些異步郵件發送的場景,目前還不能很好的支持(主要是使用了 MailMessage 無法序列化),

我覺得 ABP 應該自己定義一個 Context 型別,反轉依賴,在具體的實作當中確定郵件發送的物件型別,或者是將默認的 Smtp 發送者獨立出來一個模塊,就跟 MailKit 一樣,使用 ABP 的 Context 型別來構造 MailMessage 物件,

四、總目錄

歡迎翻閱作者的其他文章,請 點擊我 進行跳轉,如果你覺得本篇文章對你有幫助,請點擊文章末尾的 推薦按鈕

最后更新時間: 2021年6月27日 23點31分

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/288590.html

標籤:.NET Core

上一篇:C#學習筆記1.0.0-HelloWorld

下一篇:[Abp vNext 原始碼分析] - 20. 電子郵件與短信支持

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • WebAPI簡介

    Web體系結構: 有三個核心:資源(resource),URL(統一資源識別符號)和表示 他們的關系是這樣的:一個資源由一個URL進行標識,HTTP客戶端使用URL定位資源,表示是從資源回傳資料,媒體型別是資源回傳的資料格式。 接下來我們說下HTTP. HTTP協議的系統是一種無狀態的方式,使用請求/ ......

    uj5u.com 2020-09-09 22:07:47 more
  • asp.net core 3.1 入口:Program.cs中的Main函式

    本文分析Program.cs 中Main()函式中代碼的運行順序分析asp.net core程式的啟動,重點不是剖析原始碼,而是理清程式開始時執行的順序。到呼叫了哪些實體,哪些法方。asp.net core 3.1 的程式入口在專案Program.cs檔案里,如下。ususing System; us ......

    uj5u.com 2020-09-09 22:07:49 more
  • asp.net網站作為websocket服務端的應用該如何寫

    最近被websocket的一個問題困擾了很久,有一個需求是在web網站中搭建websocket服務。客戶端通過網頁與服務器建立連接,然后服務器根據ip給客戶端網頁發送資訊。 其實,這個需求并不難,只是剛開始對websocket的內容不太了解。上網搜索了一下,有通過asp.net core 實作的、有 ......

    uj5u.com 2020-09-09 22:08:02 more
  • ASP.NET 開源匯入匯出庫Magicodes.IE Docker中使用

    Magicodes.IE在Docker中使用 更新歷史 2019.02.13 【Nuget】版本更新到2.0.2 【匯入】修復單列匯入的Bug,單元測驗“OneColumnImporter_Test”。問題見(https://github.com/dotnetcore/Magicodes.IE/is ......

    uj5u.com 2020-09-09 22:08:05 more
  • 在webform中使用ajax

    如果你用過Asp.net webform, 說明你也算是.NET 開發的老兵了。WEBform應該是2011 2013左右,當時還用visual studio 2005、 visual studio 2008。后來基本都用的是MVC。 如果是新開發的專案,估計沒人會用webform技術。但是有些舊版 ......

    uj5u.com 2020-09-09 22:08:50 more
  • iis添加asp.net網站,訪問提示:由于擴展配置問題而無法提供您請求的

    今天在iis服務器配置asp.net網站,遇到一個問題,記錄一下: 問題:由于擴展配置問題而無法提供您請求的頁面。如果該頁面是腳本,請添加處理程式。如果應下載檔案,請添加 MIME 映射。 WindowServer2012服務器,添加角色安裝完.netframework和iis之后,運行aspx頁面 ......

    uj5u.com 2020-09-09 22:10:00 more
  • WebAPI-處理架構

    帶著問題去思考,大家好! 問題1:HTTP請求和回傳相應的HTTP回應資訊之間發生了什么? 1:首先是最底層,托管層,位于WebAPI和底層HTTP堆疊之間 2:其次是 訊息處理程式管道層,這里比如日志和快取。OWIN的參考是將訊息處理程式管道的一些功能下移到堆疊下端的OWIN中間件了。 3:控制器處理 ......

    uj5u.com 2020-09-09 22:11:13 more
  • 微信門戶開發框架-使用指導說明書

    微信門戶應用管理系統,采用基于 MVC + Bootstrap + Ajax + Enterprise Library的技術路線,界面層采用Boostrap + Metronic組合的前端框架,資料訪問層支持Oracle、SQLServer、MySQL、PostgreSQL等資料庫。框架以MVC5,... ......

    uj5u.com 2020-09-09 22:15:18 more
  • WebAPI-HTTP編程模型

    帶著問題去思考,大家好!它是什么?它包含什么?它能干什么? 訊息 HTTP編程模型的核心就是訊息抽象,表示為:HttPRequestMessage,HttpResponseMessage.用于客戶端和服務端之間交換請求和回應訊息。 HttpMethod類包含了一組靜態屬性: private stat ......

    uj5u.com 2020-09-09 22:15:23 more
  • 部署WebApi隨筆

    一、跨域 NuGet參考Microsoft.AspNet.WebApi.Cors WebApiConfig.cs中配置: // Web API 配置和服務 config.EnableCors(new EnableCorsAttribute("*", "*", "*")); 二、清除默認回傳XML格式 ......

    uj5u.com 2020-09-09 22:15:48 more
最新发布
  • C#多執行緒學習(二) 如何操縱一個執行緒

    <a href="https://www.cnblogs.com/x-zhi/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2943582/20220801082530.png" alt="" /></...

    uj5u.com 2023-04-19 09:17:20 more
  • C#多執行緒學習(二) 如何操縱一個執行緒

    C#多執行緒學習(二) 如何操縱一個執行緒 執行緒學習第一篇:C#多執行緒學習(一) 多執行緒的相關概念 下面我們就動手來創建一個執行緒,使用Thread類創建執行緒時,只需提供執行緒入口即可。(執行緒入口使程式知道該讓這個執行緒干什么事) 在C#中,執行緒入口是通過ThreadStart代理(delegate)來提供的 ......

    uj5u.com 2023-04-19 09:16:49 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    <a href="https://www.cnblogs.com/huangxincheng/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/214741/20200614104537.png" alt="" /&g...

    uj5u.com 2023-04-18 08:39:04 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    一:背景 1. 講故事 前段時間協助訓練營里的一位朋友分析了一個程式卡死的問題,回過頭來看這個案例比較經典,這篇稍微整理一下供后來者少踩坑吧。 二:WinDbg 分析 1. 為什么會卡死 因為是表單程式,理所當然就是看主執行緒此時正在做什么? 可以用 ~0s ; k 看一下便知。 0:000> k # ......

    uj5u.com 2023-04-18 08:33:10 more
  • SignalR, No Connection with that ID,IIS

    <a href="https://www.cnblogs.com/smartstar/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/u36196.jpg" alt="" /></a>...

    uj5u.com 2023-03-30 17:21:52 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:15:33 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:13:31 more
  • C#遍歷指定檔案夾中所有檔案的3種方法

    <a href="https://www.cnblogs.com/xbhp/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/957602/20230310105611.png" alt="" /></a&...

    uj5u.com 2023-03-27 14:46:55 more
  • C#/VB.NET:如何將PDF轉為PDF/A

    <a href="https://www.cnblogs.com/Carina-baby/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2859233/20220427162558.png" alt="" />...

    uj5u.com 2023-03-27 14:46:35 more
  • 武裝你的WEBAPI-OData聚合查詢

    <a href="https://www.cnblogs.com/podolski/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/616093/20140323000327.png" alt="" /><...

    uj5u.com 2023-03-27 14:46:16 more