Permalink
Browse files

wip emailing service for user registration

  • Loading branch information...
CoolGoose committed Dec 8, 2018
1 parent e042e25 commit dc6c974fb398cd9d576e3c6acf6c9121b26794fc
@@ -0,0 +1,67 @@
using System;
using MailKit.Net.Smtp;

namespace Craidd.Config
{
public class EmailsServiceOptions
{
/// <summary>
/// Gets or sets the name or IP Address of the host used for SMTP transactions.
/// </summary>
public string host { get; set; }

/// <summary>
/// Gets or sets the port used for SMTP transactions.
/// </summary>
public int port { get; set; } = 25;

/// <summary>
/// Specify whether the <see cref="SmtpClient"/> uses Secure Sockets Layer (SSL) to encrypt the connection.
/// </summary>
public bool useSSL { get; set; } = false;

/// <summary>
/// Gets or sets the account used for SMTP transactions.
/// </summary>
public string username { get; set; }

/// <summary>
/// Gets or sets the password used for SMTP transactions.
/// </summary>
public string password { get; set; }

/// <summary>
/// Gets or sets the email for the from address.
///
public string fromEmail { get; set; }

/// <summary>
/// Gets or sets the name for the from address.
/// </summary>
public string fromName { get; set; }

public void Validate()
{
if (string.IsNullOrWhiteSpace(host))
{
throw new ArgumentNullException(nameof(host));
}
if (string.IsNullOrWhiteSpace(username))
{
throw new ArgumentNullException(nameof(username));
}
if (password == null)
{
throw new ArgumentNullException(nameof(password));
}
if (fromName == null)
{
throw new ArgumentNullException(nameof(fromName));
}
if (fromEmail == null)
{
throw new ArgumentNullException(nameof(fromEmail));
}
}
}
}
@@ -34,19 +34,23 @@ public class UsersController : ControllerBase
private readonly IHttpContextAccessor _httpContext;
private IApiResponseHelper _apiResponse;

private readonly IEmailsService _emails;

public UsersController(
IConfiguration config,
IHttpContextAccessor httpContextAccessor,
TasksService tasks,
IUsersService users,
IApiResponseHelper apiResponse
IApiResponseHelper apiResponse,
IEmailsService emailsService
)
{
_config = config;
_tasks = tasks;
_users = users;
_httpContext = httpContextAccessor;
_apiResponse = apiResponse;
_emails = emailsService;
}

[HttpGet]
@@ -87,6 +91,10 @@ public async Task<IActionResult> Store([FromBody] Models.Validators.UserRegister
return BadRequest(_apiResponse.ErrorReponse);
}

await this._emails.sendEmailFromTemplateAsync(user.Email, "Registered", "Register", new Dictionary<string, object> {
{"Name", user.UserName}
});

return StatusCode(StatusCodes.Status201Created, new {
data = new { id = user.Id }
});
@@ -6,13 +6,16 @@
<Folder Include="wwwroot\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Cors" Version="2.1.0" />
<PackageReference Include="MailKit" Version="2.0.7" />
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.1.6" />
<PackageReference Include="Microsoft.AspNetCore.Cors" Version="2.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="2.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.1.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.1.4" />
<PackageReference Include="MimeKit" Version="2.0.7" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="RazorLight" Version="2.0.0-beta1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="1.1.0" />
</ItemGroup>
<ItemGroup>
@@ -0,0 +1,34 @@
using Craidd.Config;
using Craidd.Services;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using System;

namespace Craidd.Extensions
{
public static class EmailsSenderExtensions
{
/// <summary>
/// Using Email Middleware
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> passed to the configuration method.</param>
/// <param name="setupAction">The middleware configuration options.</param>
/// <returns>The updated <see cref="IServiceCollection"/>.</returns>
public static IServiceCollection AddEmail(this IServiceCollection services, Action<EmailsServiceOptions> setupAction = null)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}

if (setupAction != null)
{
services.Configure(setupAction); // IOptions<EmailsServiceOptions>
}

services.TryAddTransient<IEmailsService, EmailsService>();

return services;
}
}
}
@@ -0,0 +1,77 @@
using System.Threading.Tasks;
using System.Collections.Generic;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.Extensions.Options;
using MailKit.Net.Smtp;
using MimeKit;
using Craidd.Config;
using System;
using System.Linq;
using System.Dynamic;

namespace Craidd.Services
{
public class EmailsService : IEmailsService
{
private readonly EmailsServiceOptions _options;
private readonly ITemplatesService _templatesService;
public EmailsService(IOptions<EmailsServiceOptions> optionsAccessor, ITemplatesService templatesService)
{
_options = optionsAccessor?.Value ?? throw new ArgumentNullException(nameof(optionsAccessor));
_options.Validate();
_templatesService = templatesService;
}

public async Task<bool> sendEmailFromTemplateAsync(string email, string subject, string templateFile, Dictionary<string, object> messageData)
{
var message = new MimeMessage();
message.From.Add(new MailboxAddress(_options.fromName, _options.fromEmail));
message.To.Add(new MailboxAddress(email));
message.Subject = subject;

var localMessageData = messageData.Aggregate(
new ExpandoObject() as IDictionary<string, Object>,
(a, p) => { a.Add(p.Key, p.Value); return a; }
);

string html = await _templatesService.engine.CompileRenderAsync($"Emails/{templateFile}.cshtml", localMessageData);
message.Body = new TextPart("html")
{
Text = html
};

using (var client = new SmtpClient())
{
try
{
await client.ConnectAsync(_options.host, _options.port, _options.useSSL);
await client.AuthenticateAsync(_options.username, _options.password);
await client.SendAsync(message);
}
catch (SmtpCommandException ex)
{
switch (ex.ErrorCode)
{
case SmtpErrorCode.RecipientNotAccepted:
Console.WriteLine("\tRecipient not accepted: {0}", ex.Mailbox);
break;
case SmtpErrorCode.SenderNotAccepted:
Console.WriteLine("\tSender not accepted: {0}", ex.Mailbox);
break;
case SmtpErrorCode.MessageNotAccepted:
Console.WriteLine("\tMessage not accepted.");
break;
}

return false;
}
finally
{
await client.DisconnectAsync(true);
}
}

return true;
}
}
}
@@ -0,0 +1,10 @@
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Craidd.Services
{
public interface IEmailsService
{
Task<bool> sendEmailFromTemplateAsync(string email, string subject, string templateFile, Dictionary<string, object> messageData);
}
}
@@ -0,0 +1,9 @@
using RazorLight;

namespace Craidd.Services
{
public interface ITemplatesService
{
RazorLightEngine engine { get; set; }
}
}
@@ -0,0 +1,17 @@
using System;
using RazorLight;

namespace Craidd.Services
{
public class TemplatesService : ITemplatesService
{
public RazorLightEngine engine { get; set; }
public TemplatesService(string templatePath)
{
engine = new RazorLightEngineBuilder()
.UseFilesystemProject(templatePath)
.UseMemoryCachingProvider()
.Build();
}
}
}
@@ -28,7 +28,7 @@
using Craidd.Models;
using Craidd.Services;
using Craidd.Helpers;

using Craidd.Extensions;
namespace Craidd
{
public class Startup
@@ -49,6 +49,26 @@ public void ConfigureServices(IServiceCollection services)
services.AddDbContext<AppDbContext>(opt => opt.UseSqlite("Data Source=" + dbPath));

services.AddTransient<IApiResponseHelper, ApiResponseHelper>();
services.AddSingleton<ITemplatesService>(service => new TemplatesService(templatePath: System.IO.Path.Combine(Environment.CurrentDirectory, "Views")));

// services.AddScoped<IEmailsService>(service =>
// {
// var options = Configuration.GetSection("Email:Smtp")
// .GetChildren().ToDictionary(x => x.Key, x => x.Value);
// return new EmailsService(options: options, templatesService: service.GetService<ITemplatesService>());
// });

services.AddEmail(options =>
{
options.host = Configuration["Email:Smtp:Host"];
options.port = Int32.Parse(Configuration["Email:Smtp:Port"]);
options.useSSL = Boolean.Parse(Configuration["Email:Smtp:UseSSL"]);
options.username = Configuration["Email:Smtp:Username"];
options.password = Configuration["Email:Smtp:Password"];
options.fromName = Configuration["Email:Smtp:FromName"];
options.fromEmail = Configuration["Email:Smtp:FromEmail"];
});

services.AddScoped<TasksService>();
services.AddScoped<IUsersService, UsersService>();

@@ -92,6 +112,9 @@ public void ConfigureServices(IServiceCollection services)
// User settings
options.User.RequireUniqueEmail = true;

// Require an email validation before login
options.SignIn.RequireConfirmedEmail = true;

options.ClaimsIdentity.UserIdClaimType = JwtRegisteredClaimNames.Sub;
})
.AddEntityFrameworkStores<AppDbContext>()
@@ -102,7 +125,8 @@ public void ConfigureServices(IServiceCollection services)
services.AddApiVersioning();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

services.AddSwaggerGen(c => {
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "Rheoli API", Version = "v1" });
// Set the comments path for the Swagger JSON and UI.
var xmlPath = System.IO.Path.Combine(AppContext.BaseDirectory, "Craidd.xml");
@@ -127,14 +151,15 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env)
.AllowCredentials()
);

// System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.DefaultInboundClaimFilter.Clear();
// System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.DefaultInboundClaimFilter.Clear();
app.UseAuthentication();
app.UseMvc();

// Enable middleware to serve generated Swagger as a JSON endpoint.
app.UseSwagger();
// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c => {
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Rheoli V1");
});
}
@@ -0,0 +1 @@
<p> @Model.Name </p>

0 comments on commit dc6c974

Please sign in to comment.