PageRenderTime 58ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/ThinkEmailFomatter/Controllers/TemplateController.cs

https://bitbucket.org/nicdao/frg-think-emailformatter
C# | 542 lines | 391 code | 66 blank | 85 comment | 76 complexity | f355bb7da45963b20d69c4f98cbb46c3 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Data;
  4. using System.Data.Entity;
  5. using System.Linq;
  6. using System.Web;
  7. using System.Web.Mvc;
  8. using ThinkEmailFormatter.Models;
  9. using ThinkEmailFormatter.Models.Extensions;
  10. using System.Xml.Linq;
  11. using System.IO;
  12. using System.Xml;
  13. using System.Web.UI;
  14. using System.Text;
  15. using ThinkEmailFormatter.Utilities;
  16. using Microsoft.Practices.EnterpriseLibrary.Common;
  17. using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
  18. using Microsoft.Practices.EnterpriseLibrary.Logging;
  19. using System.Net;
  20. using Newtonsoft.Json.Linq;
  21. using ThinkEmailFormatter.ThinkOdata;
  22. namespace ThinkEmailFormatter.Controllers
  23. {
  24. public class TemplateController : BaseController
  25. {
  26. #region [ CONSTRUCTOR ]
  27. public TemplateController(IControllerAggregateService controllerAggregateService)
  28. : base(controllerAggregateService)
  29. {
  30. }
  31. #endregion
  32. #region [ ACTIONS ]
  33. /// <summary>
  34. /// Return a view with all template(.cshtml) files contained
  35. /// inside "Views/Template/Templates" folder
  36. /// </summary>
  37. /// <returns></returns>
  38. public ViewResult Index()
  39. {
  40. try
  41. {
  42. // get all views frmo the file system. Each view corresponds to a template
  43. var templates = _aggSvc.ViewHelper.GetTemplates();
  44. // get all template settings from the DB
  45. List<TemplateSetting> allTemplatesettings = _aggSvc.ThinkHelper.GetAllTemplateSettings();
  46. List<Template> templatesWithDescr = new List<Template>();
  47. foreach (var template in templates.ToList())
  48. {
  49. Template tmplt = new Template();
  50. tmplt.Name = template.Name;
  51. string subject = allTemplatesettings.Where(s => string.Compare(s.Name, template.Name, true) == 0).Select(t => t.Subject).FirstOrDefault();
  52. tmplt.Description = string.IsNullOrEmpty(subject) ? "(No Subject)" : subject;
  53. templatesWithDescr.Add(tmplt);
  54. }
  55. TemplateContext tc = new TemplateContext()
  56. {
  57. Templates = templatesWithDescr,
  58. EmailContext = new EmailContext(),
  59. };
  60. return View(tc);
  61. }
  62. catch (Exception ie)
  63. {
  64. _aggSvc.Log.Exception(ie);
  65. return View("Error");
  66. }
  67. }
  68. /// <summary>
  69. /// Preview the selected template associated with the selected dummy request
  70. /// Allow power user to visualize the email template in their browser
  71. /// </summary>
  72. /// <param name="dummyData"></param>
  73. /// <returns></returns>
  74. public ViewResult Preview(string template)
  75. {
  76. try
  77. {
  78. string templateName = string.Format("Templates/{0}", template);
  79. bool viewExists = this.ControllerContext.ViewExist(templateName);
  80. if (viewExists)
  81. {
  82. ViewBag.EmailEditorMode = false; // Enable all javascripts and display the email editor at the top
  83. ViewBag.TemplateName = template;
  84. ThinkTransactionData dummyDataObject = GetDummyThinkObject("DummyEvent.xml");
  85. return View(templateName, new EmailContext() { ThinkData = dummyDataObject, AttachmentsRootUrl = _aggSvc.EmailContext.AttachmentsRootUrl });
  86. }
  87. else
  88. return View("Unknown");
  89. }
  90. catch (Exception ie)
  91. {
  92. _aggSvc.Log.Exception(ie);
  93. return View("Error");
  94. }
  95. }
  96. /// <summary>
  97. /// Redirect to the "Settings" view
  98. /// where all email template's setting can be maintained
  99. /// </summary>
  100. /// <param name="emailTemplate"></param>
  101. /// <returns></returns>
  102. public ViewResult Settings(string emailTemplate)
  103. {
  104. var templateSettings = _aggSvc.ThinkHelper.GetTemplateSettings(emailTemplate);
  105. var eventTypes = _aggSvc.ThinkHelper.GetEventTypeList();
  106. var orderClasses = _aggSvc.ThinkHelper.GetOrderClassList();
  107. ViewData["EventType.Id"] = eventTypes;
  108. if (templateSettings == null)
  109. {
  110. templateSettings = new TemplateSetting()
  111. {
  112. Name = emailTemplate,
  113. DefaultEnabled = true,
  114. };
  115. templateSettings.InitiateRequiredComplexTypes();
  116. ViewData["OrderClass.Id"] = new SelectList(orderClasses, "Id", "Name", string.Empty);
  117. ViewBag.EventTypeSelected = 0;
  118. ViewBag.ButtonName = "Create New Settings";
  119. }
  120. else
  121. {
  122. ViewData["OrderClass.Id"] = new SelectList(orderClasses, "Id", "Name", templateSettings.OrderClass.Id);
  123. ViewBag.EventTypeSelected = templateSettings.EventType.Id;
  124. ViewBag.ButtonName = "Update Settings";
  125. }
  126. return View(templateSettings);
  127. }
  128. /// <summary>
  129. /// Submit the settings to the DB
  130. /// </summary>
  131. /// <param name="emailTemplate"></param>
  132. /// <returns></returns>
  133. [HttpPost]
  134. public ActionResult Settings(TemplateSetting templateSettings)
  135. {
  136. try
  137. {
  138. templateSettings.DefaultEnabled = false;
  139. if (string.IsNullOrEmpty(templateSettings.FromEmail))
  140. templateSettings.FromEmail = string.Empty;
  141. // if flag is false, it means that the selected event type does not
  142. // support filter options. Therefore, all filters should be deleted
  143. // if some were defined in a previous settings
  144. if (!templateSettings.EventType.Flag)
  145. {
  146. if (templateSettings.Filters != null && templateSettings.Filters.Any())
  147. templateSettings.Filters.ForEach(f => f.Description = "delete");
  148. }
  149. _aggSvc.ThinkHelper.CommitSettings(templateSettings);
  150. return RedirectToAction("Settings", new { emailTemplate = templateSettings.Name });
  151. }
  152. catch (Exception ie)
  153. {
  154. _aggSvc.Log.Exception(ie, "'Template' controller, 'Settings' action on Http-POST");
  155. return View("Error");
  156. }
  157. }
  158. /// <summary>
  159. /// Format the email so an HTML response is generated after the THINK email request
  160. /// The URL pattern that needs to be used is http://<domain>/afr/template/{templateName}
  161. /// This has been defined within the global.asax file under the "Main" route
  162. /// </summary>
  163. /// <param name="templateName"></param>
  164. /// <returns></returns>
  165. public ActionResult Format(string templateName)
  166. {
  167. // By default, an http status code 204 is sent back to THINK meaning
  168. // all went well and an email has been successfully sent. This response
  169. // lets THINK know that it can remove the event from the "event_queue" table
  170. HttpStatusCodeResult httpStatusCode = new HttpStatusCodeResult(204);
  171. try
  172. {
  173. if (Request.HttpMethod.ToUpper().CompareTo("POST") != 0 || Request.ContentLength == 0)
  174. {
  175. throw new InvalidDataException();
  176. }
  177. else
  178. {
  179. // Convert request to Business Object
  180. ThinkStreamToObject<ThinkTransactionData> thinkToObject = new ThinkStreamToObject<ThinkTransactionData>(Request.InputStream);
  181. ThinkTransactionData dataObject = thinkToObject.Convert();
  182. var matchedTemplate = MatchTemplateSettingsToRequest(dataObject);
  183. if (matchedTemplate != null)
  184. {
  185. string templateNamePath = string.Format("Templates/{0}", matchedTemplate.Name);
  186. bool viewExists = this.ControllerContext.ViewExist(templateNamePath);
  187. if (viewExists)
  188. {
  189. //Get Customer Number
  190. int customerId;
  191. Int32.TryParse(dataObject.td_customer.customer_id, out customerId);
  192. var customerInfo = _aggSvc.ThinkHelper.GetCustomerInfo(customerId);
  193. dataObject.td_customer.Username = customerInfo.Username;
  194. // Send email
  195. try
  196. {
  197. _aggSvc.EmailContext.FromEmail = matchedTemplate.FromEmail;
  198. _aggSvc.EmailContext.Subject = matchedTemplate.Subject;
  199. _aggSvc.EmailContext.Recipients = dataObject.td_email_recipient;
  200. _aggSvc.EmailContext.EmailHtmlBody = _aggSvc.ViewHelper.RenderViewToString(this.ControllerContext, templateNamePath, new EmailContext() { ThinkData = dataObject, AttachmentsRootUrl = _aggSvc.EmailContext.AttachmentsRootUrl });
  201. _aggSvc.EmailContext.HtmlSend();
  202. _aggSvc.Log.Information("Email sent:" + Environment.NewLine +
  203. " - Customer Id: " + dataObject.td_customer.customer_id + Environment.NewLine +
  204. " - email: " + dataObject.td_email_recipient + Environment.NewLine +
  205. " - transaction event: " + dataObject.transaction_event_descr);
  206. }
  207. catch (Exception ie)
  208. {
  209. // Internal server error
  210. httpStatusCode = new HttpStatusCodeResult(500);
  211. _aggSvc.Log.Exception(ie, "'Template' controller, 'Format' action, in the 'Send email' block of code");
  212. }
  213. }
  214. else
  215. _aggSvc.Log.Warning(string.Format("Request for email template '{0}' has failed. This template" +
  216. " has matching settings defined, but no View associated to it. Make sure that a" +
  217. " '{0}.cshtml' file has been defined in the Views/Template/Templates/ folder.",
  218. matchedTemplate.Name));
  219. }
  220. else
  221. {
  222. _aggSvc.Log.Warning(string.Format("Request for email template matching a '{0}' transaction has failed. " +
  223. "There are no templates that can match the settings defnied in that request.",
  224. dataObject.transaction_event_descr));
  225. }
  226. }
  227. }
  228. catch (Exception ie)
  229. {
  230. _aggSvc.Log.Exception(ie, "'Template' controller in the 'Format' action");
  231. }
  232. return httpStatusCode;
  233. }
  234. /// <summary>
  235. /// Send email
  236. /// </summary>
  237. /// <param name="emailContext"></param>
  238. /// <returns></returns>
  239. public ActionResult Send(EmailContext emailContext)
  240. {
  241. try
  242. {
  243. // Copy content into the EmailContext instance that has
  244. // a valid host configured
  245. _aggSvc.EmailContext.AbsorbContent(emailContext);
  246. string templateName = string.Format("Templates/{0}", _aggSvc.EmailContext.TemplateName);
  247. bool viewExists = this.ControllerContext.ViewExist(templateName);
  248. if (viewExists)
  249. {
  250. ThinkTransactionData dummyDataObject = GetDummyThinkObject("DummyEvent.xml");
  251. _aggSvc.EmailContext.EmailHtmlBody = _aggSvc.ViewHelper.RenderViewToString(this.ControllerContext, templateName, new EmailContext() { ThinkData = dummyDataObject, AttachmentsRootUrl = _aggSvc.EmailContext.AttachmentsRootUrl });
  252. _aggSvc.EmailContext.HtmlSend();
  253. ViewBag.Title = "Email successfully sent";
  254. return PartialView();
  255. }
  256. else
  257. return PartialView("Unknown");
  258. }
  259. catch (Exception ie)
  260. {
  261. _aggSvc.Log.Exception(ie);
  262. return PartialView("Error");
  263. }
  264. }
  265. public ActionResult DeleteTemplate(string emailTemplate)
  266. {
  267. try
  268. {
  269. string templateName = string.Format("Templates/{0}", emailTemplate);
  270. bool viewExists = this.ControllerContext.ViewExist(templateName);
  271. if (viewExists)
  272. {
  273. string fileNameWithExt = string.Format("{0}.cshtml", emailTemplate);
  274. string fileMachinePath = Path.Combine(_aggSvc.HttpContext.Request.MapPath("~/Views/Template/Templates"), fileNameWithExt);
  275. System.IO.File.Delete(fileMachinePath);
  276. _aggSvc.ThinkHelper.DeleteTemplateSettings(emailTemplate);
  277. }
  278. return RedirectToAction("Index");
  279. }
  280. catch (Exception ie)
  281. {
  282. _aggSvc.Log.Exception(ie);
  283. return View("Error");
  284. }
  285. }
  286. public ActionResult DownloadTemplate(string emailTemplate)
  287. {
  288. try
  289. {
  290. string templateName = string.Format("Templates/{0}", emailTemplate);
  291. bool viewExists = this.ControllerContext.ViewExist(templateName);
  292. if (viewExists)
  293. {
  294. string fileName = string.Format("{0}.cshtml", emailTemplate);
  295. return File(_aggSvc.HttpContext.Request.MapPath(string.Format("~/Views/Template/Templates/{0}", fileName)), "text/plain", fileName);
  296. }
  297. else
  298. return RedirectToAction("Index");
  299. }
  300. catch (Exception ie)
  301. {
  302. _aggSvc.Log.Exception(ie);
  303. return View("Error");
  304. }
  305. }
  306. /// <summary>
  307. /// Upload template file to the file system.
  308. /// That is the second part of a 2 steps process to Upload
  309. /// a new file to the file system. The first step was the client asynchronous
  310. /// request(using jQuery) to the "CheckTemplateExists" method
  311. /// </summary>
  312. /// <param name="file"></param>
  313. /// <returns></returns>
  314. [HttpPost]
  315. public ActionResult UploadTemplate(HttpPostedFileBase file)
  316. {
  317. try
  318. {
  319. // Verify that the user selected a file
  320. if (file != null && file.ContentLength > 0)
  321. {
  322. string fileName = Path.GetFileNameWithoutExtension(file.FileName);
  323. string fileNameWithExt = Path.GetFileName(file.FileName);
  324. string fileMachinePath = Path.Combine(_aggSvc.HttpContext.Request.MapPath("~/Views/Template/Templates"), fileNameWithExt);
  325. string templateName = string.Format("Templates/{0}", fileName);
  326. bool viewExists = this.ControllerContext.ViewExist(templateName);
  327. if (viewExists)
  328. System.IO.File.Delete(fileMachinePath);
  329. file.SaveAs(fileMachinePath);
  330. }
  331. // redirect back to the index action to show the form once again
  332. return RedirectToAction("Index");
  333. }
  334. catch (Exception ie)
  335. {
  336. _aggSvc.Log.Exception(ie);
  337. return View("Error");
  338. }
  339. }
  340. /// <summary>
  341. /// Check if template exists in the file system.
  342. /// That is the first part of a 2 steps process to Upload
  343. /// a new file to the file system. This first part is asynchronously
  344. /// requested from the client using jQuery. Then, depending on the
  345. /// client response to that first step, the second step(UploadTemplate)
  346. /// may or may not be requested
  347. /// </summary>
  348. /// <param name="fileName"></param>
  349. /// <returns></returns>
  350. [HttpPost]
  351. public JsonResult CheckTemplateExists(string fileName)
  352. {
  353. try
  354. {
  355. string template = Path.GetFileNameWithoutExtension(fileName);
  356. string ext = Path.GetExtension(fileName);
  357. if (ext == ".cshtml")
  358. {
  359. string templateName = string.Format("Templates/{0}", template);
  360. bool viewExists = this.ControllerContext.ViewExist(templateName);
  361. if (viewExists)
  362. {
  363. return Json(new { code = "existingfile", message = "This file already exists." });
  364. }
  365. else
  366. {
  367. return Json(new { code = "newfile", message = "This file does not exist yet." });
  368. }
  369. }
  370. else
  371. {
  372. return Json(new { code = "wrongextension", message = "Your file is not valid. Please, provide a '.cshtml' file." });
  373. }
  374. }
  375. catch (Exception ie)
  376. {
  377. _aggSvc.Log.Exception(ie);
  378. return Json(new { code = "error", message = "An error occured. Please try again." });
  379. }
  380. }
  381. #endregion
  382. #region [ METHODS ]
  383. private TemplateSetting MatchTemplateSettingsToRequest(ThinkTransactionData thinkRequest)
  384. {
  385. TemplateSetting templateSettings = null;
  386. // Get the orderCode from the request
  387. int orderCode = 0;
  388. int sourceCode = 0;
  389. int subDef = 0;
  390. int orderClass = 0;
  391. if (thinkRequest != null &&
  392. thinkRequest.td_order != null &&
  393. thinkRequest.td_order.Any() &&
  394. thinkRequest.td_order.First().td_item != null &&
  395. thinkRequest.td_order.First().td_item.Any())
  396. {
  397. Int32.TryParse(thinkRequest.td_order.First().td_item.First().order_code_id, out orderCode);
  398. Int32.TryParse(thinkRequest.td_order.First().td_item.First().source_code_id, out sourceCode);
  399. Int32.TryParse(thinkRequest.td_order.First().td_item.First().subscription_def_id, out subDef);
  400. }
  401. // Get the order class associated to the order code(if the order code has been specified)
  402. if (orderCode != 0)
  403. orderClass = _aggSvc.ThinkHelper.GetMatchingOrderClass(orderCode);
  404. // Get all email settings that matches the request transaction event(e.g. all email settings for "Order Item Added")
  405. IEnumerable<TemplateSetting> matchingEmailSettings = _aggSvc.ThinkHelper.GetMatchingEmailSettings(thinkRequest);
  406. if (matchingEmailSettings != null)
  407. {
  408. // If an order class has been found, filter all email settings that match that order class
  409. if (orderClass != 0)
  410. matchingEmailSettings = matchingEmailSettings.Where(e => e.OrderClass != null && e.OrderClass.Id == orderClass).Select(e => e);
  411. // Set the default template if none of the following conditions return a result
  412. templateSettings = matchingEmailSettings.FirstOrDefault();
  413. // Get email setting that have a active filters
  414. matchingEmailSettings = matchingEmailSettings.Where(e => e.Filters != null && e.Filters.Any() && e.Filters.Any(f => f.Active)).Select(e => e);
  415. if (matchingEmailSettings != null && matchingEmailSettings.Any())
  416. {
  417. // The filtering priority has been defined by the requirement as follow:
  418. // 1st priority = source code
  419. // 2nd priority = subscription definition
  420. // 3rd priority = order code
  421. // Ex: if the request specifies OC:1 - SC:2 - SD:3, and there are 2 configs (1,null,3) and (1,2,null), then though both of them matches the request, the prioriry rule
  422. // will validate (1,2,null) over (1,null,3) for the source code is more important than the subscription def
  423. matchingEmailSettings = matchingEmailSettings
  424. .Where(e => e.Filters.Any(f =>
  425. !((f.OrderCode == null || f.OrderCode.Id == 0) && (f.SourceCode == null || f.SourceCode.Id == 0) && (f.SubscriptionDefinition == null || f.SubscriptionDefinition.Id == 0)) &&
  426. (f.OrderCode == null || f.OrderCode.Id == 0 || f.OrderCode.Id == orderCode) &&
  427. (f.SourceCode == null || f.SourceCode.Id == 0 || f.SourceCode.Id == sourceCode) &&
  428. (f.SubscriptionDefinition == null || f.SubscriptionDefinition.Id == 0 || f.SubscriptionDefinition.Id == subDef) &&
  429. f.Active))
  430. .OrderByDescending(e => e.Filters.OrderByDescending(f => f, new FilterComparer()).First(), new FilterComparer())
  431. .Select(e => e);
  432. if (matchingEmailSettings != null && matchingEmailSettings.Any())
  433. templateSettings = matchingEmailSettings.First();
  434. }
  435. }
  436. return templateSettings;
  437. }
  438. /// <summary>
  439. /// Convert a dummy XML file stored in the "DummyData" folder to a "ThinkTransactionData" object
  440. /// </summary>
  441. /// <returns></returns>
  442. private ThinkTransactionData GetDummyThinkObject(string dummyXmlFile)
  443. {
  444. string dummyRequest;
  445. string dummyFilePath = _aggSvc.HttpContext.Request.MapPath(string.Format(@"~/DummyData/{0}", dummyXmlFile));
  446. using (StreamReader streamReader = new StreamReader(dummyFilePath))
  447. {
  448. dummyRequest = streamReader.ReadToEnd();
  449. }
  450. XElement xmlRequest = XElement.Parse(dummyRequest);
  451. // Convert request to Business Object
  452. ThinkStreamToObject<ThinkTransactionData> thinkToObject = new ThinkStreamToObject<ThinkTransactionData>(xmlRequest);
  453. ThinkTransactionData dummyData = thinkToObject.Convert();
  454. if (dummyData != null)
  455. {
  456. dummyData.td_customer.Username = "[Username]";
  457. dummyData.CardType = "[Card Type]";
  458. dummyData.Last4Digits = "[Last 4 CC Digits]";
  459. dummyData.RenewDate = "[Renewal date]";
  460. dummyData.OrderPrice = "[Order Price]";
  461. dummyData.AddressStreet = "[Street Address]";
  462. dummyData.AddressUnit = "[Unit Address]";
  463. dummyData.State = "[State]";
  464. dummyData.Country = "[Country]";
  465. dummyData.OrderDescription = "[Order Description]";
  466. dummyData.FullAddress = "[Address]";
  467. }
  468. return dummyData;
  469. }
  470. protected override void Dispose(bool disposing)
  471. {
  472. base.Dispose(disposing);
  473. }
  474. #endregion
  475. }
  476. }