傳送郵件的小功能(.net core 版)

Bluto發表於2017-08-27

 前言:

   使用.net core 開發有一段時間了,期間從.net core 2.0 preview1 到 preview2 又到core 1.1 現在2.0正式版出來了。又把專案升級至2.0了。目前正在用2.0進行開發。期間也遇到了不少問題。在這裡進行總結一下。

    最近工作內容就是job的遷移工作,從Framework遷移到.net Core上。體會到了兩個框架的不同之處。以及使用過程中體會到的1.1和2.0的不同 以及Framework和Core的不同。首先說一下2.0和Framework的不同吧。剛開始用的preview1 用的時候感覺差異不是特別的大。有些方法2.0的實現方式和Framework實現的方式一樣。名稱空間也沒有變,方法名也沒有變。當然了有些東西還是不一樣的。或者說有些方法沒有實現。後來升級到preview2後。突然感覺那裡不對。後來仔細觀察了一下原來是類庫變了。preview1類庫是netcore2.0。preview2類庫是.netstandard2.0。總體來說升級影響不是特別的大。後來降到1.1 差別就出來了。很多方法1.1沒有實現。或者說用別的方法給實現了。最頭疼的可能就是反射的那塊了。幾乎每次都要getRuntimeXXX一次。很是煩人。2.0的語法就和Framework反射的語法是一致的。很是方便。現在升級到2.0反射那塊沒有變,依舊可以用。應該是保留了1.1的那種過度的寫法。或者說保留了那種寫法。

更新:2017-9-15 事實證明:反射那塊不相容,getRuntime***();2.0保持和Framework用法一樣,1.1那個過渡的寫法2.0已經不相容了。

內容:

  內容比較亂吧。自己想到哪裡就講到哪裡吧。

  我有這麼一個需求:將razor檢視載入之後生成的html獲取出來。我用的檢視引擎,載入執行,之後將執行的結果進行返回過來。

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Routing;

namespace CNBlogs.Job.Web.Core.Utility
{
    public interface IViewRenderService
    {
        Task<string> RenderToStringAsync(string viewName, object model);
    }
    public class ViewRenderService : IViewRenderService
    {
        private readonly IRazorViewEngine _razorViewEngine;
        private readonly ITempDataProvider _tempDataProvider;
        private readonly IServiceProvider _serviceProvider;

        public ViewRenderService(IRazorViewEngine razorViewEngine,
            ITempDataProvider tempDataProvider,
            IServiceProvider serviceProvider)
        {
            _razorViewEngine = razorViewEngine;
            _tempDataProvider = tempDataProvider;
            _serviceProvider = serviceProvider;
        }

        public async Task<string> RenderToStringAsync(string viewName, object model)
        {
            var httpContext = new DefaultHttpContext { RequestServices = _serviceProvider };
            var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());

            using (var sw = new StringWriter())
            {
                var viewResult = _razorViewEngine.FindView(actionContext, viewName, false);

                if (viewResult.View == null)
                {
                    throw new ArgumentNullException($"{viewName} does not match any available view");
                }

                var viewDictionary = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary())
                {
                    Model = model
                };

                var viewContext = new ViewContext(
                    actionContext,
                    viewResult.View,
                    viewDictionary,
                    new TempDataDictionary(actionContext.HttpContext, _tempDataProvider),
                    sw,
                    new HtmlHelperOptions()
                );

                await viewResult.View.RenderAsync(viewContext);
                return sw.ToString();
            }
        }
    }
}
IViewRenderService

更新:2017-17-27

RazorViewEngine.FindView()方法有點小問題,無法識別絕對路徑。

如果要識別絕對路徑,需要用GetView()方法。用法GetView(viewAbsolutePath:string,viewAbsolutePath:string,IsMainPage:bool),具體引數沒有詳細研究,我目前是這麼用的。

詳見github 上的issue https://github.com/aspnet/Mvc/issues/4936 

用法呢:當然我需要進行依賴注入。services.AddScoped<IViewRenderService, ViewRenderService>();對於ViewRenderService需要的依賴注入,其實mvc框架中這些已經注入過了。直接拿來用就可以。

我是這麼用的 

 var userResumeAttachmentString = await _viewRenderService.RenderToStringAsync("_ResumeForEmail", resume);

 

說明一下: 傳的第一引數是檢視名稱。或者檢視檔案所在路徑也可以,第二引數是載入檢視所需要的model。

 說一下發郵件的問題。由於Core1.1沒有system.net.mail 。傳送郵件一時間成了難題。有難難題就有人解決,於是mailkit出現了,用法呢和Framework傳送郵件的方式大致是一樣的。

        /// <summary>
        ///傳送郵件
        /// </summary>
        /// <param name="receive">接收人</param>
        /// <param name="sender">傳送人</param>
        /// <param name="subject">標題</param>
        /// <param name="body">內容</param>
        /// <param name="attachments">附件</param>
        /// <returns></returns>
        public bool SendMail(string receive, string sender, string subject, string body, byte[] attachments = null)
        {
            string displayName = _configuration["Mail:Name"];
            string from = _configuration.GetSection("Mail").GetSection("Address").Value;
            var fromMailAddress = new MailboxAddress(displayName, from);
            var toMailAddress = new MailboxAddress(receive);
            var mailMessage = new MimeMessage();
            mailMessage.From.Add(fromMailAddress);
            mailMessage.To.Add(toMailAddress);
            if (!string.IsNullOrEmpty(sender))
            {
                var replyTo = new MailboxAddress(displayName, sender);
                mailMessage.ReplyTo.Add(replyTo);
            }
            var bodyBuilder = new BodyBuilder() { HtmlBody = body };
            mailMessage.Body = bodyBuilder.ToMessageBody();
            mailMessage.Subject = subject;
            return SendMail(mailMessage);

        }
        private bool SendMail(MimeMessage mailMessage)
        {
            try
            {
                var smtpClient = new SmtpClient();
                smtpClient.Timeout = 10 * 1000;   //設定超時時間
                string host = _configuration.GetSection("Mail").GetSection("Host").Value;
                int port = int.Parse(_configuration.GetSection("Mail").GetSection("Port").Value);
                string address = _configuration.GetSection("Mail").GetSection("Address").Value;
                string password = _configuration.GetSection("Mail").GetSection("Password").Value;
                smtpClient.Connect(host, port, MailKit.Security.SecureSocketOptions.None);//連線到遠端smtp伺服器
                smtpClient.Authenticate(address, password);
                smtpClient.Send(mailMessage);//傳送郵件
                smtpClient.Disconnect(true);
                return true;

            }
            catch 
            {
                return false;
            }

        }

 

 發郵件這個問題解決了。可是還有一個需求,那就是傳送郵件附件。當時查了國外的資料,也沒有弄明白。後來自己慢慢的試出來了。具體做法就是將字串轉化為byte陣列。然後借用bodyBuilder的一個方法進行附件的新增。

            if (attachments != null)
            {
                bodyBuilder.Attachments.Add("使用者簡歷.pdf", attachments);
            }

 

這裡有個坑,那就是新增附件的時候需要將附件的檔名和格式直接指定。

到這裡基本已經完成了。我用單元測試進行測試了一番。關於在控制檯進行依賴注入的用法,我能想到就是在單元測試中使用。很方便。比如有時候需要進行資料提交的測試。我們可以在單元測試中造假資料,然後向對應的action提交資料。獲取返回結果。不用在介面的一次次的填充資料了。

 1         [Fact]
 2         public void TestSendEmail()
 3         {
 4             var builder = new ConfigurationBuilder()
 5                .SetBasePath(Directory.GetCurrentDirectory())
 6                .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
 7             var k1 = Directory.GetCurrentDirectory();
 8             builder.AddEnvironmentVariables();
 9             var Configuration = builder.Build();
10             var serviceProvider = new ServiceCollection()
11                      .AddSingleton<IConfigurationRoot>(Configuration)
12                      .AddScoped<MsgServices, MsgServices>()
13                      .BuildServiceProvider();
14             var msgService = serviceProvider.GetService<MsgServices>();
15             var k = msgService.SendMail("xiaoqu@cnblogs.com", "1483523635@qq.com", "測試", "你好我進行了測試");
16             Assert.True(k);
17         }

需要在單元測試專案的目錄中新增appsetting.json檔案。(我專案已經升到2.0了,但這些用法還是1.1的舊的用法。)

我的appsetting.json 檔案 (敏感資訊已刪除)

1   "Mail": {
2     "Name": "部落格園招聘頻道",
3     "Address": "emailAddress",
4     "Host": "xxxxxxx",
5     "Port": 25,
6     "Password": "password"
7   }

 

單元測試顯示效果:

傳送附件的測試沒有放在單元測試中,因為檢視需要載入。顯示一張截圖吧

最後還有一個問題,還麼有解決,就是HTML to PDF中文亂碼的問題。我用的是node.js 參考的是微軟官網的。英文沒有問題,中文就亂碼,亂碼原因目前正在查詢中。期間也嘗試過用jspdf 但是中文亂碼問題解決的方式並不是很理想。目前常見的解決方式是:獲取指定元素所在塊的內容,然後進行截圖,截圖解析度很低,沒有使用。也試過DinkToPdf 我看了一點原始碼,主要核心的的功能使用的wkhtmltopdf的一個類庫,他是在這個類庫上進行了一層封裝。中文亂碼問題解決了,但是不是特別的優雅。考慮到跨平臺,也沒有使用。目前沒有合適的解決方案。正在找node.js中文亂碼的原因。希望有人用過的給提點建議。很是頭疼的中文亂碼問題。

 寫了這麼多的程式碼最終在介面上顯示的功能就是一個小按鈕。

相關文章