ASP.NET WebApi E-Mail Service (.NET 6.0+EntityFrameworkCore 6)

Article directory

  • Target
  • Create WebApi
    • 1. Create a database
      • 1. Data table composition
      • 2. SQL script
    • 2. Link EFCore and perform dependency injection
    • 3. Code explanation
      • 1. Rewrite the controller base class
      • 2. Create the controller
      • 3. Return value Models class
      • 4. Config
        • (1).Config.cs
        • (2). Model class of Config
        • (3).platform.json
      • 5. External WebApi call
        • (1).ExternWebApiBase.cs
        • (2).Address class
      • 6.Mail Platform
        • (1).MailBuilder
        • (2).Mail Platform
      • 7. Global reference (NET6.0)
    • 3. Packages that need to be imported
  • Project file download
  • EMail-API service

Goal

Send a letter to the request mailbox through SMTP, the sending mailbox is created by the API creator, and the permissions are set

Create WebApi

1. Create a database

1. Data table composition

TB_RequestLog

column name type feature remark
Index int Primary key automatically grows non-null Number
Region nchar(32) not empty controller name
Action nchar(32) not empty function name
Arguments varchar (max) not empty parameter set
IP nchar(16) Requester IP
Address nchar(32) Requester location
Identity nchar(11) Requester authentication
LogTime datetime not null log time

TB_Mail

column name type feature remark
Guid nchar(16) Non-empty primary key Unique UID
Address nchar(32) Not empty Current mailbox
Title nvarchar(max) not empty Title
Body nvarchar (max) not empty body
Allocated nchar(32) Not Null Assigned Subject Number
SendTime datetimt Not Null Time of occurrence

2.SQL script

USE [db_spaceserver]
go
SET ANSI_NULLS ON
go
SET QUOTED_IDENTIFIER ON
go
CREATE TABLE [dbo].[TB_Mail](
[Guid] [nchar](16) NOT NULL,
[Address] [nchar](32) NOT NULL,
[Title] [nvarchar](max) NOT NULL,
[Body] [nvarchar](max) NOT NULL,
[Allocated] [nchar](32) NOT NULL,
[SendTime] [datetime] NOT NULL,
 CONSTRAINT [PK_TB_Mail] PRIMARY KEY CLUSTERED
(
[Guid] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
go
SET ANSI_NULLS ON
go
SET QUOTED_IDENTIFIER ON
go
CREATE TABLE [dbo].[TB_RequestLog](
[Index] [int] IDENTITY(1,1) NOT NULL,
[Region] [nchar](32) NOT NULL,
[Action] [nchar](32) NOT NULL,
[Arguments] [varchar](max) NULL,
[IP][nchar](16) NULL,
[Address] [nchar](32) NULL,
[Identity] [nchar](11) NULL,
[LogTime] [datetime] NOT NULL,
 CONSTRAINT [PK_TB_RequestLog] PRIMARY KEY CLUSTERED
(
[Index] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
go

2. Link EFCore and perform dependency injection

The process refers to the previous steps to connect EFCore
EFCore’s link generation context and model
Dependency injection after completion
(The author uses the environment of .NET6.0, the configuration items are in Program.cs, if you use the previous version, the configuration items are in Startup.cs)

builder.Services.AddSqlServer<Db_SpaceserverContext>(builder.Configuration.GetConnectionString("DB_SpaceServer"));

Remember to configure the link string in advance in appsettings.json
Then make the following configuration

builder.Services.AddControllersWithViews().AddJsonOptions(options =>
{<!-- -->
    options.JsonSerializerOptions.Encoder = System.Text.Encodings.Web.JavaScriptEncoder.Create(System.Text.Unicode.UnicodeRanges.All);
});
builder.Services.AddRouting(options => options.LowercaseUrls = true);

The first configuration above can make Json serialization without encoding errors, and the second one can make the URL all lowercase

3. Code explanation

1. Override the controller base class

First of all, we inherit a controller base class to replace ControllerBase, because we need to use many specific methods, and the base class can provide a lot of convenience for programming

public class ControllerBaseEx : ControllerBase, IDisposable
    {<!-- -->
        protected Db_SpaceserverContext DBContext {<!-- --> set; get; }
        protected string RequestIP
        {<!-- -->
            get
            {<!-- -->
                var ip = HttpContext.Request.Headers["X-Forwarded-For"].FirstOrDefault();
                if (string. IsNullOrEmpty(ip))
                {<!-- -->
                    ip = HttpContext.Connection.RemoteIpAddress.ToString();
                }
                return ip;
            }
            
        }
        protected Config Config {<!-- --> get; } = new Config();
        protected List<Task> WaitTasks = new List<Task>();
        protected ControllerBaseEx(Db_SpaceserverContext context)
        {<!-- -->
            DBContext = context;
        }
        void IDisposable. Dispose()
        {<!-- -->
            //Wait for all tasks to be executed
            foreach (var c in WaitTasks) c. Wait();
        }
        protected void Log(object args, string identity = "")
        {<!-- -->
            Address address = new();
            string region = (ControllerContext.RouteData.Values["controller"]  "NULL").ToString();
            string action = (ControllerContext.RouteData.Values["action"]  "NULL").ToString();
            var log = new TbRequestLog()
            {<!-- -->
                Action = action,
                Region = region,
                Ip = RequestIP.Length < 5 ? "" : RequestIP,
                Address = RequestIP.Length < 5 ? "" : address.GetAddress(RequestIP),
                LogTime = DateTime. Now,
                Identity = identity,
                Arguments = JsonConvert. SerializeObject(args)
            };
            DBContext.TbRequestLog.Add(log);
            DBContext. SaveChanges();
            
        }
        protected void Log(string args, string identity = "")
        {<!-- -->
            Address address = new();
            string region = (ControllerContext.RouteData.Values["controller"]  "NULL").ToString();
            string action = (ControllerContext.RouteData.Values["action"]  "NULL").ToString();
            var log = new TbRequestLog()
            {<!-- -->
                Action = action,
                Region = region,
                Ip = RequestIP.Length < 5 ? "" : RequestIP,
                Address = RequestIP.Length < 5 ? "" : address.GetAddress(RequestIP),
                LogTime = DateTime. Now,
                Identity = identity,
                Arguments = args
            };
            DBContext.TbRequestLog.Add(log);
            DBContext. SaveChanges();
        }
        protected string JsonString(object args) => JsonConvert.SerializeObject(args);
        protected ActionResult Json(object args) => new JsonResult(args);
        protected BadRequestObjectResult Error(string result) => BadRequest(new {<!-- --> code = 400, result = result });
        protected string Random(int length, string dic = "")
        {<!-- -->
            dic = dic == "" ? "qwertyuiopasdfghjklzxcvbnm0123456789QWERTYUIOPASDFGHJKLZXCVBNM" : dic == "-1" ? "0123456789" : dic;
            string data = "";Random rand = new Random();
            for(int i= 0; i < length; i ++ )
            {<!-- -->
                data + = dic[rand. Next(dic. Length)];
            }
            return data;
        }

      
    }

field value

DBContext EFCore context object, initialized with injection object after inheritance
RequestIP Request IP, actually get the X-Forwarded-For field in the header, if the local request, this value is ‘:::’
Config is used to get other configuration files
WaitTasks is used to save all asynchronous tasks

function

protected ControllerBaseEx(Db_SpaceserverContext context)

parameter name type description
context Db_SpaceserverContext used to initialize DBContext properties

initialization function

void IDisposable. Dispose()

Used to wait for all task objects when the object is released

protected void Log(object args, string identity = “”)

parameter name type description
args object parameter object object
identity string Requester Authentication

protected void Log(string args, string identity = “”)

parameter name type description
args string Serialized parameter set
identity string Requester Authentication

Used to record LOG information

protected string JsonString(object args)

parameter name type description
args object Data to be converted

for serializing objects

protected ActionResult Json(object args)

parameter name type description
args object returned json object

Used to return JsonResult

protected BadRequestObjectResult Error(string result)

parameter name type description
result string Error reason

Used to return BadRequest

protected string Random(int length, string dic = “”)

parameter name type description
length int generated length
dic string generated Dictionary

It is used to generate a random sequence of a specified length. If dic is empty, it will be selected from the full dictionary. If it is -1, a digital set will be generated, and the others will be specified sets

2. Create a controller

ControllerNameMailCodeController.cs

 [Route("api/[controller]/[action]")]
    [ApiController]
    public class MailCodeController : ControllerBaseEx
    {<!-- -->
        private ILogger<MailCodeController> _logger;
        private MailPlatformSetting_setting;
        public MailCodeController(Db_SpaceserverContext context, ILogger<MailCodeController> logger) : base(context)
        {<!-- -->
            _logger = logger;
            _setting = Config.GetSetting<MailPlatformSetting>("platform.json");
        }

        [HttpGet]
        public async Task<IActionResult> SendCode(string code,string office,string address)
        {<!-- -->
            try
            {<!-- -->
                //write to log
                object log = new
                {<!-- -->
                    code,
                    office,
                    address
                };
                Log(JsonString(log), "SYSTEM");

                //judgment information
                if (code.Length > 10) throw new Exception("exceeds the maximum content length, 10 characters");
                if (office.Length > 16) throw new Exception("exceeds the maximum signature length, 16 bits");

                //Check if the email address matches
                Regex regex = new(@"^[A-Za-z0-9\\一-\\龥] + @[a-zA-Z0-9_-] + (\.[a-zA-Z0-9_ -] + ) + $");
                if (!regex.IsMatch(address)) throw new Exception("Email address does not match");

                //Generate GUID and time
                string guid = Random(16);
                DateTime time = DateTime. Now;

                //Create a platform
                MailPlatform platform = new(_setting. Platform. ToArray());

                //Create information
                MailBuilder builder = new MailBuilder();
                builder.Address.Add(address);
                builder.Subject = $"Verification code from {office}";
                builder.Body = $"Your verification code is: {code}, the sender is: {office}, and the sending time is: {time}";

                //send email
                var task = platform.SendMailAsync(builder.Build());
                WaitTasks. Add(task);

                //write record
                TbMail mail = new TbMail()
                {<!-- -->
                    Address = address,
                    Allocated = platform.LastAllocatedMail,
                    Body = builder.Body,
                    Title = builder. Subject,
                    Guid = guid,
                    SendTime = time,
                };

                //Save to database
                DBContext.TbMail.Add(mail);
                DBContext. SaveChanges();
                

                //Generate return set
                MailCodeModel model = new()
                {<!-- -->
                    Address = address,
                    Content = $"-H {builder.Subject} -B {builder.Body}",
                    Guid = guid,
                    DateTime = time,
                };
                return Json(model);
            }
            catch(Exception ex)
            {<!-- -->
                return Error(ex. Message);
            }
        }
    }

field value

_logger WebApi comes with log object
_setting MailPlatform setting object

function

public MailCodeController(Db_SpaceserverContext context, ILogger logger)

parameter name type description
context Db_SpaceserverContext Injection context object
logger ILogger Inject log object

initialization function

[HttpGet]
public async Task SendCode(string code, string office, string address)

parameter name type description
code string Verification code
office string company name
address string target mailbox

Verification code sending function

3. Return value Models class

public class MailCodeModel
    {<!-- -->
        public string Guid {<!-- --> set; get; }
        public string Address {<!-- --> set; get; }
        public string Content {<!-- --> set; get; }
        public DateTime DateTime {<!-- --> set; get; }

    }

field value

Guid The unique Uid of the sending mark
Address target mailbox
Content content
DateTime Sending time

4.Config

(1).Config.cs

public class Config
    {<!-- -->
        public T GetSetting<T>(string path) where T:class
        {<!-- -->
            StreamReader reader = new(path);
            var data = reader. ReadToEnd();
            reader. Close();
            return JsonConvert. DeserializeObject<T>(data);
        }
       
    }

function

public T GetSetting(string path) where T:class

parameter name type description
path string json file address
T T Profile Model

Get the configuration file and turn it into a .NET object

(2).Model class of Config

public class MailPlatformSetting
    {<!-- -->
        public List<EMailSetting> Platform {<!-- --> get; set; }
        public int Count => Platform. Count;
    }
    public class EMailSetting
    {<!-- -->
        public string EMail {<!-- --> get; set; }
        public string Key {<!-- --> get; set; }
    }

(3).platform.json

{<!-- -->
  "Platform": [
    {<!-- -->
      "EMail": "Mailbox",
      "Key": "Authorization Code"
    },
    {<!-- -->
      "EMail": "Mailbox",
      "Key": "Authorization Code"
    },
    {<!-- -->
      "EMail": "Mailbox",
      "Key": "Authorization Code"
    }
  ]
}

5. External WebApi call

(1).ExternWebApiBase.cs

public class ExternWebApiBase
    {<!-- -->
        protected string RequestByGet(string baseurl, params string[] args)
        {<!-- -->
            string url = baseurl + "?";
            url + = string.Join(' &', args);
            HttpClient client = new HttpClient();
            HttpRequestMessage request = new HttpRequestMessage(HttpMethod. Get, url);
            HttpResponseMessage response = client.Send(request);
            return response.Content.ReadAsStringAsync().Result;
        }
        protected T RequestByGet<T>(string baseurl, params string[] args)
        {<!-- -->
            string url = baseurl + "?";
            url + = string.Join(' &', args);
            HttpClient client = new HttpClient();
            HttpRequestMessage request = new HttpRequestMessage(HttpMethod. Get, url);
            HttpResponseMessage response = client.Send(request);
            return JsonConvert. DeserializeObject<T>(response. Content. ReadAsStringAsync(). Result);
        }
        protected string RequestByPost(string baseurl, object args)
        {<!-- -->
            string url = baseurl;
            HttpClient client = new HttpClient();
            HttpRequestMessage request = new HttpRequestMessage(HttpMethod. Post, url);
            //Piece together json
            JsonContent content = JsonContent. Create(args);
            request.Content = content;
            HttpResponseMessage response = client.Send(request);
            return response.Content.ReadAsStringAsync().Result;
        }
        protected T RequestByPost<T>(string baseurl, object args)
        {<!-- -->
            string url = baseurl;
            HttpClient client = new HttpClient();
            HttpRequestMessage request = new HttpRequestMessage(HttpMethod. Post, url);
            //Piece together json
            JsonContent content = JsonContent. Create(args);
            request.Content = content;
            HttpResponseMessage response = client.Send(request);
            return JsonConvert. DeserializeObject<T>(response. Content. ReadAsStringAsync(). Result);
        }
    }

(2).Address class

 public class Address: ExternWebApiBase
    {<!-- -->
        public string GetAddress(string ip)
        {<!-- -->
            string url = "http://whois.pconline.com.cn/ip.jsp";
            string data = RequestByGet(url,
                $"ip={ip}",
                $"level=3"
                );
            return data;
        }
    }

6. MailPlatform

Implementation of MailPlatform
But it is different from the above blog, we have to change it a little bit

(1).MailBuilder

public class MailBuilder
    {<!-- -->
        public MailBuilder() {<!-- --> }
        public List<string>? Address {<!-- --> set; get; } = new List<string>();
        public string? Body {<!-- --> set; get; } = string.Empty;
        public Encoding? Encoding {<!-- --> set; get; } = Encoding.UTF8;
        public bool? IsHtml {<!-- --> set; get; } = true;
        public string? Subject {<!-- --> set; get; } = string.Empty;
        public List<string>? Attachments {<!-- --> set; get; } = new List<string>();
        public MailMessage Build()
        {<!-- -->
            var message = new MailMessage();
            if (Address?.Count == 0) throw new Exception("The sender cannot be empty");
            foreach (var c in Address)
            {<!-- -->
                message.To.Add(c);
            }
            message.Subject = Subject;
            message.Body = Body;
            message.IsBodyHtml = IsHtml  true;
            message.BodyEncoding = Encoding;
            message.Priority = MailPriority.Normal;
            message.SubjectEncoding = Encoding;
            foreach(var c in Attachments)
            {<!-- -->
                if (!File.Exists(c)) throw new Exception("The attachment file does not exist");
                var data = new Attachment(c, MediaTypeNames.Application.Octet);//instantiate the attachment
                data.ContentDisposition.CreationDate = File.GetCreationTime(c);
                data.ContentDisposition.ModificationDate = File.GetLastAccessTime(c);
                data.ContentDisposition.ReadDate = DateTime.Now;
                message.Attachments.Add(data);//Add to the attachment
            }
            return message;
        }
        public Task<MailMessage> BuildAsync()
        {<!-- -->
            Task<MailMessage> message = new Task<MailMessage>(() =>
            {<!-- -->
                var message = new MailMessage();
                if (Address?.Count == 0) throw new Exception("The sender cannot be empty");
                foreach (var c in Address)
                {<!-- -->
                    message.To.Add(c);
                }
                message.Subject = Subject;
                message.Body = Body;
                message.IsBodyHtml = IsHtml  true;
                message.BodyEncoding = Encoding;
                message.Priority = MailPriority.Normal;
                message.SubjectEncoding = Encoding;
                foreach (var c in Attachments)
                {<!-- -->
                    if (!File.Exists(c)) throw new Exception("The attachment file does not exist");
                    var data = new Attachment(c, MediaTypeNames.Application.Octet);//instantiate the attachment
                    data.ContentDisposition.CreationDate = File.GetCreationTime(c);
                    data.ContentDisposition.ModificationDate = File.GetLastAccessTime(c);
                    data.ContentDisposition.ReadDate = DateTime.Now;
                    message.Attachments.Add(data);//Add to the attachment
                }
                return message;
            });
            message. Start();
            return message;
        }
        
    }

(2).MailPlatform

public class MailPlatform
    {<!-- -->
        private string _last;
        public string LastAllocatedMail {<!-- --> get => _last; }
        private List<(MailAddress, string)>? _mailinformation;
        public MailPlatform(params (MailAddress, string)[]? pairs)
        {<!-- -->
            _mailinformation = pairs.ToList()  throw new Exception("Wrong initialization");
        }
        public MailPlatform(params EMailSetting[]? pairs)
        {<!-- -->
            _mailinformation = new List<(MailAddress, string)>();
            foreach(var c in pairs)
            {<!-- -->
                (MailAddress, string) data = (new MailAddress(c.EMail), c.Key);
                _mailinformation. Add(data);
            }
            if (_mailinformation.Count == 0) throw new Exception("Wrong initialization");

        }
        public void SendMail(MailMessage message)
        {<!-- -->
            var random = new Random();
            var info = _mailinformation?.OrderBy(s => Guid.NewGuid()).FirstOrDefault()  throw new Exception("No settings have been initialized");
            _last = info.Item1.Address;
            message.From = info.Item1;
            var client = new SmtpClient()
            {<!-- -->
                EnableSsl = true,
                UseDefaultCredentials = false,
                Credentials = new System.Net.NetworkCredential(info.Item1.Address, info.Item2),
                DeliveryMethod = SmtpDeliveryMethod. Network,
                Host = "smtp." + info.Item1.Host
            };
            client.Send(message);
        }
        public void SendMailAsync(MailMessage message, SendCompletedEventHandler CompletedMethod, object args)
        {<!-- -->
            var random = new Random();
            var info = _mailinformation?.OrderBy(s => Guid.NewGuid()).FirstOrDefault()  throw new Exception("No settings have been initialized");
            _last = info.Item1.Address;
            message.From = info.Item1;
            var client = new SmtpClient()
            {<!-- -->
                EnableSsl = true,
                UseDefaultCredentials = false,
                Credentials = new System.Net.NetworkCredential(info.Item1.Address, info.Item2),
                DeliveryMethod = SmtpDeliveryMethod. Network,
                Host = "smtp." + info.Item1.Host
            };
            client.SendCompleted += new SendCompletedEventHandler(CompletedMethod);
            client.SendAsync(message, args);
        }
        public Task SendMailAsync(MailMessage message)
        {<!-- -->
            var random = new Random();
            var info = _mailinformation?.OrderBy(s => Guid.NewGuid()).FirstOrDefault()  throw new Exception("No settings have been initialized");
            _last = info.Item1.Address;
            Task task = new Task(() =>
              {<!-- -->
                  message.From = info.Item1;
                  var client = new SmtpClient()
                  {<!-- -->
                      EnableSsl = true,
                      UseDefaultCredentials = false,
                      Credentials = new System.Net.NetworkCredential(info.Item1.Address, info.Item2),
                      DeliveryMethod = SmtpDeliveryMethod. Network,
                      Host = "smtp." + info.Item1.Host
                  };
                  client.Send(message);
              });
            task. Start();
            return task;
        }
    }

Because to get the Allocate information, you need to call the LastAllocatedMail property

7. Global reference (NET6.0)

GlobalUsing.cs

//SpaceServer function
global using SpaceServer. EFCore;
global using SpaceServer. EFCore. Models;
global using SpaceServer. Programs. Mail;
global using SpaceServer. Programs. Address;
global using SpaceServer. Programs. Config;
global using SpaceServer.Programs.Config.Models;
global using SpaceServer.Models;

//Mincrosoft function
global using Microsoft.AspNetCore.Mvc;
global using Microsoft.AspNetCore.Http;

//System function
global using System.Net;
global using System.IO;
global using System. Text;
global using System.Text.RegularExpressions;

//external function
global using Newtonsoft.Json;
global using Newtonsoft.Json.Linq;

3. Packages that need to be imported

Project file download

Project file download

EMail-API service

This API can only be used for verification code testing, not for other aspects, all problems have nothing to do with the author
Request address: api.spaceserver.cloud/api/mailcode/sendcode?
Request method: GET
parameter set

return set

parameter name type description
code string Verification code sent
office string Company name
address string Target email address
parameter name type description
Guid string Unique Identifier
Address string Target Email Address
Content string Content
DateTime DateTime Send time

error set

parameter name type description
code int error code
result string error Reason