Code
Data Folder
Person.cs
using System.ComponentModel.DataAnnotations;
namespace SQLiteBirthdayReminders.Data
{
public class Person
{
[Required]
public int PersonID { get; set; }
[Required(ErrorMessage = "'First Name' is required.")]
[StringLength(50, ErrorMessage = "'First Name' has a maximum length of 50 characters.")]
public string PersonFirstName { get; set; } = String.Empty;
[Required(ErrorMessage = "'Last Name' is required.")]
[StringLength(50, ErrorMessage = "'Last Name' has a maximum length of 50 characters.")]
public string PersonLastName { get; set; } = String.Empty;
[Required(ErrorMessage = "Date of Birth is compulsory")]
public DateTime PersonDateOfBirth { get; set; }
[Required(ErrorMessage = "'Send Reminder To' is required.")]
[EmailAddress(ErrorMessage = "Invalid Email Address format.")]
[StringLength(100, ErrorMessage = "'Email' has a maximum length of 100 characters.")]
public string PersonSendReminderTo { get; set; } = String.Empty;
public string? PersonFullName { get; }
public DateTime NextBirthday { get; }
public int? DaysToNextBirthday { get; }
public int? AgeNextBirthday { get; }
}
}
PersonService.cs
using Dapper;
using System.Data.SQLite;
using System.Data;
namespace SQLiteBirthdayReminders.Data
{
public class PersonService : IPersonService
{
// Database connection
private readonly IConfiguration _configuration;
public PersonService(IConfiguration configuration)
{
_configuration = configuration;
}
public string connectionId = "Default";
public string sqlCommand = "";
// Get a list of person rows (SQL Select)
public async Task<IEnumerable<Person>> PersonList()
{
IEnumerable<Person> people;
sqlCommand = "Select * from Person";
using IDbConnection conn = new SQLiteConnection(_configuration.GetConnectionString(connectionId));
{
people = await conn.QueryAsync<Person>(sqlCommand);
}
return people;
}
// Add (create) a Person table row (SQL Insert)
public async Task<bool> PersonInsert(Person person)
{
var parameters = new DynamicParameters();
parameters.Add("@PersonFirstName", person.PersonFirstName, DbType.String);
parameters.Add("@PersonLastName", person.PersonLastName, DbType.String);
parameters.Add("@PersonDateOfBirth", person.PersonDateOfBirth, DbType.Date);
parameters.Add("@PersonSendReminderTo", person.PersonSendReminderTo, DbType.String);
sqlCommand = "Insert into Person (PersonFirstName, PersonLastName, PersonDateOfBirth, PersonSendReminderTo) ";
sqlCommand += "values(@PersonFirstName, @PersonLastName, date(@PersonDateOfBirth), @PersonSendReminderTo)";
using IDbConnection conn = new SQLiteConnection(_configuration.GetConnectionString(connectionId));
{
await conn.ExecuteAsync(sqlCommand, parameters);
}
return true;
}
public async Task<int> PersonInsertDuplicateCheck(Person person)
{
int Count;
var parameters = new DynamicParameters();
parameters.Add("@PersonFirstName", person.PersonFirstName, DbType.String);
parameters.Add("@PersonLastName", person.PersonLastName, DbType.String);
parameters.Add("@PersonDateOfBirth", person.PersonDateOfBirth, DbType.Date);
parameters.Add("@PersonSendReminderTo", person.PersonSendReminderTo, DbType.String);
sqlCommand = "SELECT COUNT(*) ";
sqlCommand += "FROM Person";
sqlCommand += " WHERE PersonFirstName = @PersonFirstName";
sqlCommand += " AND PersonLastName = @PersonLastName";
sqlCommand += " AND PersonDateOfBirth = date(@PersonDateOfBirth)";
sqlCommand += " AND PersonSendReminderTo = @PersonSendReminderTo";
using IDbConnection conn = new SQLiteConnection(_configuration.GetConnectionString(connectionId));
{
Count = await conn.QueryFirstOrDefaultAsync<int>(sqlCommand, parameters);
}
return Count; ;
}
public async Task<int> PersonUpdateDuplicateCheck(Person person)
{
int Count;
var parameters = new DynamicParameters();
parameters.Add("@PersonID", person.PersonID, DbType.Int32);
parameters.Add("@PersonFirstName", person.PersonFirstName, DbType.String);
parameters.Add("@PersonLastName", person.PersonLastName, DbType.String);
parameters.Add("@PersonDateOfBirth", person.PersonDateOfBirth, DbType.Date);
parameters.Add("@PersonSendReminderTo", person.PersonSendReminderTo, DbType.String);
sqlCommand = "SELECT COUNT(*) ";
sqlCommand += "FROM Person";
sqlCommand += " WHERE PersonFirstName = @PersonFirstName";
sqlCommand += " AND PersonLastName = @PersonLastName";
sqlCommand += " AND PersonDateOfBirth = date(@PersonDateOfBirth)";
sqlCommand += " AND PersonSendReminderTo = @PersonSendReminderTo";
sqlCommand += " AND PersonID != @PersonID";
using IDbConnection conn = new SQLiteConnection(_configuration.GetConnectionString(connectionId));
{
Count = await conn.QueryFirstOrDefaultAsync<int>(sqlCommand, parameters);
}
return Count;
}
public async Task<bool> PersonUpdate(Person person)
{
var parameters = new DynamicParameters();
parameters.Add("PersonID", person.PersonID, DbType.Int32);
parameters.Add("PersonFirstName", person.PersonFirstName, DbType.String);
parameters.Add("PersonLastName", person.PersonLastName, DbType.String);
parameters.Add("PersonDateOfBirth", person.PersonDateOfBirth, DbType.Date);
parameters.Add("PersonSendReminderTo", person.PersonSendReminderTo, DbType.String);
sqlCommand = "Update Person ";
sqlCommand += "SET PersonFirstName = @PersonFirstName, ";
sqlCommand += "PersonLastName = @PersonLastName, ";
sqlCommand += "PersonDateOfBirth = date(@PersonDateOfBirth), ";
sqlCommand += "PersonSendReminderTo = @PersonSendReminderTo ";
sqlCommand += "WHERE PersonID = @PersonID";
using IDbConnection conn = new SQLiteConnection(_configuration.GetConnectionString(connectionId));
{
await conn.ExecuteAsync(sqlCommand, parameters);
}
return true;
}
public async Task<Person> PersonGetOne(int PersonID)
{
Person person = new Person();
var parameters = new DynamicParameters();
parameters.Add("@PersonID", PersonID, DbType.Int32);
sqlCommand = "SELECT PersonID, PersonFirstName, ";
sqlCommand += "PersonLastName, PersonDateOfBirth, ";
sqlCommand += "PersonSendReminderTo ";
sqlCommand += "FROM Person WHERE PersonID = @PersonID";
using IDbConnection conn = new SQLiteConnection(_configuration.GetConnectionString(connectionId));
{
person = await conn.QueryFirstOrDefaultAsync<Person>(sqlCommand, parameters);
}
return person;
}
// Physically delete one Person row based on its PersonID (SQL Delete)
public async Task<bool> PersonDelete(int PersonID)
{
var parameters = new DynamicParameters();
parameters.Add("@PersonID", PersonID, DbType.Int32);
sqlCommand = "DELETE FROM Person ";
sqlCommand += "WHERE PersonID= @PersonID";
using IDbConnection conn = new SQLiteConnection(_configuration.GetConnectionString(connectionId));
{
await conn.ExecuteAsync(sqlCommand, parameters);
}
return true;
}
// Get a list of person rows by UserEmail
public async Task<IEnumerable<Person>> PersonListGetByUser(string PersonSendReminderTo)
{
IEnumerable<Person> people;
var parameters = new DynamicParameters();
parameters.Add("@PersonSendReminderTo", PersonSendReminderTo, DbType.String);
sqlCommand = "SELECT PersonID, PersonFirstName, PersonLastName, ";
sqlCommand += "PersonDateOfBirth, PersonSendReminderTo, ";
//If Statement: iif(Test Expression, Do if True, Do if false)
sqlCommand += "iif(";
//If this is true
sqlCommand += "strftime('%j', 'now') < strftime('%j', PersonDateOfBirth), "; //(current day of year < day of year of birthday)
//Then do this:
sqlCommand += "strftime('%Y', 'now') - strftime('%Y', PersonDateOfBirth), "; //Calc their current age if current day of year < day of year of birthday
//Otherwise, do this:
sqlCommand += "strftime('%Y', 'now') - strftime('%Y', PersonDateOfBirth) + 1"; //Calc their current age if current day of year > day of year of birthday
//Close If statement
sqlCommand += ") ";
sqlCommand += "as AgeNextBirthday, ";
sqlCommand += "date(PersonDateOfBirth, '+'||";
sqlCommand += "iif(";
//If this is true
sqlCommand += "strftime('%j', 'now') < strftime('%j', PersonDateOfBirth), "; //(current day of year < day of year of birthday)
//Then do this:
sqlCommand += "strftime('%Y', 'now') - strftime('%Y', PersonDateOfBirth), "; //Calc their current age if current day of year < day of year of birthday
//Otherwise, do this:
sqlCommand += "strftime('%Y', 'now') - strftime('%Y', PersonDateOfBirth) + 1"; //Calc their current age if current day of year > day of year of birthday
//Close If statement
sqlCommand += ") ";
sqlCommand += "||' years'";
sqlCommand += ") ";
sqlCommand += "as NextBirthday ";
sqlCommand += "FROM Person ";
sqlCommand += "WHERE PersonSendReminderTo = @PersonSendReminderTo ";
sqlCommand += "ORDER BY NextBirthday ";
using IDbConnection conn = new SQLiteConnection(_configuration.GetConnectionString(connectionId));
{
people = await conn.QueryAsync<Person>(sqlCommand, parameters);
}
return people;
}
public async Task<IEnumerable<Person>> PersonGetByDaysToNextBirthday(int DaysToNextBirthday)
{
IEnumerable<Person> people;
var parameters = new DynamicParameters();
parameters.Add("@DaysToNextBirthday", DaysToNextBirthday, DbType.Int32);
sqlCommand = "SELECT ";
sqlCommand += "PersonFirstName, ";
sqlCommand += "PersonFirstName || ' ' || PersonLastName as PersonFullName, ";
//Next Birthday
sqlCommand += "date(PersonDateOfBirth, '+'||";
sqlCommand += "iif(";
//If this is true
sqlCommand += "strftime('%j', 'now') < strftime('%j', PersonDateOfBirth), "; //(current day of year < day of year of birthday)
//Then do this:
sqlCommand += "strftime('%Y', 'now') - strftime('%Y', PersonDateOfBirth), "; //Calc their current age if current day of year < day of year of birthday
//Otherwise, do this:
sqlCommand += "strftime('%Y', 'now') - strftime('%Y', PersonDateOfBirth) + 1"; //Calc their current age if current day of year > day of year of birthday
//Close If statement
sqlCommand += ") ";
sqlCommand += "||' years'";
sqlCommand += ") ";
sqlCommand += "as NextBirthday, ";
//Age Next Birthday
sqlCommand += "iif(";
//If this is true
sqlCommand += "strftime('%j', 'now') < strftime('%j', PersonDateOfBirth), "; //(current day of year < day of year of birthday)
//Then do this:
sqlCommand += "strftime('%Y', 'now') - strftime('%Y', PersonDateOfBirth), "; //Calc their current age if current day of year < day of year of birthday
//Otherwise, do this:
sqlCommand += "strftime('%Y', 'now') - strftime('%Y', PersonDateOfBirth) + 1"; //Calc their current age if current day of year > day of year of birthday
//Close If statement
sqlCommand += ") ";
sqlCommand += "as AgeNextBirthday, ";
// No of days to next birthday goes here
sqlCommand += "CAST ("; //Start of CAST for NextBirthday
sqlCommand += "JULIANDAY(strftime('%Y-%m-%d', ";
//Next Birthday
sqlCommand += "date(PersonDateOfBirth, '+'||";
sqlCommand += "iif(";
//If this is true
sqlCommand += "strftime('%j', 'now') < strftime('%j', PersonDateOfBirth), "; //(current day of year < day of year of birthday)
//Then do this:
sqlCommand += "strftime('%Y', 'now') - strftime('%Y', PersonDateOfBirth), "; //Calc their current age if current day of year < day of year of birthday
//Otherwise, do this:
sqlCommand += "strftime('%Y', 'now') - strftime('%Y', PersonDateOfBirth) + 1"; //Calc their current age if current day of year > day of year of birthday
//Close If statement
sqlCommand += ") ";
sqlCommand += "||' years'";
sqlCommand += ")";
sqlCommand += "))";
sqlCommand += " as INTEGER) "; //End of CAST for Next Birthday
sqlCommand += "- CAST(JULIANDAY(strftime('%Y-%m-%d','now')) as INTEGER) as DaysToNextBirthday, ";
sqlCommand += "PersonSendReminderTo ";
sqlCommand += "FROM Person WHERE DaysToNextBirthday = @DaysToNextBirthday";
using IDbConnection conn = new SQLiteConnection(_configuration.GetConnectionString(connectionId));
{
people = await conn.QueryAsync<Person>(sqlCommand, parameters);
}
return people;
}
}
}
IPersonService.cs
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace SQLiteBirthdayReminders.Data
{
public interface IPersonService
{
Task<bool> PersonInsert(Person person);
Task<IEnumerable<Person>> PersonList();
Task<int> PersonInsertDuplicateCheck(Person person);
Task<int> PersonUpdateDuplicateCheck(Person person);
Task<bool> PersonUpdate(Person person);
Task<Person> PersonGetOne(int PersonID);
Task<bool> PersonDelete(int PersonID);
Task<IEnumerable<Person>> PersonListGetByUser(string @PersonSendReminderTo);
Task<IEnumerable<Person>> PersonGetByDaysToNextBirthday(int DaysToNextBirthday);
}
}
EmailSender.cs
using MailKit.Net.Smtp;
using MailKit.Security;
using Microsoft.Extensions.Configuration;
using MimeKit;
using MimeKit.Text;
using System;
namespace SQLiteBirthdayReminders.Data;
public interface IEmailService
{
bool Send(string to, string subject, string body);
}
public class EmailService : IEmailService
{
private readonly IConfiguration configuration;
public EmailService(IConfiguration Configuration)
{
configuration = Configuration;
}
public bool Send(string to, string subject, string body)
{
//Get settings from appsettings
var SmtpHost = configuration["SmtpHost"];
var SmtpPort = configuration["SmtpPort"];
var SmtpUserFriendlyName = configuration["SmtpUserFriendlyName"];
var SmtpUserEmailAddress = configuration["SmtpUserEmailAddress"];
var SmtpPass = configuration["SmtpPass"];
// create message
var email = new MimeMessage();
email.From.Add(new MailboxAddress(SmtpUserFriendlyName, SmtpUserEmailAddress));
email.To.Add(new MailboxAddress(to, to));
email.Subject = subject;
email.Body = new TextPart(TextFormat.Html) { Text = body };
try
{
// send email
using var smtp = new SmtpClient();
smtp.Connect(SmtpHost, Int32.Parse(SmtpPort), SecureSocketOptions.StartTls);
smtp.Authenticate(SmtpUserEmailAddress, SmtpPass);
smtp.Send(email);
smtp.Disconnect(true);
return true;
}
catch
{
return false;
}
}
}ReminderService.cs
namespace SQLiteBirthdayReminders.Data
{
using Dapper;
using System.Data.SQLite;
using System.Data;
using MailKit.Net.Smtp;
using MailKit.Security;
using Microsoft.Extensions.Configuration;
using MimeKit;
using MimeKit.Text;
public interface IReminderService
{
public void SendReminders() { }
}
public class ReminderService : IReminderService
{
// Database connection
private readonly IConfiguration _configuration;
public ReminderService(IConfiguration configuration)
{
_configuration = configuration;
}
public string connectionId = "Default";
private List<Person>? birthdaypeople;
public async Task SendReminders()
{
string? SendTo;
string? EmailSubject;
string? EmailBody;
for (int daysNotice = 7; daysNotice < 22; daysNotice = daysNotice + 7)
{
var parameters = new DynamicParameters();
parameters.Add("@DaysToNextBirthday", daysNotice, DbType.Int32);
IEnumerable<Person> people;
string? sqlCommand;
sqlCommand = "SELECT ";
sqlCommand += "PersonFirstName, ";
sqlCommand += "PersonFirstName || ' ' || PersonLastName as PersonFullName, ";
//Next Birthday
sqlCommand += "date(PersonDateOfBirth, '+'||";
sqlCommand += "iif(";
//If this is true
sqlCommand += "strftime('%j', 'now') < strftime('%j', PersonDateOfBirth), "; //(current day of year < day of year of birthday)
//Then do this:
sqlCommand += "strftime('%Y', 'now') - strftime('%Y', PersonDateOfBirth), "; //Calc their current age if current day of year < day of year of birthday
//Otherwise, do this:
sqlCommand += "strftime('%Y', 'now') - strftime('%Y', PersonDateOfBirth) + 1"; //Calc their current age if current day of year > day of year of birthday
//Close If statement
sqlCommand += ") ";
sqlCommand += "||' years'";
sqlCommand += ") ";
sqlCommand += "as NextBirthday, ";
//Age Next Birthday
sqlCommand += "iif(";
//If this is true
sqlCommand += "strftime('%j', 'now') < strftime('%j', PersonDateOfBirth), "; //(current day of year < day of year of birthday)
//Then do this:
sqlCommand += "strftime('%Y', 'now') - strftime('%Y', PersonDateOfBirth), "; //Calc their current age if current day of year < day of year of birthday
//Otherwise, do this:
sqlCommand += "strftime('%Y', 'now') - strftime('%Y', PersonDateOfBirth) + 1"; //Calc their current age if current day of year > day of year of birthday
//Close If statement
sqlCommand += ") ";
sqlCommand += "as AgeNextBirthday, ";
// No of days to next birthday goes here
sqlCommand += "CAST ("; //Start of CAST for NextBirthday
sqlCommand += "JULIANDAY(strftime('%Y-%m-%d', ";
//Next Birthday
sqlCommand += "date(PersonDateOfBirth, '+'||";
sqlCommand += "iif(";
//If this is true
sqlCommand += "strftime('%j', 'now') < strftime('%j', PersonDateOfBirth), "; //(current day of year < day of year of birthday)
//Then do this:
sqlCommand += "strftime('%Y', 'now') - strftime('%Y', PersonDateOfBirth), "; //Calc their current age if current day of year < day of year of birthday
//Otherwise, do this:
sqlCommand += "strftime('%Y', 'now') - strftime('%Y', PersonDateOfBirth) + 1"; //Calc their current age if current day of year > day of year of birthday
//Close If statement
sqlCommand += ") ";
sqlCommand += "||' years'";
sqlCommand += ")";
sqlCommand += "))";
sqlCommand += " as INTEGER) "; //End of CAST for Next Birthday
sqlCommand += "- CAST(JULIANDAY(strftime('%Y-%m-%d','now')) as INTEGER) as DaysToNextBirthday, ";
sqlCommand += "PersonSendReminderTo ";
sqlCommand += "FROM Person WHERE DaysToNextBirthday = @DaysToNextBirthday";
using IDbConnection conn = new SQLiteConnection(_configuration.GetConnectionString(connectionId));
{
people = await conn.QueryAsync<Person>(sqlCommand, parameters);
}
birthdaypeople = people.ToList(); //Convert from IEnumerable to List
foreach (var person in birthdaypeople)
{
SendTo = person.PersonSendReminderTo;
EmailSubject = "It's " + person.PersonFullName + "'s Birthday!";
EmailBody = "<p>Hello</p>";
if (daysNotice == 21)
{
EmailBody = EmailBody + "<p>A bit of warning that it's ";
}
else if (daysNotice == 14)
{
EmailBody = EmailBody + "<p>Don't forget, it's ";
}
else
{
EmailBody = EmailBody + "<p>Better get cracking, it's only " + daysNotice + " days until ";
}
EmailBody = EmailBody + person.PersonFirstName + "'s birthday on " + person.NextBirthday.ToString("dd/MM/yyyy") + ".</p>";
if (person.AgeNextBirthday < 21)
{
EmailBody = EmailBody + "<p>" + person.PersonFirstName + " will be " + person.AgeNextBirthday + ".</p>";
}
//EmailBody = EmailBody + "<p>Chris <br/>";
EmailBody = EmailBody + "Chris's Azure Birthday Reminder Service<br/>";
EmailBody = EmailBody + "https://birthdayreminders.azurewebsites.net<p>";
// Get settings from appsettings
var SmtpHost = _configuration["SmtpHost"];
var SmtpPort = _configuration["SmtpPort"];
var SmtpUserFriendlyName = _configuration["SmtpUserFriendlyName"];
var SmtpUserEmailAddress = _configuration["SmtpUserEmailAddress"];
var SmtpPass = _configuration["SmtpPass"];
// create message
var email = new MimeMessage();
email.From.Add(new MailboxAddress(SmtpUserFriendlyName, SmtpUserEmailAddress));
email.To.Add(new MailboxAddress(SendTo, SendTo));
email.Subject = EmailSubject;
email.Body = new TextPart(TextFormat.Html) { Text = EmailBody };
// send email
using var smtp = new SmtpClient();
{
smtp.Connect(SmtpHost, Int32.Parse(SmtpPort), SecureSocketOptions.StartTls);
smtp.Authenticate(SmtpUserEmailAddress, SmtpPass);
smtp.Send(email);
smtp.Disconnect(true);
smtp.Dispose();
}
}
}
}
}
}
Pages Folder
_Layout.cshtml
@using Microsoft.AspNetCore.Components.Web
@namespace SQLiteBirthdayReminders.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<base href="~/" />
<link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
<link href="css/site.css" rel="stylesheet" />
<link href="SQLiteBirthdayReminders.styles.css" rel="stylesheet" />
<link href="_content/Syncfusion.Blazor.Themes/bootstrap5.css" rel="stylesheet" />
<script src="_content/Syncfusion.Blazor.Core/scripts/syncfusion-blazor.min.js" type="text/javascript"></script>
<component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" />
</head>
<body>
@RenderBody()
<div id="blazor-error-ui">
<environment include="Staging,Production">
An error has occurred. This application may no longer respond until reloaded.
</environment>
<environment include="Development">
An unhandled exception has occurred. See browser dev tools for details.
</environment>
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
<script src="_framework/blazor.server.js"></script>
</body>
</html>
EmailTestPage.razor
@page "/manualreminders/"
@using MailKit.Net.Smtp;
@using MailKit.Security;
@using MimeKit;
@using MimeKit.Text;
@inject IEmailService EmailService
@inject IPersonService PersonService
<h3>Manual Birthday Reminder Emails</h3>
<hr />
<div>
<p>By clicking the button the system will look for forthcoming
birthdays and will send reminder emails.</p>
</div>
<div class="button-container">
<button type="button" class="e-btn e-normal e-primary" @onclick="@SendEmail">Send Birthday Reminder Emails</button>
</div>
<div class="col-lg-12 control-section toast-default-section">
<SfToast @ref="ToastObj" Timeout="4000">
<ToastPosition X="Right" Y="Top"></ToastPosition>
</SfToast>
</div>
<style>
@@font-face {
font-family: 'Toast_icons';
src: url(data:application/x-font-ttf;charset=utf-8;base64,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) format('truetype');
font-weight: normal;
font-style: normal;
}
#toast_types button {
margin: 5px;
min-width: 160px;
max-width: 160px;
}
.toast-icons {
font-family: 'Toast_icons' !important;
speak: none;
font-size: 55px;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
#toast_type .e-toast-icon.e-icons {
height: auto;
font-size: 30px;
}
.toast-icons.e-success::before {
content: "\e701";
}
.toast-icons.e-error::before {
content: "\e700";
}
.toast-icons.e-info::before {
content: "\e704";
}
.toast-icons.e-warning::before {
content: "\e703";
}
#toast_types {
text-align: center;
}
</style>
@code {
public MimeMessage email = new MimeMessage();
private IEnumerable<Person>? people;
private List<Person>? birthdaypeople;
SfToast ToastObj;
bool SomeoneWithBirthdayFound = false;
private List<ToastModel> Toast = new List<ToastModel>
{
new ToastModel{ Title = "Warning!", Content="There was a problem with your network connection.", CssClass="e-toast-warning", Icon="e-warning toast-icons" },
new ToastModel{ Title = "Success!", Content="Your message has been sent successfully.", CssClass="e-toast-success", Icon="e-success toast-icons" },
new ToastModel{ Title = "Error!", Content="A problem has been occurred while submitting your email", CssClass="e-toast-danger", Icon="e-error toast-icons" },
new ToastModel{ Title = "Information!", Content="No one with an upcoming birthday was found.", CssClass="e-toast-info", Icon="e-info toast-icons" }
};
public async Task SendEmail()
{
string? SendTo;
string? EmailSubject;
string? EmailBody;
for(int daysNotice = 7; daysNotice < 22; daysNotice = daysNotice + 7)
{
people = await PersonService.PersonGetByDaysToNextBirthday(daysNotice);
birthdaypeople = people.ToList(); //Convert from IEnumerable to List
foreach(var person in birthdaypeople)
{
SomeoneWithBirthdayFound = true;
SendTo = person.PersonSendReminderTo;
EmailSubject = "It's " + person.PersonFullName + "'s Birthday!";
EmailBody = "<p>Hello</p>";
if(daysNotice == 21)
{
EmailBody = EmailBody + "<p>A bit of warning that it's ";
}
else if(daysNotice == 14)
{
EmailBody = EmailBody + "<p>Don't forget, it's ";
}
else
{
EmailBody = EmailBody + "<p>Better get cracking, it's only " + daysNotice + " days until ";
}
EmailBody = EmailBody + person.PersonFirstName + "'s birthday on " + person.NextBirthday.ToString("dd/MM/yyyy") + ".</p>";
if (person.AgeNextBirthday < 21)
{
EmailBody = EmailBody + "<p>" + person.PersonFirstName + " will be " + person.AgeNextBirthday + ".</p>";
}
//EmailBody = EmailBody + "<p>Chris <br/>";
EmailBody = EmailBody + "Chris's Azure Birthday Reminder Service<br/>";
EmailBody = EmailBody + "https://birthdayreminders.azurewebsites.net<p>";
if (EmailService.Send(SendTo, EmailSubject, EmailBody))
{
//do something
await this.ToastObj.Show(Toast[1]);
}
else
{
//do something else
await this.ToastObj.Show(Toast[2]);
};
}
}
if(!SomeoneWithBirthdayFound)
{
await this.ToastObj.Show(Toast[3]);
}
}
}Index.razor
@page "/"
<PageTitle>Birthday Reminders</PageTitle>
@using SQLiteBirthdayReminders.Data
@inject IPersonService PersonService
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.Identity.Client
@using System
@using System.Collections.Generic
@using System.Security.Claims
@inject AuthenticationStateProvider AuthenticationStateProvider
<div class="col-sm-12">
<h3>Birthday Reminders</h3>
<br />
<SfGrid DataSource="@people"
Toolbar="Toolbaritems"
AllowSorting="true">
<GridEvents RowSelected="RowSelectHandler" OnToolbarClick="ToolbarClickHandler" TValue="Person"></GridEvents>
<GridColumns>
<GridColumn Field="@nameof(Person.NextBirthday)"
HeaderText="Next Birthday"
Format="d"
Type="ColumnType.Date"
TextAlign="@TextAlign.Center"
Width="20">
</GridColumn>
<GridColumn Field="@nameof(Person.PersonFirstName)"
HeaderText="First Name"
TextAlign="@TextAlign.Left"
Width="30">
</GridColumn>
<GridColumn Field="@nameof(Person.PersonLastName)"
HeaderText="Last Name"
TextAlign="@TextAlign.Left"
Width="30">
</GridColumn>
<GridColumn Field="@nameof(Person.AgeNextBirthday)"
HeaderText="Age Next Birthday"
TextAlign="@TextAlign.Right"
Width="20">
</GridColumn>
<GridColumn Field="@nameof(Person.PersonDateOfBirth)"
HeaderText="Date of Birth"
Format="d"
Type="ColumnType.Date"
TextAlign="@TextAlign.Center"
Width="60">
</GridColumn>
</GridColumns>
</SfGrid>
</div>
<SfDialog @ref="DialogAddEditPerson" IsModal="true" Width="500px" ShowCloseIcon="true" Visible="false">
<DialogTemplates>
<Header> @HeaderText </Header>
</DialogTemplates>
<EditForm Model="@personaddedit" OnValidSubmit="@PersonSave">
<DataAnnotationsValidator/>
<div>
<SfTextBox Enabled="true" Placeholder="First Name"
FloatLabelType="@FloatLabelType.Always"
@bind-Value="personaddedit.PersonFirstName">
</SfTextBox>
<ValidationMessage For="@(() => personaddedit.PersonFirstName)" />
<SfTextBox Enabled="true" Placeholder="Last Name"
FloatLabelType="@FloatLabelType.Always"
@bind-Value="personaddedit.PersonLastName">
</SfTextBox>
<ValidationMessage For="@(() => personaddedit.PersonLastName)" />
<SfDatePicker TValue="DateTime"
Placeholder='Date of Birth'
FloatLabelType="@FloatLabelType.Auto"
@bind-Value="personaddedit.PersonDateOfBirth">
</SfDatePicker>
<ValidationMessage For="@(() => personaddedit.PersonDateOfBirth)" />
<SfTextBox Enabled="false" Placeholder="Send Reminders to:"
FloatLabelType="@FloatLabelType.Always"
@bind-Value="personaddedit.PersonSendReminderTo">
</SfTextBox>
<ValidationMessage For="@(() => personaddedit.PersonSendReminderTo)" />
</div>
<br /><br />
<div class="e-footer-content">
<div class="button-container">
<button type="submit" class="e-btn e-normal e-primary">Save</button>
<button type="button" class="e-btn e-normal" @onclick="@CloseDialog">Cancel</button>
</div>
</div>
</EditForm>
</SfDialog>
<SfDialog @ref="DialogDeletePerson" IsModal="true" Width="500px" ShowCloseIcon="true" Visible="false">
<DialogTemplates>
<Header> Confirm Delete </Header>
<Content>
<SfTextBox Enabled="false" Placeholder="First Name"
FloatLabelType="@FloatLabelType.Always"
@bind-Value="personaddedit.PersonFirstName"></SfTextBox>
<SfTextBox Enabled="false" Placeholder="Last Name"
FloatLabelType="@FloatLabelType.Always"
@bind-Value="personaddedit.PersonLastName"></SfTextBox>
<br />
<br />
<span class="text-danger">Please confirm that you want to delete this record</span>
</Content>
</DialogTemplates>
<DialogButtons>
<DialogButton Content="Delete" IsPrimary="true" OnClick="@ConfirmDeleteYes" />
<DialogButton Content="Cancel" IsPrimary="false" OnClick="@ConfirmDeleteNo" />
</DialogButtons>
</SfDialog>
<WarningPage @ref="Warning" WarningHeaderMessage="@WarningHeaderMessage" WarningContentMessage="@WarningContentMessage" />
@code {
// Create an empty list, named people, of empty Person objects.
IEnumerable<Person>? people;
private List<ItemModel> Toolbaritems = new List<ItemModel>();
SfDialog DialogAddEditPerson;
Person personaddedit = new Person();
string HeaderText = "";
private string? UserEmail;
[CascadingParameter]
private Task<AuthenticationState>? authState { get; set; }
private ClaimsPrincipal? principal;
WarningPage Warning;
string WarningHeaderMessage = "";
string WarningContentMessage = "";
public int SelectedPersonId { get; set; } = 0;
SfDialog DialogDeletePerson;
protected override async Task OnInitializedAsync()
{
if (authState != null)
{
principal = (await authState).User;
}
foreach (Claim claim in principal.Claims)
{
//claimtype = claimtype + "Claim Type: " + claim.Type + "; CLAIM VALUE: " + claim.Value + "</br>";
if(claim.Type == "emails")
{
UserEmail = claim.Value;
}
}
//Populate the list of Person objects from the Person table.
//people = await PersonService.PersonList();
people = await PersonService.PersonListGetByUser(UserEmail);
Toolbaritems.Add(new ItemModel() { Text = "Add", TooltipText = "Add a new person", PrefixIcon = "e-add" });
Toolbaritems.Add(new ItemModel() { Text = "Edit", TooltipText = "Edit selected person", PrefixIcon = "e-edit" });
Toolbaritems.Add(new ItemModel() { Text = "Delete", TooltipText = "Delete selected person", PrefixIcon = "e-delete" });
}
public async Task ToolbarClickHandler(Syncfusion.Blazor.Navigations.ClickEventArgs args)
{
if (args.Item.Text == "Add")
{
//Code for adding goes here
personaddedit = new Person(); // Ensures a blank form when adding
HeaderText = "Add a Contact";
await this.DialogAddEditPerson.Show();
personaddedit.PersonDateOfBirth = new DateTime(2000, 12, 31);
personaddedit.PersonSendReminderTo = UserEmail;
}
if (args.Item.Text == "Edit")
{
//Code for editing
//Check that a Person has been selected
if (SelectedPersonId == 0)
{
WarningHeaderMessage = "Warning!";
WarningContentMessage = "Please select a Contact from the grid.";
Warning.OpenDialog();
}
else
{
//populate personaddedit (temporary data set used for the editing process)
HeaderText = "Edit Contact";
personaddedit = await PersonService.PersonGetOne(SelectedPersonId);
await this.DialogAddEditPerson.Show();
}
}
if (args.Item.Text == "Delete")
{
//Code for deleting
if (SelectedPersonId == 0)
{
WarningHeaderMessage = "Warning!";
WarningContentMessage = "Please select a Contact from the grid.";
Warning.OpenDialog();
}
else
{
//populate personaddedit (temporary data set used for the editing process)
HeaderText = "Delete Contact";
personaddedit = await PersonService.PersonGetOne(SelectedPersonId);
await this.DialogDeletePerson.Show();
}
}
}
protected async Task PersonSave()
{
//In all cases check the reasonableness of the date of birth
//Make sure it's not in the future
if(personaddedit.PersonDateOfBirth> DateTime.Now)
{
WarningHeaderMessage = "Warning!";
WarningContentMessage = $"It looks like the Date of Birth is wrong. It's in the future!";
Warning.OpenDialog();
return;
}
//Check whether they are more than, say, 105 years old...
DateTime zeroTime = new DateTime(1, 1, 1);
TimeSpan span = DateTime.Today - personaddedit.PersonDateOfBirth;
// Because we start at year 1 for the Gregorian calendar, we must subtract a year here.
// We need to add zeroTime because span is just a number of days (i.e. not date format)
int years = (zeroTime + span).Year - 1;
//double ageInDays = span.TotalDays;
//int ageInYears = Convert.ToInt32(ageInDays/365.25);
if(years > 105)
{
WarningHeaderMessage = "Warning!";
WarningContentMessage = $"It looks like the Date of Birth is wrong. They would be { years } old!";
Warning.OpenDialog();
return;
}
if (personaddedit.PersonID == 0)
{
//Check for duplicates
int duplicates = await PersonService.PersonInsertDuplicateCheck(personaddedit);
if(duplicates > 0)
{
//Person already exists - warn the user
WarningHeaderMessage = "Warning!";
WarningContentMessage = "This Person already exists; they cannot be added again.";
Warning.OpenDialog();
return;
}
bool Success = await PersonService.PersonInsert(personaddedit);
if (Success != true)
{
//Person already exists - warn the user
WarningHeaderMessage = "Warning!";
WarningContentMessage = "Unexpected ERROR...";
Warning.OpenDialog();
}
else
{
//Refresh datagrid
//people = await PersonService.PersonList();
people = await PersonService.PersonListGetByUser(UserEmail);
StateHasChanged();
// Ensures a blank form for adding a new record
personaddedit = new Person();
//Adds defaults for a new record
personaddedit.PersonDateOfBirth = new DateTime(2000, 12, 31);
personaddedit.PersonSendReminderTo = UserEmail;
}
}
else
{
// Item is being edited
//Check for duplicates
int duplicates = await PersonService.PersonUpdateDuplicateCheck(personaddedit);
if(duplicates > 0)
{
//Person already exists - warn the user
WarningHeaderMessage = "Warning!";
WarningContentMessage = "This Person already exists; they cannot be added again.";
Warning.OpenDialog();
return;
}
bool Success = await PersonService.PersonUpdate(personaddedit);
if (Success != true)
{
//Person already exists
WarningHeaderMessage = "Warning!";
WarningContentMessage = "Unexpected ERROR...";
Warning.OpenDialog();
// Data is left in the dialog so the user can see the problem.
}
else
{
//Refresh datagrid
//people = await PersonService.PersonList();
people = await PersonService.PersonListGetByUser(UserEmail);
StateHasChanged();
await CloseDialog();
personaddedit = new Person();
SelectedPersonId = 0;
}
}
}
private async Task CloseDialog()
{
await this.DialogAddEditPerson.Hide();
//Refresh datagrid
//people = await PersonService.PersonList();
people = await PersonService.PersonListGetByUser(UserEmail);
StateHasChanged();
}
public void RowSelectHandler(RowSelectEventArgs<Person> args)
{
//{args.Data} returns the current selected records.
SelectedPersonId = args.Data.PersonID;
}
public async void ConfirmDeleteNo()
{
await DialogDeletePerson.Hide();
SelectedPersonId = 0;
}
public async void ConfirmDeleteYes()
{
bool Success = await PersonService.PersonDelete(SelectedPersonId);
if (Success == false)
{
WarningHeaderMessage = "Warning!";
WarningContentMessage = "Unknown error has occurred - the record has not been deleted!";
Warning.OpenDialog();
}
else
{
await this.DialogDeletePerson.Hide();
//people = await PersonService.PersonList();
people = await PersonService.PersonListGetByUser(UserEmail);
this.StateHasChanged();
//personaddedit = new Person();
SelectedPersonId = 0;
}
}
}WarningPage.razor
@using Syncfusion.Blazor.Popups;
<SfDialog @ref="DialogWarning" @bind-Visible="@IsVisible" IsModal="true" Width="300px" ShowCloseIcon="true">
<DialogTemplates>
<Header> @WarningHeaderMessage </Header>
<Content>@WarningContentMessage</Content>
</DialogTemplates>
<DialogButtons>
<DialogButton Content="OK" IsPrimary="true" OnClick="@CloseDialog" />
</DialogButtons>
</SfDialog>
@code {
SfDialog? DialogWarning;
public bool IsVisible { get; set; } = false;
[Parameter] public string? WarningHeaderMessage { get; set; }
[Parameter] public string? WarningContentMessage { get; set; }
public void OpenDialog()
{
this.IsVisible = true;
this.StateHasChanged();
}
public void CloseDialog()
{
this.IsVisible = false;
this.StateHasChanged();
}
}Shared Folder
NavMenu.razor
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.Identity.Client
@using System
@using System.Collections.Generic
@using System.Security.Claims
@inject AuthenticationStateProvider AuthenticationStateProvider
<div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="">Birthday Reminders</a>
<button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu">
<span class="navbar-toggler-icon"></span>
</button>
</div>
</div>
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
<nav class="flex-column">
<div class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
</div>
@if (UserEmail == "christopherbell@blazorcode.uk" || UserEmail == "anotherperson@anotherdomain.com")
{
<div class="nav-item px-3">
<NavLink class="nav-link" href="manualreminders">
<span class="oi oi-list-rich" aria-hidden="true"></span> Manual Emails
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="dashboard">
<span class="oi oi-list-rich" aria-hidden="true"></span> Hangfire Dashboard
</NavLink>
</div>
}
</nav>
</div>
@code {
private bool collapseNavMenu = true;
private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null;
private string? UserEmail;
[CascadingParameter]
private Task<AuthenticationState>? authState { get; set; }
private ClaimsPrincipal? principal;
private void ToggleNavMenu()
{
collapseNavMenu = !collapseNavMenu;
}
protected override async Task OnInitializedAsync()
{
if (authState != null)
{
principal = (await authState).User;
}
foreach (Claim claim in principal.Claims)
{
if(claim.Type == "emails")
{
UserEmail = claim.Value;
}
}
}
}
_Imports.razor
@using System.Net.Http
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using SQLiteBirthdayReminders
@using SQLiteBirthdayReminders.Shared
@using Syncfusion.Blazor
@using Syncfusion.Blazor.Inputs
@using Syncfusion.Blazor.Popups
@using Syncfusion.Blazor.Data
@using Syncfusion.Blazor.DropDowns
@using Syncfusion.Blazor.Calendars
@using Syncfusion.Blazor.Navigations
@using Syncfusion.Blazor.Lists
@using Syncfusion.Blazor.Grids
@using Syncfusion.Blazor.Buttons
@using Syncfusion.Blazor.Notifications
@using SQLiteBirthdayReminders.DataProgram.cs
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.UI;
using SQLiteBirthdayReminders.Data;
using Syncfusion.Blazor;
using Hangfire;
using Hangfire.Storage.SQLite;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"));
builder.Services.AddControllersWithViews()
.AddMicrosoftIdentityUI();
builder.Services.AddAuthorization(options =>
{
// By default, all incoming requests will be authorized according to the default policy
options.FallbackPolicy = options.DefaultPolicy;
});
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor()
.AddMicrosoftIdentityConsentHandler();
builder.Services.AddScoped<IPersonService, PersonService>();
builder.Services.AddTransient<IEmailService, EmailService>();
builder.Services.AddSingleton<IReminderService, ReminderService>();
builder.Services.AddSyncfusionBlazor(options => { options.IgnoreScriptIsolation = true; });
builder.Services.AddHangfire(configuration => configuration
.UseSimpleAssemblyNameTypeSerializer()
.UseRecommendedSerializerSettings()
.UseSQLiteStorage());
builder.Services.AddHangfireServer();
builder.Services.AddLocalization();
var app = builder.Build();
//Register Syncfusion license
var SyncfusionLicenceKey = builder.Configuration["SyncfusionLicenceKey"];
Syncfusion.Licensing.SyncfusionLicenseProvider.RegisterLicense(SyncfusionLicenceKey);
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseRequestLocalization("en-GB");
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.UseHangfireDashboard("/dashboard");
IReminderService ReminderService = app.Services.GetRequiredService<IReminderService>(); //Needed for items below
RecurringJob.AddOrUpdate(
"Daily Birthday Reminder", () => ReminderService.SendReminders(),
"35 03 * * *", TimeZoneInfo.Utc);
app.Run();
appsetting.json
This is a generic example, substitute your own settings.
{
/*
The following identity settings need to be configured
before the project can be successfully executed.
For more info see https://aka.ms/dotnet-template-ms-identity-platform
*/
"AzureAd": {
"Instance": "https://domain.b2clogin.com/tfp/",
"Domain": "qualified.domain.onmicrosoft.com",
"TenantId": "22222222-2222-2222-2222-222222222222",
"ClientId": "11111111-1111-1111-11111111111111111",
"CallbackPath": "/signin-oidc"
"SignUpSignInPolicyId": "domainname"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"Default": "Data Source=.\\Data\\SQLiteBirthdays.db;Version=3;"
},
"SyncfusionLicenceKey": "LicenceKey",
"SmtpHost": "smtp-host",
"SmtpPort": 587,
"SmtpUserFriendlyName": "John Smith", //E.g John Smith
"SmtpUserEmailAddress": "johnsmith@outlook.com",
"SmtpPass": "Password123"
}