diff --git a/src/Postal.AspNetCore/Email.cs b/src/Postal.AspNetCore/Email.cs index 646ae74..79baa2c 100644 --- a/src/Postal.AspNetCore/Email.cs +++ b/src/Postal.AspNetCore/Email.cs @@ -2,11 +2,13 @@ using System.Collections.Generic; using System.Dynamic; using System.Net.Mail; +using System.Runtime.Serialization; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Routing; using Postal.AspNetCore; namespace Postal @@ -17,6 +19,7 @@ namespace Postal /// ViewBag property of a Controller. Any dynamic property access is mapped to the /// view data dictionary. /// + [DataContract] public class Email : DynamicObject, IViewData { /// Create an Email where the ViewName is derived from the name of the class. @@ -25,9 +28,8 @@ protected Email() { Attachments = new List(); ViewName = DeriveViewNameFromClassName(); - ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary()); - ViewData.Model = this; - ImageEmbedder = new ImageEmbedder(); + ViewData = new Dictionary(); + RequestPath = new RequestPath(); } /// @@ -38,16 +40,10 @@ protected Email() { } - public Email(string viewName, IModelMetadataProvider modelMetadataProvider) + public Email(string viewName, IModelMetadataProvider modelMetadataProvider): this() { if (viewName == null) throw new ArgumentNullException(nameof(viewName)); if (string.IsNullOrWhiteSpace(viewName)) throw new ArgumentException("View name cannot be empty.", "viewName"); - - Attachments = new List(); - ViewName = viewName; - ViewData = new ViewDataDictionary(modelMetadataProvider, new ModelStateDictionary()); - ViewData.Model = this; - ImageEmbedder = new ImageEmbedder(); } /// @@ -63,24 +59,29 @@ public Email(string viewName, string areaName, IModelMetadataProvider modelMetad /// /// The name of the view containing the email template. /// + [DataMember] public string ViewName { get; set; } /// /// The name of the area containing the email template. /// - public string AreaName { get; set; } + [DataMember] + public string? AreaName { get; set; } /// /// The view data to pass to the view. /// - public ViewDataDictionary ViewData { get; set; } + [DataMember] + public Dictionary ViewData { get; set; } /// /// The attachments to send with the email. /// + [DataMember] public List Attachments { get; set; } - internal ImageEmbedder ImageEmbedder { get; private set; } + [DataMember] + public RequestPath RequestPath { get; set; } /// /// Adds an attachment to the email. @@ -132,16 +133,13 @@ string DeriveViewNameFromClassName() return viewName; } - public RequestPath RequestPath { get; set; } - internal HttpContextData HttpContextData { get; private set; } public void CaptureHttpContext(HttpContext httpContext) { - var endpoint = httpContext.GetEndpoint(); var routeValues = httpContext.Features.Get()?.RouteValues; - HttpContextData = new HttpContextData { Endpoint = endpoint, RouteValues = routeValues }; RequestPath = new RequestPath(); + RequestPath.Path = httpContext.Request.Path.ToString(); RequestPath.PathBase = httpContext.Request.PathBase.ToString(); RequestPath.Host = httpContext.Request.Host.ToString(); RequestPath.IsHttps = httpContext.Request.IsHttps; diff --git a/src/Postal.AspNetCore/EmailParser.cs b/src/Postal.AspNetCore/EmailParser.cs index bc1006c..a44aca0 100644 --- a/src/Postal.AspNetCore/EmailParser.cs +++ b/src/Postal.AspNetCore/EmailParser.cs @@ -32,14 +32,14 @@ public EmailParser(IEmailViewRender alternativeViewRenderer) /// The email view output. /// The used to generate the output. /// A containing the email headers and content. - public async Task ParseAsync(string emailViewOutput, Email email) + public async Task ParseAsync(string emailViewOutput, Email email, ImageEmbedder? imageEmbedder = null) { var message = new MailMessage(); - await InitializeMailMessageAsync(message, emailViewOutput, email); + await InitializeMailMessageAsync(message, emailViewOutput, email, imageEmbedder); return message; } - private async Task InitializeMailMessageAsync(MailMessage message, string emailViewOutput, Email email) + private async Task InitializeMailMessageAsync(MailMessage message, string emailViewOutput, Email email, ImageEmbedder? imageEmbedder = null) { if (string.IsNullOrWhiteSpace(emailViewOutput)) { @@ -47,15 +47,15 @@ private async Task InitializeMailMessageAsync(MailMessage message, string emailV } using (var reader = new StringReader(emailViewOutput)) { - await ParserUtils.ParseHeadersAsync(reader, (key, value) => ProcessHeaderAsync(key, value, message, email)); + await ParserUtils.ParseHeadersAsync(reader, (key, value) => ProcessHeaderAsync(key, value, message, email, imageEmbedder)); AssignCommonHeaders(message, email); if (message.AlternateViews.Count == 0) { var messageBody = reader.ReadToEnd().Trim(); - if (email.ImageEmbedder.HasImages) + if (imageEmbedder != null && imageEmbedder.HasImages) { var view = AlternateView.CreateAlternateViewFromString(messageBody, new ContentType("text/html")); - email.ImageEmbedder.AddImagesToView(view); + imageEmbedder.AddImagesToView(view); message.AlternateViews.Add(view); message.Body = "Plain text not available."; message.IsBodyHtml = false; @@ -112,19 +112,29 @@ private void AssignCommonHeaders(MailMessage message, Email email) private void AssignCommonHeader(Email email, string header, Action assign) where T : class { - object value; + object? value; if (email.ViewData.TryGetValue(header, out value)) { - var typedValue = value as T; - if (typedValue != null) assign(typedValue); + if (value is T typedValue) + { + assign(typedValue); + return; + } + } + var foundKV = email.ViewData.Where(x => String.Equals(x.Key, header, StringComparison.OrdinalIgnoreCase) && x.Value is T typedValue); + if (foundKV.Any()) + { + var val = foundKV.First(); + assign((T)val.Value); + return; } } - private async Task ProcessHeaderAsync(string key, string value, MailMessage message, Email email) + private async Task ProcessHeaderAsync(string key, string value, MailMessage message, Email email, ImageEmbedder? imageEmbedder) { if (IsAlternativeViewsHeader(key)) { - foreach (var view in CreateAlternativeViews(value, email)) + foreach (var view in CreateAlternativeViews(value, email, imageEmbedder)) { message.AlternateViews.Add(await view); } @@ -135,17 +145,17 @@ private async Task ProcessHeaderAsync(string key, string value, MailMessage mess } } - private IEnumerable> CreateAlternativeViews(string deliminatedViewNames, Email email) + private IEnumerable> CreateAlternativeViews(string deliminatedViewNames, Email email, ImageEmbedder? imageEmbedder) { var viewNames = deliminatedViewNames.Split(new[] { ',', ' ', ';' }, StringSplitOptions.RemoveEmptyEntries); - return viewNames.Select(v => CreateAlternativeView(email, v)).ToList(); + return viewNames.Select(v => CreateAlternativeView(email, v, imageEmbedder)).ToList(); } - private async Task CreateAlternativeView(Email email, string alternativeViewName) + private async Task CreateAlternativeView(Email email, string alternativeViewName, ImageEmbedder? imageEmbedder) { var fullViewName = GetAlternativeViewName(email, alternativeViewName); - var output = await alternativeViewRenderer.RenderAsync(email, fullViewName); - string contentType; + var output = await alternativeViewRenderer.RenderAsync(email, fullViewName, imageEmbedder); + string? contentType; string body; using (var reader = new StringReader(output)) { @@ -179,7 +189,10 @@ private async Task CreateAlternativeView(Email email, string alte // A different charset can be specified in the Content-Type header. // e.g. Content-Type: text/html; charset=utf-8 } - email.ImageEmbedder.AddImagesToView(alternativeView); + if (imageEmbedder != null) + { + imageEmbedder.AddImagesToView(alternativeView); + } return alternativeView; } @@ -206,9 +219,9 @@ private MemoryStream CreateStreamOfBody(string body) return stream; } - private string ParseHeadersForContentType(StringReader reader) + private string? ParseHeadersForContentType(StringReader reader) { - string contentType = null; + string? contentType = null; ParserUtils.ParseHeaders(reader, (key, value) => { if (key.Equals("content-type", StringComparison.OrdinalIgnoreCase)) diff --git a/src/Postal.AspNetCore/EmailService.cs b/src/Postal.AspNetCore/EmailService.cs index 5c3e0b8..97752ed 100644 --- a/src/Postal.AspNetCore/EmailService.cs +++ b/src/Postal.AspNetCore/EmailService.cs @@ -15,18 +15,6 @@ namespace Postal /// public class EmailService : IEmailService { - /// Creates a new , using the given view engines. - [Obsolete] - public static EmailService Create(IServiceProvider serviceProvider, Func createSmtpClient = null) - { - var emailViewRender = serviceProvider.GetRequiredService(); - var emailParser = serviceProvider.GetRequiredService(); - var options = Options.Create(new EmailServiceOptions() { CreateSmtpClient = createSmtpClient }); - var loggerFactory = serviceProvider.GetRequiredService(); - var logger = loggerFactory.CreateLogger(); - return new EmailService(emailViewRender, emailParser, options, logger); - } - /// /// Creates a new . /// @@ -101,13 +89,19 @@ public async Task SendAsync(MailMessage mailMessage) /// A containing the rendered email. public async Task CreateMailMessageAsync(Email email) { - var rawEmailString = await emailViewRenderer.RenderAsync(email); + var imageEmbedder = new ImageEmbedder(); + var rawEmailString = await emailViewRenderer.RenderAsync(email, null, imageEmbedder); emailParser = new EmailParser(emailViewRenderer); - var mailMessage = await emailParser.ParseAsync(rawEmailString, email); + var mailMessage = await emailParser.ParseAsync(rawEmailString, email, imageEmbedder); if ((mailMessage.From == null || mailMessage.From.Address == null) && this.options.FromAddress != null) { mailMessage.From = new MailAddress(this.options.FromAddress); } + mailMessage.BodyTransferEncoding = System.Net.Mime.TransferEncoding.Base64; + if (options.BodyTransferEncoding != null) + { + mailMessage.BodyTransferEncoding = (System.Net.Mime.TransferEncoding)options.BodyTransferEncoding; + } return mailMessage; } } diff --git a/src/Postal.AspNetCore/EmailViewRender.cs b/src/Postal.AspNetCore/EmailViewRender.cs index 7789b05..59b2444 100644 --- a/src/Postal.AspNetCore/EmailViewRender.cs +++ b/src/Postal.AspNetCore/EmailViewRender.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.Extensions.Options; using Postal.AspNetCore; namespace Postal @@ -17,10 +18,13 @@ public class EmailViewRender : IEmailViewRender /// Creates a new that uses the given view engines. /// /// The view engines to use when rendering email views. - public EmailViewRender(ITemplateService templateService) + public EmailViewRender( + ITemplateService templateService, + IOptions options + ) { _templateService = templateService; - EmailViewDirectoryName = "Emails"; + _emailViewDirectoryName = options?.Value?.EmailViewsDirectory ?? "Emails"; } readonly ITemplateService _templateService; @@ -29,7 +33,7 @@ public EmailViewRender(ITemplateService templateService) /// The name of the directory in "Views" that contains the email views. /// By default, this is "Emails". /// - public string EmailViewDirectoryName { get; set; } + private string _emailViewDirectoryName; /// /// Renders an email view. @@ -38,7 +42,8 @@ public EmailViewRender(ITemplateService templateService) /// The rendered email view output. public virtual Task RenderAsync(Email email) { - return RenderAsync(email, null); + var imageEmbedder = new ImageEmbedder(); + return RenderAsync(email, null, imageEmbedder); } /// @@ -47,22 +52,35 @@ public virtual Task RenderAsync(Email email) /// The email to render. /// Optional email view name override. If null then the email's ViewName property is used instead. /// The rendered email view output. - public virtual async Task RenderAsync(Email email, string viewName = null) + public virtual Task RenderAsync(Email email, string? viewName) + { + var imageEmbedder = new ImageEmbedder(); + return RenderAsync(email, viewName, imageEmbedder); + } + + /// + /// Renders an email view. + /// + /// The email to render. + /// Optional email view name override. If null then the email's ViewName property is used instead. + /// Optional ImageEmbedder. If null then the email cannot be generated with Image. + /// The rendered email view output. + public virtual async Task RenderAsync(Email email, string? viewName = null, ImageEmbedder? imageEmbedder = null) { viewName = viewName ?? email.ViewName; var routeData = new Microsoft.AspNetCore.Routing.RouteData(); - routeData.Values["controller"] = EmailViewDirectoryName; - routeData.Values["page"] = EmailViewDirectoryName; + routeData.Values["controller"] = _emailViewDirectoryName; + routeData.Values["page"] = _emailViewDirectoryName; if (!string.IsNullOrWhiteSpace(email.AreaName)) { routeData.Values["area"] = email.AreaName; routeData.DataTokens["area"] = email.AreaName; } - Dictionary viewData = new Dictionary(); - viewData[ImageEmbedder.ViewDataKey] = email.ImageEmbedder; - var viewOutput = await _templateService.RenderTemplateAsync(email.HttpContextData, routeData, viewName, email, viewData, true); + Dictionary viewData = new Dictionary(); + viewData[ImageEmbedder.ViewDataKey] = imageEmbedder; + var viewOutput = await _templateService.RenderTemplateAsync(routeData, viewName, email, viewData, true); viewData.Remove(ImageEmbedder.ViewDataKey); return viewOutput; } diff --git a/src/Postal.AspNetCore/HttpContextData.cs b/src/Postal.AspNetCore/HttpContextData.cs deleted file mode 100644 index 61e2aa4..0000000 --- a/src/Postal.AspNetCore/HttpContextData.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Routing; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Postal.AspNetCore -{ - public class HttpContextData - { - public Endpoint Endpoint { get; internal set; } - public RouteValueDictionary RouteValues { get; internal set; } - } -} diff --git a/src/Postal.AspNetCore/InternalClass/EndpointFeature.cs b/src/Postal.AspNetCore/InternalClass/EndpointFeature.cs deleted file mode 100644 index f2740a5..0000000 --- a/src/Postal.AspNetCore/InternalClass/EndpointFeature.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Microsoft.AspNetCore.Http.Features; -using Microsoft.AspNetCore.Http; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Postal.AspNetCore.InternalClass -{ - internal sealed class EndpointFeature : IEndpointFeature - { - public Endpoint Endpoint { get; set; } - - public EndpointFeature(Endpoint endpoint) - { - Endpoint = endpoint; - } - } -} diff --git a/src/Postal.AspNetCore/TemplateServices/TemplateService.cs b/src/Postal.AspNetCore/TemplateServices/TemplateService.cs index dbf0882..958493c 100644 --- a/src/Postal.AspNetCore/TemplateServices/TemplateService.cs +++ b/src/Postal.AspNetCore/TemplateServices/TemplateService.cs @@ -3,13 +3,15 @@ using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Routing.Patterns; using Microsoft.Extensions.Logging; -using Postal.AspNetCore.InternalClass; using System; using System.Collections.Generic; using System.Diagnostics; @@ -19,7 +21,7 @@ namespace Postal.AspNetCore { - public class TemplateService : ITemplateService + public partial class TemplateService : ITemplateService { public static readonly string ViewExtension = ".cshtml"; @@ -31,6 +33,8 @@ public class TemplateService : ITemplateService private readonly IRazorPageActivator _pageActivator; private readonly System.Text.Encodings.Web.HtmlEncoder _htmlEncoder; private readonly DiagnosticListener _diagnosticListener; + private readonly IUrlHelperFactory _urlHelperFactory; + public TemplateService( ILogger logger, @@ -40,7 +44,8 @@ public TemplateService( ITempDataProvider tempDataProvider, Microsoft.Extensions.Hosting.IHostEnvironment hostingEnvironment, System.Text.Encodings.Web.HtmlEncoder htmlEncoder, - DiagnosticListener diagnosticListener + DiagnosticListener diagnosticListener, + IUrlHelperFactory urlHelperFactory ) { _logger = logger; @@ -51,55 +56,63 @@ DiagnosticListener diagnosticListener _pageActivator = pageActivator; _htmlEncoder = htmlEncoder; _diagnosticListener = diagnosticListener; + _urlHelperFactory = urlHelperFactory; } - public async Task RenderTemplateAsync(HttpContextData httpContextData, RouteData routeData, - string viewName, TViewModel viewModel, Dictionary additonalViewDictionary = null, bool isMainPage = true) where TViewModel : IViewData + [LoggerMessage( + EventId = 1, + Level = LogLevel.Debug, + Message = "RequestPath != null\nRequestPath:{requestPath}\nHttpRequest:{request}")] + public partial void LogRequestPath(RequestPath requestPath, HttpRequest request); + + [LoggerMessage( + EventId = 2, + Level = LogLevel.Debug, + Message = "_hostingEnvironment is {type} -> GetView from {property}: {value}")] + public partial void LogHostEnvironment(string type, string property, string value); + + public async Task RenderTemplateAsync(RouteData routeData, + string viewName, TViewModel viewModel, Dictionary? additonalViewDictionary = null, bool isMainPage = true) where TViewModel : IViewData { var httpContext = new DefaultHttpContext() { RequestServices = _serviceProvider, }; - if (httpContextData != null) - { - httpContext.Features.Set(new EndpointFeature(httpContextData.Endpoint)); - httpContext.Features.Set(new RouteValuesFeature() { RouteValues = httpContextData.RouteValues }); - } if (viewModel.RequestPath != null) { httpContext.Request.Host = HostString.FromUriComponent(viewModel.RequestPath.Host); httpContext.Request.Scheme = viewModel.RequestPath.Scheme; + httpContext.Request.Path = viewModel.RequestPath.Path != null ? PathString.FromUriComponent(viewModel.RequestPath.Path) : null; httpContext.Request.PathBase = PathString.FromUriComponent(viewModel.RequestPath.PathBase); - _logger.LogDebug($"RequestPath != null"); - _logger.LogTrace($"\tHost: {viewModel.RequestPath.Host} -> {httpContext.Request.Host}"); - _logger.LogTrace($"\tScheme: {viewModel.RequestPath.Scheme}"); - _logger.LogTrace($"\tPathBase: {viewModel.RequestPath.PathBase} -> {httpContext.Request.PathBase}"); + this.LogRequestPath(viewModel.RequestPath, httpContext.Request); } var actionDescriptor = new ActionDescriptor { - RouteValues = routeData.Values.ToDictionary(kv => kv.Key, kv => kv.Value.ToString()) + RouteValues = routeData.Values.ToDictionary(kv => kv.Key, kv => kv.Value!.ToString()) }; + httpContext.SetEndpoint(new Endpoint(r => Task.CompletedTask, null, null)); var actionContext = new ActionContext(httpContext, routeData, actionDescriptor); + this._urlHelperFactory.GetUrlHelper(actionContext); using (var outputWriter = new StringWriter()) { - Microsoft.AspNetCore.Mvc.ViewEngines.ViewEngineResult viewResult = null; + Microsoft.AspNetCore.Mvc.ViewEngines.ViewEngineResult? viewResult = null; RazorPageResult? razorPageResult = null; if (IsApplicationRelativePath(viewName) || IsRelativePath(viewName)) { _logger.LogDebug($"Relative path"); if (_hostingEnvironment is Microsoft.AspNetCore.Hosting.IWebHostEnvironment webHostEnvironment) { - _logger.LogDebug($"_hostingEnvironment is IWebHostEnvironment -> GetView from WebRootPath: {webHostEnvironment.WebRootPath}"); + this.LogHostEnvironment("IWebHostEnvironment", "WebRootPath", webHostEnvironment.WebRootPath); viewResult = _viewEngine.GetView(webHostEnvironment.WebRootPath, viewName, isMainPage); } else { - _logger.LogDebug($"_hostingEnvironment is IHostEnvironment -> GetView from ContentRootPath: {_hostingEnvironment.ContentRootPath}"); + this.LogHostEnvironment("IHostEnvironment", "ContentRootPath", _hostingEnvironment.ContentRootPath); viewResult = _viewEngine.GetView(_hostingEnvironment.ContentRootPath, viewName, isMainPage); } } @@ -107,10 +120,27 @@ public async Task RenderTemplateAsync(HttpContextData httpCo { _logger.LogDebug($"Not a relative path"); viewResult = _viewEngine.FindView(actionContext, viewName, isMainPage); - razorPageResult = _viewEngine.FindPage(actionContext, viewName); + } + razorPageResult = _viewEngine.FindPage(actionContext, viewName); + + if ((viewResult == null || !viewResult.Success) && (razorPageResult == null || (razorPageResult != null && razorPageResult?.Page == null))) + { + var searchedLocations = viewResult?.SearchedLocations ?? []; + if (razorPageResult != null && razorPageResult?.SearchedLocations != null) + { + searchedLocations = searchedLocations.Union(razorPageResult?.SearchedLocations!); + } + _logger.LogError($"Failed to render template {viewName} because it was not found. \r\nThe following locations are searched: \r\n{string.Join("\r\n", searchedLocations)}"); + throw new TemplateServiceException($"Failed to render template {viewName} because it was not found. \r\nThe following locations are searched: \r\n{string.Join("\r\n", searchedLocations)}"); } - var viewDictionary = new ViewDataDictionary(viewModel.ViewData, viewModel); + var viewDictionary = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary()); + foreach (var kv in viewModel.ViewData) + { + viewDictionary.Add(kv.Key, kv.Value); + } + viewDictionary.Model = viewModel; + if (additonalViewDictionary != null) { _logger.LogDebug($"additonalViewDictionary count: {additonalViewDictionary.Count}"); @@ -129,20 +159,9 @@ public async Task RenderTemplateAsync(HttpContextData httpCo var tempDataDictionary = new TempDataDictionary(httpContext, _tempDataProvider); - if (!viewResult.Success && (razorPageResult == null || (razorPageResult != null && razorPageResult?.Page == null))) - { - var searchedLocations = viewResult.SearchedLocations; - if (razorPageResult?.SearchedLocations != null) - { - searchedLocations = viewResult.SearchedLocations.Union(razorPageResult?.SearchedLocations); - } - _logger.LogError($"Failed to render template {viewName} because it was not found. \r\nThe following locations are searched: \r\n{string.Join("\r\n", searchedLocations)}"); - throw new TemplateServiceException($"Failed to render template {viewName} because it was not found. \r\nThe following locations are searched: \r\n{string.Join("\r\n", searchedLocations)}"); - } - try { - if (viewResult.Success) + if (viewResult!.Success) { _logger.LogDebug($"View template found: {viewResult.View.Path}"); var viewContext = new ViewContext(actionContext, viewResult.View, viewDictionary, @@ -152,8 +171,8 @@ public async Task RenderTemplateAsync(HttpContextData httpCo } else if (razorPageResult?.Page != null) { - _logger.LogDebug($"Razor page found: {razorPageResult?.Page.Path}"); - var page = razorPageResult?.Page; + var page = razorPageResult?.Page!; + _logger.LogDebug($"Razor page found: {page.Path}"); var razorView = new RazorView( _viewEngine, _pageActivator, @@ -166,7 +185,7 @@ public async Task RenderTemplateAsync(HttpContextData httpCo var viewContext = new ViewContext(actionContext, razorView, viewDictionary, tempDataDictionary, outputWriter, new HtmlHelperOptions()); - await viewResult.View.RenderAsync(viewContext); + await viewResult.View!.RenderAsync(viewContext); var pageNormal = ((Page)page); pageNormal.PageContext = new PageContext(); pageNormal.ViewContext = viewContext; diff --git a/src/Postal.AspNetCore/TransferEncoding.cs b/src/Postal.AspNetCore/TransferEncoding.cs new file mode 100644 index 0000000..8c63bdf --- /dev/null +++ b/src/Postal.AspNetCore/TransferEncoding.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Postal.AspNetCore +{ + // + // Summary: + // Specifies the Content-Transfer-Encoding header information for an email message + // attachment. + public enum TransferEncoding + { + // + // Summary: + // Indicates that the transfer encoding is unknown. + Unknown = -1, + // + // Summary: + // Encodes data that consists of printable characters in the US-ASCII character + // set. See RFC 2406 Section 6.7. + QuotedPrintable = 0, + // + // Summary: + // Encodes stream-based data. See RFC 2406 Section 6.8. + Base64 = 1, + // + // Summary: + // Used for data that is not encoded. The data is in 7-bit US-ASCII characters with + // a total line length of no longer than 1000 characters. See RFC2406 Section 2.7. + SevenBit = 2, + // + // Summary: + // The data is in 8-bit characters that may represent international characters with + // a total line length of no longer than 1000 8-bit characters. For more information + // about this 8-bit MIME transport extension, see IETF RFC 6152. + EightBit = 3 + } +}