⬆️ ⬇️

ASP.NET MVC Lesson A. Notification and Distribution

The purpose of the lesson Understand the sending of letters and confirming SMS. MailNotify, use configuration file. Mailing through the creation of a separate stream.



SmtpClient and MailNotify


When developing a website, we sooner or later encounter interaction with e-mail, whether it is user activation, a reminder or resetting a password, or creating a newsletter.

Let us define what we need for this:







Create a static class, let's call it MailSender (/Tools/Mail/MailSender.cs):

public static class MailSender { private static IConfig _config; public static IConfig Config { get { if (_config == null) { _config = (DependencyResolver.Current).GetService<IConfig>(); } return _config; } } private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); public static void SendMail(string email, string subject, string body, MailAddress mailAddress = null) { try { if (Config.EnableMail) { if (mailAddress == null) { mailAddress = new MailAddress(Config.MailSetting.SmtpReply, Config.MailSetting.SmtpUser); } MailMessage message = new MailMessage( mailAddress, new MailAddress(email)) { Subject = subject, BodyEncoding = Encoding.UTF8, Body = body, IsBodyHtml = true, SubjectEncoding = Encoding.UTF8 }; SmtpClient client = new SmtpClient { Host = Config.MailSetting.SmtpServer, Port = Config.MailSetting.SmtpPort, UseDefaultCredentials = false, EnableSsl = Config.MailSetting.EnableSsl, Credentials = new NetworkCredential(Config.MailSetting.SmtpUserName, Config.MailSetting.SmtpPassword), DeliveryMethod = SmtpDeliveryMethod.Network }; client.Send(message); } else { logger.Debug("Email : {0} {1} \t Subject: {2} {3} Body: {4}", email, Environment.NewLine, subject, Environment.NewLine, body); } } catch (Exception ex) { logger.Error("Mail send exception", ex.Message); } } } 


')

Consider more:





Consider the mailing list of letters on the template. Create a class (also static) NotifyMail (/Tools/Mail/NotifyMail.cs):

 public static class NotifyMail { private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); private static IConfig _config; public static IConfig Config { get { if (_config == null) { _config = (DependencyResolver.Current).GetService<IConfig>(); } return _config; } } public static void SendNotify(string templateName, string email, Func<string, string> subject, Func<string, string> body) { var template = Config.MailTemplates.FirstOrDefault(p => string.Compare(p.Name, templateName, true) == 0); if (template == null) { logger.Error("Can't find template (" + templateName + ")"); } else { MailSender.SendMail(email, subject.Invoke(template.Subject), body.Invoke(template.Template)); } } } 




Similarly, we get the config. When sending out, we specify for it, and then use Func <string, string> to form the subject and body of the letter.



Notify the user about the registration using the Register template from Web.config:

 <add name="Register" subject="  {0}" template="! <br/><br/>    <a href='http://{1}/User/Activate/{0}'>http://{1}/User/Activate/{0}</a>,     .<br/>-----<br/> ,  <a href='http://{1}'>{1}</a>" /> 




Note how it is necessary to screen html-tags to properly make a template. It is necessary to take into account the relationship between the template for string.Format () and the number of parameters. When registering in UserController.cs, add (/Areas/Default/Controllers/UserController.cs:Register):

 Repository.CreateUser(user); NotifyMail.SendNotify("Register", user.Email, subject => string.Format(subject, HostName), body => string.Format(body, "", HostName)); return RedirectToAction("Index"); 




HostName we added in BaseController initialization (/Controllers/BaseController.cs):

  public static string HostName = string.Empty; protected override void Initialize(System.Web.Routing.RequestContext requestContext) { if (requestContext.HttpContext.Request.Url != null) { HostName = requestContext.HttpContext.Request.Url.Authority; } … 


We register, and a letter arrives in our mail:







More difficult case


All this is good, but if we need a newsletter with a bunch of promotional offers, then this format does not suit us. Firstly, it is difficult to set a similar template in Web.config, and secondly, the number of parameters is not known. As well as usual html-templates, it would be wonderful to set a letter template in View. Well, consider the ActionMailer library ( http://nuget.org/packages/ActionMailer ):



 PM> Install-Package ActionMailer Successfully installed 'ActionMailer 0.7.4'. Successfully added 'ActionMailer 0.7.4' to LessonProject.Model. 




We inherit MailController from MailerBase:

 public class MailController : MailerBase { public EmailResult Subscription(string message, string email) { To.Add(email); Subject = ""; MessageEncoding = Encoding.UTF8; return Email("Subscription", message); } } 




Add the Subscription.html.cshtml View (/Areas/Default/Views/Mail/Subscription.html.cshtml):

 @model string @{ Layout = null; } <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> </head> <body> <div> <h1>@Model</h1> </div> </body> </html> 




Add to the Web.config configuration for working with mail (Web.config):

 <system.net> <mailSettings> <smtp deliveryMethod="Network" from="lxndrpetrov@gmail.com"> <network host="smtp.gmail.com" port="587" userName="lxndrpetrov" password="******" enableSsl="true" /> </smtp> </mailSettings> </system.net> 




And we create a test method in UserController.cs (/Areas/Default/Controllers/UserController.cs):

 [Authorize] public ActionResult SubscriptionTest() { var mailController = new MailController(); var email = mailController.Subscription(", !", CurrentUser.Email); email.Deliver(); return Content("OK"); } 




Run:



localhost / User / SubscriptionTest - and get a letter in the mail.



Consider an example of getting the text of a letter in a string This will require a StreamReader (/Areas/Default/Controllers/UserController.cs):

 [Authorize] public ActionResult SubscriptionShow() { var mailController = new MailController(); var email = mailController.Subscription(", !", CurrentUser.Email); using (var reader = new StreamReader(email.Mail.AlternateViews[0].ContentStream)) { var content = reader.ReadToEnd(); return Content(content); } return null; } 




Content already has a generated page. Run:

localhost / User / SubscriptionShow



Smsnotify




In this chapter, we will discuss the interaction using SMS, and not just mail. But there is a nuance - access to the mailing list is provided by individual services, and here we consider only the basic principles of the module writing for working with SMS providers using the example of working with unisender.ru.

Create a class of settings like MailSetting (/Global/Config/SmsSetting.cs):

 public class SmsSetting : ConfigurationSection { [ConfigurationProperty("apiKey", IsRequired = true)] public string APIKey { get { return this["apiKey"] as string; } set { this["apiKey"] = value; } } [ConfigurationProperty("sender", IsRequired = true)] public string Sender { get { return this["sender"] as string; } set { this["sender"] = value; } } [ConfigurationProperty("templateUri", IsRequired = true)] public string TemplateUri { get { return this["templateUri"] as string; } set { this["templateUri"] = value; } } } 
Set in Web.Config (Web.config):

 <configSections> … <section name="smsConfig" type="LessonProject.Global.Config.SmsSetting, LessonProject" /> </configSections> … <smsConfig apiKey="*******" sender="Daddy" templateUri="http://api.unisender.com/ru/api/sendSms" /> </configuration> 




Create the SmsSender class (/Tools/Sms/SmsSender.cs):

 public static class SmsSender { private static IConfig _config; public static IConfig Config { get { if (_config == null) { _config = (DependencyResolver.Current).GetService<IConfig>(); } return _config; } } private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); public static string SendSms(string phone, string text) { if (!string.IsNullOrWhiteSpace(Config.SmsSetting.APIKey)) { return GetRequest(phone, Config.SmsSetting.Sender, text); } else { logger.Debug("Sms \t Phone: {0} Body: {1}", phone, text); return "Success"; } } private static string GetRequest(string phone, string sender, string text) { try { HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(Config.SmsSetting.TemplateUri); /// important, otherwise the service can't desirialse your request properly webRequest.ContentType = "application/x-www-form-urlencoded"; webRequest.Method = "POST"; webRequest.KeepAlive = false; webRequest.PreAuthenticate = false; string postData = "format=json&api_key=" + Config.SmsSetting.APIKey + "&phone=" + phone + "&sender=" + sender + "&text=" + HttpUtility.UrlEncode(text); var ascii = new ASCIIEncoding(); byte[] byteArray = ascii.GetBytes(postData); webRequest.ContentLength = byteArray.Length; Stream dataStream = webRequest.GetRequestStream(); dataStream.Write(byteArray, 0, byteArray.Length); dataStream.Close(); WebResponse webResponse = webRequest.GetResponse(); Stream responceStream = webResponse.GetResponseStream(); Encoding enc = System.Text.Encoding.UTF8; StreamReader loResponseStream = new StreamReader(webResponse.GetResponseStream(), enc); string Response = loResponseStream.ReadToEnd(); return Response; } catch (Exception ex) { logger.ErrorException("   SMS", ex); return "   SMS"; } } } 




The result comes like:

 {"result":{"currency":"RUB","price":"0.49","sms_id":"1316886153.2_79859667475"}} 


It can be disassembled and analyzed.

In the next lesson we will look at how to work with json.



Separate stream


If we send email to a lot of people, the processing can take a lot of time. For this, I use the following principle:





A separate thread is launched in Application_Start. The timer is set to repeat after 1 minute:

 public class MvcApplication : System.Web.HttpApplication { private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); private Thread mailThread { get; set; } protected void Application_Start() { var adminArea = new AdminAreaRegistration(); var adminAreaContext = new AreaRegistrationContext(adminArea.AreaName, RouteTable.Routes); adminArea.RegisterArea(adminAreaContext); var defaultArea = new DefaultAreaRegistration(); var defaultAreaContext = new AreaRegistrationContext(defaultArea.AreaName, RouteTable.Routes); defaultArea.RegisterArea(defaultAreaContext); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); mailThread = new Thread(new ThreadStart(ThreadFunc)); mailThread.Start(); } private static void ThreadFunc() { while (true) { try { var mailThread = new Thread(new ThreadStart(MailThread)); mailThread.Start(); logger.Info("Wait for end mail thread"); mailThread.Join(); logger.Info("Sleep 60 seconds"); } catch (Exception ex) { logger.ErrorException("Thread period error", ex); } Thread.Sleep(60000); } } private static void MailThread() { var repository = DependencyResolver.Current.GetService<IRepository>(); while (MailProcessor.SendNextMail(repository)) { } } } 




Consider the MailProcessor class (but we will not create it):

 public class MailProcessor { public static bool SendNextMail(IRepository repository) { var mail = repository.PopMailQueue(); if (mail != null) { MailSender.SendMail(mail.Email, mail.Subject, mail.Body); return true; } return false; } } 




MailProcessor.SendNextMail(repository) - sends the next letter; if there are no letters, it returns false

The MainThread thread waits for MailThread and takes a smoke break for one minute. And further. If there are no new letters in the database, then we smoke next minute.



All sources are located at https://bitbucket.org/chernikov/lessons

Source: https://habr.com/ru/post/176075/



All Articles