{"id":177,"date":"2025-12-21T23:46:22","date_gmt":"2025-12-21T23:46:22","guid":{"rendered":"https:\/\/blogs.giamkichsan.com\/?p=177"},"modified":"2025-12-21T23:46:23","modified_gmt":"2025-12-21T23:46:23","slug":"iemailsender-trong-netcore","status":"publish","type":"post","link":"https:\/\/blogs.giamkichsan.com\/index.php\/2025\/12\/21\/iemailsender-trong-netcore\/","title":{"rendered":"IEmailSender trong netcore"},"content":{"rendered":"\n<p>N\u00f3 ho\u1ea1t \u0111\u1ed9ng theo c\u01a1 ch\u1ebf <strong>Dependency Injection (DI)<\/strong> v\u00e0 <strong>b\u1ea1n ph\u1ea3i t\u1ef1 c\u00e0i \u0111\u1eb7t c\u00e1ch g\u1eedi email<\/strong>.<\/p>\n\n\n\n<p>\ud83d\udc49 \u0110\u00e2y l\u00e0 <strong>interface<\/strong>, Identity <strong>ch\u1ec9 bi\u1ebft g\u1ecdi<\/strong>, kh\u00f4ng bi\u1ebft g\u1eedi b\u1eb1ng c\u00e1ch n\u00e0o.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public interface IEmailSender\n{\n    Task SendEmailAsync(string email, string subject, string htmlMessage);\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">2. Lu\u1ed3ng ho\u1ea1t \u0111\u1ed9ng t\u1ed5ng qu\u00e1t<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>Controller \/ Razor Page\n        \u2193\n_emailSender.SendEmailAsync(...)\n        \u2193\nEmailSender (class b\u1ea1n vi\u1ebft)\n        \u2193\nSMTP \/ SendGrid \/ MailKit\n        \u2193\nEmail \u0111\u01b0\u1ee3c g\u1eedi\n<\/code><\/pre>\n\n\n\n<p>\ud83d\udc49 Identity <strong>kh\u00f4ng can thi\u1ec7p<\/strong> v\u00e0o b\u01b0\u1edbc n\u00e0y.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">3. C\u00e1ch <code>_emailSender<\/code> \u0111\u01b0\u1ee3c inject (DI)<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">\u0110\u0103ng k\u00fd trong <code>Program.cs<\/code><\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>builder.Services.AddTransient&lt;IEmailSender, SmtpEmailSender&gt;();\n<\/code><\/pre>\n\n\n\n<p>\ud83d\udc49 Khi ASP.NET Core t\u1ea1o Controller:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>N\u00f3 th\u1ea5y c\u1ea7n <code>IEmailSender<\/code><\/li>\n\n\n\n<li>N\u00f3 t\u1ea1o <code>SmtpEmailSender<\/code><\/li>\n\n\n\n<li>G\u00e1n v\u00e0o <code>_emailSender<\/code><\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">4. V\u00ed d\u1ee5 implement <code>_emailSender<\/code> b\u1eb1ng SMTP<\/h2>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>V\u00ed d\u1ee5 n\u00e0y <strong>ch\u1ec9 minh h\u1ecda<\/strong>, kh\u00f4ng c\u00f3 n\u1ed9i dung nh\u1ea1y c\u1ea3m.<\/p>\n<\/blockquote>\n\n\n\n<pre class=\"wp-block-code\"><code>using System.Net;\nusing System.Net.Mail;\n\npublic class SmtpEmailSender : IEmailSender\n{\n    private readonly IConfiguration _config;\n\n    public SmtpEmailSender(IConfiguration config)\n    {\n        _config = config;\n    }\n\n    public async Task SendEmailAsync(string email, string subject, string htmlMessage)\n    {\n        var smtpClient = new SmtpClient(\n            _config&#91;\"Email:SmtpServer\"],\n            int.Parse(_config&#91;\"Email:Port\"])\n        )\n        {\n            Credentials = new NetworkCredential(\n                _config&#91;\"Email:Username\"],\n                _config&#91;\"Email:Password\"]\n            ),\n            EnableSsl = true\n        };\n\n        var mail = new MailMessage\n        {\n            From = new MailAddress(_config&#91;\"Email:From\"]),\n            Subject = subject,\n            Body = htmlMessage,\n            IsBodyHtml = true\n        };\n\n        mail.To.Add(email);\n\n        await smtpClient.SendMailAsync(mail);\n    }\n}\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">5. C\u00e1ch g\u1ecdi <code>_emailSender<\/code> trong Controller<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>await _emailSender.SendEmailAsync(\n    user.Email,\n    \"X\u00e1c nh\u1eadn email\",\n    \"Vui l\u00f2ng nh\u1ea5n link \u0111\u1ec3 x\u00e1c nh\u1eadn t\u00e0i kho\u1ea3n.\"\n);\n<\/code><\/pre>\n\n\n\n<p>\ud83d\udc49 Controller <strong>ch\u1ec9 g\u1ecdi<\/strong>, kh\u00f4ng quan t\u00e2m SMTP.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">6. <code>_emailSender<\/code> KH\u00d4NG t\u1ef1 ch\u1ea1y<\/h2>\n\n\n\n<p>\u26a0\ufe0f Quan tr\u1ecdng:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>_emailSender<\/code> <strong>kh\u00f4ng t\u1ef1 \u0111\u1ed9ng g\u1eedi email<\/strong><\/li>\n\n\n\n<li>Identity <strong>kh\u00f4ng g\u1ecdi n\u00f3<\/strong><\/li>\n\n\n\n<li>B\u1ea1n ph\u1ea3i g\u1ecdi <strong>th\u1ee7 c\u00f4ng<\/strong><\/li>\n<\/ul>\n\n\n\n<p>V\u00ed d\u1ee5:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Sau khi \u0111\u0103ng k\u00fd<\/li>\n\n\n\n<li>Khi reset password<\/li>\n\n\n\n<li>Khi resend email<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">4. Identity k\u1ebft h\u1ee3p <code>_emailSender<\/code> th\u1ebf n\u00e0o<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">B\u01b0\u1edbc 1: T\u1ea1o User<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>await _userManager.CreateAsync(user, password);\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">B\u01b0\u1edbc 2: T\u1ea1o token x\u00e1c nh\u1eadn email<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>var token = await _userManager.GenerateEmailConfirmationTokenAsync(user);\n<\/code><\/pre>\n\n\n\n<p>\ud83d\udc49 Token \u0111\u01b0\u1ee3c t\u1ea1o b\u1edfi <strong>Data Protection API<\/strong>, kh\u00f4ng ph\u1ea3i EmailStore.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">B\u01b0\u1edbc 3: T\u1ea1o link x\u00e1c nh\u1eadn<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>var link = Url.Action(\n    \"ConfirmEmail\",\n    \"Account\",\n    new { userId = user.Id, token = token },\n    Request.Scheme);\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">B\u01b0\u1edbc 4: G\u1eedi email<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>await _emailSender.SendEmailAsync(\n    user.Email,\n    \"X\u00e1c nh\u1eadn email\",\n    $\"Click v\u00e0o link: &lt;a href='{link}'&gt;Confirm&lt;\/a&gt;\");\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">B\u01b0\u1edbc 5: User click link \u2192 x\u00e1c nh\u1eadn<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>await _userManager.ConfirmEmailAsync(user, token);\n<\/code><\/pre>\n\n\n\n<p>\ud83d\udc49 L\u00fac n\u00e0y:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>UserManager<\/code> g\u1ecdi<\/li>\n\n\n\n<li><code>IUserEmailStore.SetEmailConfirmedAsync(...)<\/code><\/li>\n\n\n\n<li>L\u01b0u <code>EmailConfirmed = true<\/code> v\u00e0o DB<\/li>\n<\/ul>\n\n\n\n<p>Identity ch\u1ec9 h\u1ed7 tr\u1ee3:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>T\u1ea1o token<\/li>\n\n\n\n<li>X\u00e1c nh\u1eadn token<\/li>\n\n\n\n<li>L\u01b0u tr\u1ea1ng th\u00e1i email<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">8. V\u00ec sao Microsoft thi\u1ebft k\u1ebf nh\u01b0 v\u1eady?<\/h2>\n\n\n\n<p>\u2714 Kh\u00f4ng \u00e9p d\u00f9ng SMTP<br>\u2714 D\u00f9ng \u0111\u01b0\u1ee3c cho API \/ Mobile<br>\u2714 Thay SendGrid \u2192 SMTP kh\u00f4ng \u0111\u1ed5i code<br>\u2714 D\u1ec5 test (mock <code>IEmailSender<\/code>)<\/p>\n","protected":false},"excerpt":{"rendered":"<div class=\"mh-excerpt\"><p>N\u00f3 ho\u1ea1t \u0111\u1ed9ng theo c\u01a1 ch\u1ebf Dependency Injection (DI) v\u00e0 b\u1ea1n ph\u1ea3i t\u1ef1 c\u00e0i \u0111\u1eb7t c\u00e1ch g\u1eedi email. \ud83d\udc49 \u0110\u00e2y l\u00e0 interface, Identity ch\u1ec9 bi\u1ebft <a class=\"mh-excerpt-more\" href=\"https:\/\/blogs.giamkichsan.com\/index.php\/2025\/12\/21\/iemailsender-trong-netcore\/\" title=\"IEmailSender trong netcore\">[&#8230;]<\/a><\/p>\n<\/div>","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5],"tags":[],"class_list":["post-177","post","type-post","status-publish","format-standard","hentry","category-netcore-lap-trinh-netframework"],"_links":{"self":[{"href":"https:\/\/blogs.giamkichsan.com\/index.php\/wp-json\/wp\/v2\/posts\/177","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blogs.giamkichsan.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.giamkichsan.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.giamkichsan.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.giamkichsan.com\/index.php\/wp-json\/wp\/v2\/comments?post=177"}],"version-history":[{"count":1,"href":"https:\/\/blogs.giamkichsan.com\/index.php\/wp-json\/wp\/v2\/posts\/177\/revisions"}],"predecessor-version":[{"id":178,"href":"https:\/\/blogs.giamkichsan.com\/index.php\/wp-json\/wp\/v2\/posts\/177\/revisions\/178"}],"wp:attachment":[{"href":"https:\/\/blogs.giamkichsan.com\/index.php\/wp-json\/wp\/v2\/media?parent=177"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.giamkichsan.com\/index.php\/wp-json\/wp\/v2\/categories?post=177"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.giamkichsan.com\/index.php\/wp-json\/wp\/v2\/tags?post=177"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}