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

@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.Data

Program.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"
}