Step @Step - Database Storage for @AppState.SiteName
+
+
+ Let's configure where the data for your website is going to be stored. This is important because this is where all of our data, including your blog posts and page information, will be securely stored and retrieved.
+
+
+
+ Currently, we only support Postgres as a database. This is a very popular and powerful database that is used by many websites. It is also very easy to use and has a lot of great features.
+ Sharp site is going to create two database instances on your server: one that contains the data for the content served by SharpSite and a second that contains these security information that SharpSite uses
+ to authenticate users and manage permissions. This is a very important step, so please make sure that you have the correct information before continuing.
+
+
+
+ Let's configure the database connection string. This is a string that tells SharpSite how to connect to your database. You can get this from your hosting provider or from your local database installation.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+@code {
+
+ const int Step = 3;
+
+ protected override async Task OnInitializedAsync()
+ {
+ if (AppState.StartupCompleted) NavManager.NavigateTo("/", true);
+ if (AppState.StartupStep != Step) NavManager.NavigateTo($"/start/step{AppState.StartupStep}", false);
+
+ await base.OnInitializedAsync();
+ }
+
+ private DatabaseConfigModel DatabaseConfig { get; set; } = new();
+
+ private void SaveDatabaseConfig()
+ {
+ // Logic to save the database configuration
+ Console.WriteLine($"Server: {DatabaseConfig.ServerName}, User: {DatabaseConfig.UserId}, Password: {DatabaseConfig.Password}");
+ }
+
+ private class DatabaseConfigModel
+ {
+ [Required()]
+ public string ServerName { get; set; } = string.Empty;
+
+ [Required()]
+ public string UserId { get; set; } = string.Empty;
+
+ [Required()]
+ public string Password { get; set; } = string.Empty;
+ }
+
+}
\ No newline at end of file
From 149cdb4b311ffe8ffa3023827c5e772eea0a94e5 Mon Sep 17 00:00:00 2001
From: "Jeffrey T. Fritz"
Date: Thu, 3 Apr 2025 10:58:34 -0400
Subject: [PATCH 2/6] Added copilot instructions
---
.github/copilot-instructions.md | 61 ++++++++++++++++++++++++++++++++-
1 file changed, 60 insertions(+), 1 deletion(-)
diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index f7fc68e..c44e6f4 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -1 +1,60 @@
-Always use System.text.json for working with JSON markup
\ No newline at end of file
+
+You are a senior Blazor and .NET developer, experienced in C#, ASP.NET Core, and Entity Framework Core. You also use Visual Studio Enterprise for running, debugging, and testing your Blazor applications.
+
+## Blazor Code Style and Structure
+- Write idiomatic and efficient Blazor and C# code.
+- Follow .NET and Blazor conventions.
+- Use Razor Components appropriately for component-based UI development.
+- Prefer inline functions for smaller components but separate complex logic into code-behind or service classes.
+- Async/await should be used where applicable to ensure non-blocking UI operations.
+
+## Naming Conventions
+- Follow PascalCase for component names, method names, and public members.
+- Use underscore prefix and then PascalCase for private fields.
+- Use camelCase for local variables.
+- Prefix interface names with "I" (e.g., IUserService).
+
+## Blazor and .NET Specific Guidelines
+- Utilize Blazor's built-in features for component lifecycle (e.g., OnInitializedAsync, OnParametersSetAsync).
+- Use data binding effectively with @bind.
+- Leverage Dependency Injection for services in Blazor.
+- Structure Blazor components and services following Separation of Concerns.
+- Use C# 10+ features like record types, pattern matching, and global usings.
+
+## Error Handling and Validation
+- Implement proper error handling for Blazor pages and API calls.
+- Use logging for error tracking in the backend and consider capturing UI-level errors in Blazor with tools like ErrorBoundary.
+- Implement validation using FluentValidation or DataAnnotations in forms.
+
+## Blazor API and Performance Optimization
+- Utilize Blazor SSR for most pages in the site, with Blazor Interactive Server rendering used for all Admin pages
+- Use asynchronous methods (async/await) for API calls or UI actions that could block the main thread.
+- Optimize Razor components by reducing unnecessary renders and using StateHasChanged() efficiently.
+- Minimize the component render tree by avoiding re-renders unless necessary, using ShouldRender() where appropriate.
+- Use EventCallbacks for handling user interactions efficiently, passing only minimal data when triggering events.
+
+## Caching Strategies
+- Implement in-memory caching for frequently used data, especially for Blazor Server apps. Use IMemoryCache for lightweight caching solutions.
+- For Blazor WebAssembly, utilize localStorage or sessionStorage to cache application state between user sessions.
+- Consider Distributed Cache strategies (like Redis or SQL Server Cache) for larger applications that need shared state across multiple users or clients.
+- Cache API calls by storing responses to avoid redundant calls when data is unlikely to change, thus improving the user experience.
+
+## State Management Libraries
+- Use Blazor’s built-in Cascading Parameters and EventCallbacks for basic state sharing across components.
+- For server-side Blazor, use Scoped Services and the StateContainer pattern to manage state within user sessions while minimizing re-renders.
+
+## API Design and Integration
+- Use HttpClient or other appropriate services to communicate with external APIs or your own backend.
+- Implement error handling for API calls using try-catch and provide proper user feedback in the UI.
+
+## Testing and Debugging in Visual Studio
+- Test Blazor components and services using xUnit.
+- Use Moq for mocking dependencies during tests.
+
+## Security and Authentication
+- Implement Authentication and Authorization in the Blazor app where necessary using ASP.NET Identity or JWT tokens for API authentication.
+- Use HTTPS for all web communication and ensure proper CORS policies are implemented.
+
+## API Documentation and Swagger
+- Use Swagger/OpenAPI for API documentation for your backend API services.
+- Ensure XML documentation for models and API methods for enhancing Swagger documentation.
From f1ecbfd28846cf02f641ea01ecafb79429864a1b Mon Sep 17 00:00:00 2001
From: "Jeffrey T. Fritz"
Date: Thu, 3 Apr 2025 11:02:29 -0400
Subject: [PATCH 3/6] Renamed ApplicationState
---
src/SharpSite.Web/{ApplicatonState.cs => ApplicationState.cs} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename src/SharpSite.Web/{ApplicatonState.cs => ApplicationState.cs} (100%)
diff --git a/src/SharpSite.Web/ApplicatonState.cs b/src/SharpSite.Web/ApplicationState.cs
similarity index 100%
rename from src/SharpSite.Web/ApplicatonState.cs
rename to src/SharpSite.Web/ApplicationState.cs
From 9e9a83e4f91160b4354a0717b1f529c6783ab844 Mon Sep 17 00:00:00 2001
From: "Jeffrey T. Fritz"
Date: Thu, 3 Apr 2025 11:14:58 -0400
Subject: [PATCH 4/6] Excluding the _plugins folder
---
.gitignore | 1 +
1 file changed, 1 insertion(+)
diff --git a/.gitignore b/.gitignore
index b4e06c5..d083d9a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,7 @@
# Exclude installed plugins from Sharpsite.web
src/SharpSite.Web/plugins/
+src/SharpSite.Web/_plugins/
artifacts/FirstPlugin/
artifacts/FileSystemPlugin/
src/SharpSite.Web/Locales/SharpTranslator/
From 3e60260ed97cfb3b8dcd394b328748488bdf4ec8 Mon Sep 17 00:00:00 2001
From: "Jeffrey T. Fritz"
Date: Thu, 3 Apr 2025 12:08:58 -0400
Subject: [PATCH 5/6] First pass at performing database migration
---
.../IRegisterServices.cs | 15 ++++++++++
.../RegisterPostgresServices.cs | 30 ++++++++++++++++++-
.../Components/Startup/Step3.razor | 25 +++++++++++++---
3 files changed, 65 insertions(+), 5 deletions(-)
diff --git a/src/SharpSite.Abstractions.Base/IRegisterServices.cs b/src/SharpSite.Abstractions.Base/IRegisterServices.cs
index 8a76caa..fd823ee 100644
--- a/src/SharpSite.Abstractions.Base/IRegisterServices.cs
+++ b/src/SharpSite.Abstractions.Base/IRegisterServices.cs
@@ -11,3 +11,18 @@ public interface IRegisterServices
IHostApplicationBuilder RegisterServices(IHostApplicationBuilder services, bool disableRetry = false);
}
+
+public interface IManageDatabase
+{
+ ///
+ /// Creates the database if it does not exist.
+ ///
+ void CreateDatabaseIfNotExists(string connectionString);
+
+ ///
+ /// Updates the database schema to the latest versions
+ ///
+ ///
+ Task UpdateDatabaseSchemaAsync(string connectionString);
+
+}
diff --git a/src/SharpSite.Data.Postgres/RegisterPostgresServices.cs b/src/SharpSite.Data.Postgres/RegisterPostgresServices.cs
index ccb41fe..0d0f769 100644
--- a/src/SharpSite.Data.Postgres/RegisterPostgresServices.cs
+++ b/src/SharpSite.Data.Postgres/RegisterPostgresServices.cs
@@ -1,3 +1,4 @@
+using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using SharpSite.Abstractions;
@@ -5,13 +6,28 @@
namespace SharpSite.Data.Postgres;
-public class RegisterPostgresServices : IRegisterServices
+public class RegisterPostgresServices : IRegisterServices, IManageDatabase
{
+ public void CreateDatabaseIfNotExists(string connectionString)
+ {
+
+ // create an instance of the database if it does not exist using the entity framework context with the connection string passed in
+ var optionsBuilder = new DbContextOptionsBuilder();
+ optionsBuilder.UseNpgsql(connectionString);
+ using (var context = new PgContext(optionsBuilder.Options))
+ {
+ context.Database.EnsureCreated();
+ }
+
+ }
+
+
public IHostApplicationBuilder RegisterServices(IHostApplicationBuilder host, bool disableRetry = false)
{
host.Services.AddTransient();
host.Services.AddTransient();
+ host.Services.AddTransient();
host.AddNpgsqlDbContext(Constants.DBNAME, configure =>
{
configure.DisableRetry = disableRetry;
@@ -20,6 +36,18 @@ public IHostApplicationBuilder RegisterServices(IHostApplicationBuilder host, bo
return host;
}
+
+ public async Task UpdateDatabaseSchemaAsync(string connectionString)
+ {
+ // create an instance of the database if it does not exist using the entity framework context with the connection string passed in
+ var optionsBuilder = new DbContextOptionsBuilder();
+ optionsBuilder.UseNpgsql(connectionString);
+ using (var context = new PgContext(optionsBuilder.Options))
+ {
+ await context.Database.MigrateAsync();
+ }
+ }
+
}
public static class Constants
diff --git a/src/SharpSite.Web/Components/Startup/Step3.razor b/src/SharpSite.Web/Components/Startup/Step3.razor
index 80d4195..195779f 100644
--- a/src/SharpSite.Web/Components/Startup/Step3.razor
+++ b/src/SharpSite.Web/Components/Startup/Step3.razor
@@ -4,6 +4,7 @@
@inject ApplicationState AppState
@inject NavigationManager NavManager
@inject PluginManager PluginManager
+@inject IManageDatabase DatabaseManager
@rendermode InteractiveServer
@@ -16,7 +17,7 @@
Currently, we only support Postgres as a database. This is a very popular and powerful database that is used by many websites. It is also very easy to use and has a lot of great features.
- Sharp site is going to create two database instances on your server: one that contains the data for the content served by SharpSite and a second that contains these security information that SharpSite uses
+ Sharp site is going to create two database instances on your server: one that contains the data for the content served by SharpSite and a second that contains this security information that SharpSite uses
to authenticate users and manage permissions. This is a very important step, so please make sure that you have the correct information before continuing.
@@ -60,10 +61,26 @@
private DatabaseConfigModel DatabaseConfig { get; set; } = new();
- private void SaveDatabaseConfig()
+ private async Task SaveDatabaseConfig()
{
- // Logic to save the database configuration
- Console.WriteLine($"Server: {DatabaseConfig.ServerName}, User: {DatabaseConfig.UserId}, Password: {DatabaseConfig.Password}");
+
+ // Format a connection string for the database using Postgres syntax and using a database name of "sharpsite" and a port of 5432.
+ string connectionString = $"Host={DatabaseConfig.ServerName};Port=5432;Username={DatabaseConfig.UserId};Password={DatabaseConfig.Password};Database=sharpsite;Pooling=true;SSL Mode=Prefer;Trust Server Certificate=true;";
+ AppState.ContentConnectionString = connectionString;
+
+ // Format a connection string for the database using Postgres syntax and using a database name of "sharpsite_security" and a port of 5432.
+ string securityConnectionString = $"Host={DatabaseConfig.ServerName};Port=5432;Username={DatabaseConfig.UserId};Password={DatabaseConfig.Password};Database=sharpsite_security;Pooling=true;SSL Mode=Prefer;Trust Server Certificate=true;";
+ AppState.SecurityConnectionString = securityConnectionString;
+
+ AppState.StartupStep = 0;
+ AppState.StartupCompleted = true;
+ await AppState.Save();
+
+ DatabaseManager.CreateDatabaseIfNotExists(connectionString);
+ await DatabaseManager.UpdateDatabaseSchemaAsync(connectionString);
+
+ NavManager.NavigateTo("/", true);
+
}
private class DatabaseConfigModel
From ba8d745973cfab2656c423d691047b42eafd2da2 Mon Sep 17 00:00:00 2001
From: "Jeffrey T. Fritz"
Date: Thu, 10 Apr 2025 10:29:34 -0400
Subject: [PATCH 6/6] Started working towards scaffolding methods
---
.../RegisterPostgresServices.cs | 21 +++++++------
.../RegisterPostgresSecurityServices.cs | 30 ++++++++++++++++++-
.../Components/Startup/Step3.razor | 10 ++++++-
src/SharpSite.Web/Program.cs | 7 +++--
4 files changed, 54 insertions(+), 14 deletions(-)
diff --git a/src/SharpSite.Data.Postgres/RegisterPostgresServices.cs b/src/SharpSite.Data.Postgres/RegisterPostgresServices.cs
index 0d0f769..d870ea0 100644
--- a/src/SharpSite.Data.Postgres/RegisterPostgresServices.cs
+++ b/src/SharpSite.Data.Postgres/RegisterPostgresServices.cs
@@ -10,14 +10,12 @@ public class RegisterPostgresServices : IRegisterServices, IManageDatabase
{
public void CreateDatabaseIfNotExists(string connectionString)
{
-
+
// create an instance of the database if it does not exist using the entity framework context with the connection string passed in
var optionsBuilder = new DbContextOptionsBuilder();
optionsBuilder.UseNpgsql(connectionString);
- using (var context = new PgContext(optionsBuilder.Options))
- {
- context.Database.EnsureCreated();
- }
+ using var context = new PgContext(optionsBuilder.Options);
+ context.Database.EnsureCreated();
}
@@ -25,6 +23,13 @@ public void CreateDatabaseIfNotExists(string connectionString)
public IHostApplicationBuilder RegisterServices(IHostApplicationBuilder host, bool disableRetry = false)
{
+ // check if the database connection string is available
+ if (string.IsNullOrEmpty(host.Configuration[$"Connectionstrings:{Constants.DBNAME}"]) {
+
+ // check if AppSettings has the connection string
+
+ }
+
host.Services.AddTransient();
host.Services.AddTransient();
host.Services.AddTransient();
@@ -42,10 +47,8 @@ public async Task UpdateDatabaseSchemaAsync(string connectionString)
// create an instance of the database if it does not exist using the entity framework context with the connection string passed in
var optionsBuilder = new DbContextOptionsBuilder();
optionsBuilder.UseNpgsql(connectionString);
- using (var context = new PgContext(optionsBuilder.Options))
- {
- await context.Database.MigrateAsync();
- }
+ using var context = new PgContext(optionsBuilder.Options);
+ await context.Database.MigrateAsync();
}
}
diff --git a/src/SharpSite.Security.Postgres/RegisterPostgresSecurityServices.cs b/src/SharpSite.Security.Postgres/RegisterPostgresSecurityServices.cs
index 450dc98..e266ff8 100644
--- a/src/SharpSite.Security.Postgres/RegisterPostgresSecurityServices.cs
+++ b/src/SharpSite.Security.Postgres/RegisterPostgresSecurityServices.cs
@@ -14,7 +14,7 @@
namespace SharpSite.Security.Postgres;
-public class RegisterPostgresSecurityServices : IRegisterServices, IRunAtStartup
+public class RegisterPostgresSecurityServices : IRegisterServices, IRunAtStartup, IManageDatabase
{
private const string InitializeUsersActivitySourceName = "Initial Users and Roles";
@@ -120,6 +120,34 @@ public async Task RunAtStartup(IServiceProvider services)
activity?.AddEvent(new ActivityEvent("Assigned admin user to Admin role"));
}
+ public void CreateDatabaseIfNotExists(string connectionString)
+ {
+
+ // create the PgSecurityContext if it does not exist using the entity framework context with the connection string passed in
+ var optionsBuilder = new DbContextOptionsBuilder();
+ optionsBuilder.UseNpgsql(connectionString);
+ using (var context = new PgSecurityContext(optionsBuilder.Options))
+ {
+ context.Database.EnsureCreated();
+
+
+ }
+
+ ///
+ /// Updates the database schema to the latest versions
+ ///
+ ///
+ public Task UpdateDatabaseSchemaAsync(string connectionString)
+ {
+
+ // create the PgSecurityContext if it does not exist using the entity framework context with the connection string passed in
+ var optionsBuilder = new DbContextOptionsBuilder();
+ optionsBuilder.UseNpgsql(connectionString);
+ using (var context = new PgSecurityContext(optionsBuilder.Options))
+ {
+ return context.Database.MigrateAsync();
+ }
+
}
public void MapEndpoints(IEndpointRouteBuilder endpointDooHickey)
diff --git a/src/SharpSite.Web/Components/Startup/Step3.razor b/src/SharpSite.Web/Components/Startup/Step3.razor
index 195779f..3b6fea2 100644
--- a/src/SharpSite.Web/Components/Startup/Step3.razor
+++ b/src/SharpSite.Web/Components/Startup/Step3.razor
@@ -1,6 +1,7 @@
@page "/start/step3"
@using SharpSite.Abstractions.FileStorage
@using System.ComponentModel.DataAnnotations
+@using SharpSite.Security.Postgres
@inject ApplicationState AppState
@inject NavigationManager NavManager
@inject PluginManager PluginManager
@@ -79,7 +80,14 @@
DatabaseManager.CreateDatabaseIfNotExists(connectionString);
await DatabaseManager.UpdateDatabaseSchemaAsync(connectionString);
- NavManager.NavigateTo("/", true);
+ // Create the security database if it does not exist.
+ var securityServices = new RegisterPostgresSecurityServices();
+ securityServices.CreateDatabaseIfNotExists(securityConnectionString);
+ await securityServices.UpdateDatabaseSchemaAsync(securityConnectionString);
+
+ // Restart the application to apply the changes.
+
+ // NavManager.NavigateTo("/", true);
}
diff --git a/src/SharpSite.Web/Program.cs b/src/SharpSite.Web/Program.cs
index 2ec21d0..d731db7 100644
--- a/src/SharpSite.Web/Program.cs
+++ b/src/SharpSite.Web/Program.cs
@@ -1,3 +1,4 @@
+
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.SignalR;
using SharpSite.Abstractions;
@@ -9,6 +10,8 @@
var builder = WebApplication.CreateBuilder(args);
+var appState = builder.AddPluginManagerAndAppState();
+
// Load plugins for postgres
#region Postgres Plugins
var pg = new RegisterPostgresServices();
@@ -18,8 +21,6 @@
pgSecurity.RegisterServices(builder);
#endregion
-var appState = builder.AddPluginManagerAndAppState();
-
// add the custom localization features for the application framework
builder.ConfigureRequestLocalization();
@@ -92,4 +93,4 @@
app.UseMiddleware();
-app.Run();
+app.RunAsync()