Email and Test Page

Email Test Page

This should now be fairly straightforward.

Add a new class in the Data folder, called EMailSender.cs and paste in the following, overwriting any template code.  With the exception of the 'namespace' this is exactly the same as in the SQL Server version of the project

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;
        }

    }
}

Add a new page, called EmailTestPage.razor and paste in the following, again overwriting any existing code.  Similarly, this is exactly the same code as used in the SQL Server project

@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,AAEAAAAKAIAAAwAgT1MvMj0gSRkAAAEoAAAAVmNtYXDnM+eRAAABsAAAAEpnbHlmzVnmlwAAAhgAAAZAaGVhZBEYIl8AAADQAAAANmhoZWEHlgN3AAAArAAAACRobXR4LvgAAAAAAYAAAAAwbG9jYQnUCGIAAAH8AAAAGm1heHABHQBcAAABCAAAACBuYW1lfUUTYwAACFgAAAKpcG9zdAxfTDgAAAsEAAAAggABAAADUv9qAFoEAAAAAAAD6AABAAAAAAAAAAAAAAAAAAAADAABAAAAAQAACcU5MF8PPPUACwPoAAAAANcI7skAAAAA1wjuyQAAAAAD6APoAAAACAACAAAAAAAAAAEAAAAMAFAABwAAAAAAAgAAAAoACgAAAP8AAAAAAAAAAQPqAZAABQAAAnoCvAAAAIwCegK8AAAB4AAxAQIAAAIABQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUGZFZABA5wDnCgNS/2oAWgPoAJYAAAABAAAAAAAABAAAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAAAAAAgAAAAMAAAAUAAMAAQAAABQABAA2AAAABAAEAAEAAOcK//8AAOcA//8AAAABAAQAAAABAAIAAwAEAAUABgAHAAgACQAKAAsAAAAAAAAAQgB8AMIA4gEcAZQCBgJwAo4DAAMgAAAAAwAAAAADlAOUAAsAFwAjAAABFwcXNxc3JzcnBycFDgEHLgEnPgE3HgEFHgEXPgE3LgEnDgEBTXh4L3h4L3h4L3h4AbwDt4qKtwMDt4qKt/0eBeuxsesFBeuxsesCbHh4L3h4L3h4L3h4p4q3AwO3ioq3AwO3irHrBQXrsbHrBQXrAAAAAwAAAAADlAOUAAUAEQAdAAABJwcXAScXDgEHLgEnPgE3HgEFHgEXPgE3LgEnDgEBr2UylwEbMqADt4qKtwMDt4qKt/0eBeuxsesFBeuxsesBrGQylgEcMqKKtwMDt4qKtwMDt4qx6wUF67Gx6wUF6wAAAAAFAAAAAAOUA5cABQARAB0AIQAlAAABFzcnNSMFDgEHLgEnPgE3HgEFHgEXPgE3LgEnDgElFzcnBRc3JwHKxiCnPwFOA6V8fKUDA6V8fKX9aATToJ/UBATUn5/UAh7ANsD9fja/NQGedzNj29F8pAMDpHx8pQMDpXyf1AQE1J+g0wQE0/GhQKGhQKFAAAQAAAAAA74DfgADAAcACgANAAAlMzUjNTM1IwEhCQEhAQHLUlJSUgFj/YwBOv42A5T+NuZUUqf+igIc/ZADFgAEAAAAAAOUA5QAAwAHABMAHwAAATM1IzUzNSMFDgEHLgEnPgE3HgEFHgEXPgE3LgEnDgEBylRUVFQBbgO3ioq3AwO3ioq3/R4F67Gx6wUF67Gx6wEk+lNT0Iq3AwO3ioq3AwO3irHrBQXrsbHrBQXrAAAAAAcAAAAAA+gDMQALABUAJQAuADcAQQBLAAABFhcVITUmJz4BMxYFFhcVITU+ATcWJQYHFSE1LgEjIgYHLgEjIgEWFAYiJjQ2MgUWFAYiJjQ2MiUGFBYXPgE0JiIFBhQWFz4BNCYiA1xEBP6sAxUeRiRX/qxEBP45BIlXV/7xZQsD6AvKUypvMzNvKlMCKxozTTMzTP6CGTNMNDRMAQItWUREWlqI/jstWkREWVmIAWMbFjc3IBgKDwQcGxY3NxY3BAQjJUt7e0tKFxgYFwEMGU01NU0zGhlNNTVNMxYthloCAlqGWy4thloCAlqGWwAAAAQAAAAAA5wCxwAIABQANABFAAABFBYyNjQmIgYXDgEHLgEnPgE3HgEfAQcOAQ8BNz4BNS4BJw4BBxQWHwEnLgEvATc+ATc2FiUOAQ8BFx4BNz4BPwEnJiciAb8fLR4eLR+wAkU0NEUBAUU0NEX8BgEemG0FBB8kAlZBQFcBKyUCCkeVTAYBH76RVMP+3bDPBwcKZclcu/AGCwrM2AoBxxYfHy0eHhc0RQEBRTQ1RQEBRSgEARpWGAECFUIoQVcCAldBLEYUAQEIQkAGASJsBwFCoRbFFAoJW0sBCo8LCgztAQAAAAIAAAAAA4ADbAA4AEEAAAEEJCcmDgEWFx4BHwEVFAYHDgEnJg4BFhcWNjc2Fx4BBx4BFzc+ASc2JicmJzUzPgE3PgEnJicjIiUUFjI2NCYiBgNM/tz+pwwMGxEDDAaMfAcSETKEQw8WBg8Og80hNSg4JwICEw0FDhECAjFJEBICPYhKDQgGChQCB/5dMUgxMUgxAuB/ZRcIAxgbCQdHEQGTGi8TOVgKAw8dFwMNuDUFHTGDCA0QAQECFQ8Mnz8LCasJKiUHGg0SATMkMDBJMDAAAAAAAgAAAAAC/QMkAAMADQAAAQchJxMeATMhMjY3EyEC2x3+bB0kBCQZAQQZJARH/ewDBuDg/fcZICAZAicAAwAAAAACzwPoACwAQwBPAAABERQfARYfAzMVHgE7ATI2NRE0JisBNTEWOwEyNjQmJyMiJi8BLgErAQ4BAxUzNTQ2NzMeARcVMzUuAScjIgcjESM1HgEXPgE3LgEnDgEBVQEBAwQCCAjXARENOg0REQ2zDROVExoaE2UQGAQfAxAKYg0RPR8RDZcNEQEeASIalxANAR8CTTo6TQEBTTo6TQJ8/nYEBQIGBAIFArYNERENARENEUoNGicZARMPfQoNARH98Hl5DREBARENeXkaIgEIAe3FOk0CAk06Ok0BAU0AAAAAAgAAAAAC5gMyAAkAEQAAJRQWMyEyNjURITcjFSE1IycjASApHgEaHin+WFBuAeR+JLD8HigoHgGfeT09HgAAAAAAEgDeAAEAAAAAAAAAAQAAAAEAAAAAAAEAEgABAAEAAAAAAAIABwATAAEAAAAAAAMAEgAaAAEAAAAAAAQAEgAsAAEAAAAAAAUACwA+AAEAAAAAAAYAEgBJAAEAAAAAAAoALABbAAEAAAAAAAsAEgCHAAMAAQQJAAAAAgCZAAMAAQQJAAEAJACbAAMAAQQJAAIADgC/AAMAAQQJAAMAJADNAAMAAQQJAAQAJADxAAMAAQQJAAUAFgEVAAMAAQQJAAYAJAErAAMAAQQJAAoAWAFPAAMAAQQJAAsAJAGnIEZpbmFsIFRvYXN0IE1ldHJvcFJlZ3VsYXJGaW5hbCBUb2FzdCBNZXRyb3BGaW5hbCBUb2FzdCBNZXRyb3BWZXJzaW9uIDEuMEZpbmFsIFRvYXN0IE1ldHJvcEZvbnQgZ2VuZXJhdGVkIHVzaW5nIFN5bmNmdXNpb24gTWV0cm8gU3R1ZGlvd3d3LnN5bmNmdXNpb24uY29tACAARgBpAG4AYQBsACAAVABvAGEAcwB0ACAATQBlAHQAcgBvAHAAUgBlAGcAdQBsAGEAcgBGAGkAbgBhAGwAIABUAG8AYQBzAHQAIABNAGUAdAByAG8AcABGAGkAbgBhAGwAIABUAG8AYQBzAHQAIABNAGUAdAByAG8AcABWAGUAcgBzAGkAbwBuACAAMQAuADAARgBpAG4AYQBsACAAVABvAGEAcwB0ACAATQBlAHQAcgBvAHAARgBvAG4AdAAgAGcAZQBuAGUAcgBhAHQAZQBkACAAdQBzAGkAbgBnACAAUwB5AG4AYwBmAHUAcwBpAG8AbgAgAE0AZQB0AHIAbwAgAFMAdAB1AGQAaQBvAHcAdwB3AC4AcwB5AG4AYwBmAHUAcwBpAG8AbgAuAGMAbwBtAAAAAAIAAAAAAAAACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAECAQMBBAEFAQYBBwEIAQkBCgELAQwBDQAFRXJyb3IHU3VjY2VzcwVBbGFybQdXYXJuaW5nBEluZm8HTWVldGluZwVCbGluawdTdHJldGNoA1NpcANTaXQFVHJhc2gAAAAA) 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]); 
        }
    }

}

Amend NavMenu to include the new page and remove the Counter and FetchData pages.

@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;
            }
        } 
    }
}

Add the following in Program.cs

builder.Services.AddSingleton<IEmailService, EmailService>();

And add the following to _Imports.cs

@using SQLiteBirthdayReminders.Data

Hangfire

We have already installed the following Hangfire packages using NuGet Package Manager:

  • Hangfire.AspNetCore
  • Hangfire,Core
  • Hangfire.Storage.SQLite

To complete the installation we need to add the following to the top of Program.cs

using Hangfire;
using Hangfire.Storage.SQLite;

Add the following to the builder section, it probably doesn't matter where but I suggest with the other similar statements (we'll add the ReminderService shortly):

builder.Services.AddSingleton<IReminderService, ReminderService>();

(As the ReminderService hasn't been added yet, so this will display errors).  Further down the Builder section add this

builder.Services.AddHangfire(configuration => configuration
            .UseSimpleAssemblyNameTypeSerializer()
            .UseRecommendedSerializerSettings()           
            .UseSQLiteStorage());

builder.Services.AddHangfireServer();

Lastly (in Program.cs) add the following just before "app.Run();"

app.UseHangfireDashboard("/dashboard");

IReminderService ReminderService = app.Services.GetRequiredService<IReminderService>();      //Needed for items below
RecurringJob.AddOrUpdate(
    "Daily Birthday Reminder", () => ReminderService.SendReminders(),
    "35 03 * * *", TimeZoneInfo.Utc);

Reminder Service

In the Data folder add a new class, calling it 'ReminderService.cs' and replace all the code with the following.  This is copied exactly from the original Birthday Reminders project:

namespace BlazorBirthdayReminders.Data
{
    using Dapper;
    using Microsoft.Data.SqlClient;
    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;
                using IDbConnection conn = new SqlConnection(_configuration.GetConnectionString(connectionId));
                {
                    people = await conn.QueryAsync<Person>("spPerson_GetByDaysToNextBirthday", parameters, commandType: CommandType.StoredProcedure);
                }

                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();
                    }
                }
            }
        }
    }
}

Make the following changes:

  • Change 'namespace BlazorBirthdayReminders.Data' to
    • 'SQLiteBirthdayReminders.Data'
  • Change 'using Microsoft.Data.SqlClient;' to:
    • 'using System.Data.SQLite;'
  • Change 'using IDbConnection conn = new SqlConnection(...' to:
    • 'using IDbConnection conn = new SQLiteConnection(...'

However, the biggest change is caused by the fact that we can't use a stored procedure and must therefore construct the SQL statement we need in code.  This is the same as we used in PersonService, so can be copied and pasted into ReminderService.  Insert the following after 'IEnumerablepeople;'

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";

And change the parameter line to get people with:

people = await conn.QueryAsync<Person>(sqlCommand, parameters);

There must be a better way than the above; I am repeating code that surely I only need once - but I seem to have a complete blind spot and cannot find how to reuse code!

Housekeeping

We should get rid of all the code we don't need.  Delete the following files using Solution Explorer.

  • WeatherForecast.cs
  • WeatherForecastService.cs
  • Counter.Razor
  • FetchData.razor

In Program.cs, delete the following line:

builder.Services.AddSingleton<WeatherForecastService>();

Hangfire Again

Save all the files and run.

The first time the application is run a SQLite database file called 'hangfire.db is created in the root directory.  In the original SQL Server version of the project I incorporated the Hangfire tables in the Birthdays SQL database.  I contemplated doing the same with SQLIte, but as the system seems to be working fine, and there is no financial incentive to keeping the number of database files to a minimum, I decided to leave as is.

I also considered whether I needed to change the properties of hangfire.db to change its Properties to: 'Build Action' to 'Content' and 'Copy to Output Directory' to 'Copy if newer', but decided that is probably unnecessary as the file will be created anyway.  The downside is that I probably won't be able to monitor the Hangfire Dashboard for the deployed application; hopefully it will work.