├── .gitignore ├── 0-Start ├── BigBadBlog.Web │ ├── Areas │ │ └── Identity │ │ │ └── Pages │ │ │ └── _ViewStart.cshtml │ ├── BigBadBlog.Web.csproj │ ├── Data │ │ ├── ApplicationDbContext.cs │ │ ├── IPostRepository.cs │ │ ├── MarkdownPostRepository.cs │ │ └── Migrations │ │ │ ├── 00000000000000_CreateIdentitySchema.Designer.cs │ │ │ ├── 00000000000000_CreateIdentitySchema.cs │ │ │ └── ApplicationDbContextModelSnapshot.cs │ ├── Pages │ │ ├── Error.cshtml │ │ ├── Error.cshtml.cs │ │ ├── Index.cshtml │ │ ├── Index.cshtml.cs │ │ ├── Post.cshtml │ │ ├── Post.cshtml.cs │ │ ├── Privacy.cshtml │ │ ├── Privacy.cshtml.cs │ │ ├── Shared │ │ │ ├── _Layout.cshtml │ │ │ ├── _Layout.cshtml.css │ │ │ ├── _LoginPartial.cshtml │ │ │ └── _ValidationScriptsPartial.cshtml │ │ ├── _ViewImports.cshtml │ │ └── _ViewStart.cshtml │ ├── Posts │ │ ├── FirstPost.md │ │ └── OrderingPizza.md │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── app.db │ ├── appsettings.Development.json │ ├── appsettings.json │ └── wwwroot │ │ ├── css │ │ └── site.css │ │ ├── favicon.ico │ │ ├── js │ │ └── site.js │ │ └── lib │ │ ├── bootstrap │ │ ├── LICENSE │ │ └── dist │ │ │ ├── css │ │ │ ├── bootstrap-grid.css │ │ │ ├── bootstrap-grid.css.map │ │ │ ├── bootstrap-grid.min.css │ │ │ ├── bootstrap-grid.min.css.map │ │ │ ├── bootstrap-grid.rtl.css │ │ │ ├── bootstrap-grid.rtl.css.map │ │ │ ├── bootstrap-grid.rtl.min.css │ │ │ ├── bootstrap-grid.rtl.min.css.map │ │ │ ├── bootstrap-reboot.css │ │ │ ├── bootstrap-reboot.css.map │ │ │ ├── bootstrap-reboot.min.css │ │ │ ├── bootstrap-reboot.min.css.map │ │ │ ├── bootstrap-reboot.rtl.css │ │ │ ├── bootstrap-reboot.rtl.css.map │ │ │ ├── bootstrap-reboot.rtl.min.css │ │ │ ├── bootstrap-reboot.rtl.min.css.map │ │ │ ├── bootstrap-utilities.css │ │ │ ├── bootstrap-utilities.css.map │ │ │ ├── bootstrap-utilities.min.css │ │ │ ├── bootstrap-utilities.min.css.map │ │ │ ├── bootstrap-utilities.rtl.css │ │ │ ├── bootstrap-utilities.rtl.css.map │ │ │ ├── bootstrap-utilities.rtl.min.css │ │ │ ├── bootstrap-utilities.rtl.min.css.map │ │ │ ├── bootstrap.css │ │ │ ├── bootstrap.css.map │ │ │ ├── bootstrap.min.css │ │ │ ├── bootstrap.min.css.map │ │ │ ├── bootstrap.rtl.css │ │ │ ├── bootstrap.rtl.css.map │ │ │ ├── bootstrap.rtl.min.css │ │ │ └── bootstrap.rtl.min.css.map │ │ │ └── js │ │ │ ├── bootstrap.bundle.js │ │ │ ├── bootstrap.bundle.js.map │ │ │ ├── bootstrap.bundle.min.js │ │ │ ├── bootstrap.bundle.min.js.map │ │ │ ├── bootstrap.esm.js │ │ │ ├── bootstrap.esm.js.map │ │ │ ├── bootstrap.esm.min.js │ │ │ ├── bootstrap.esm.min.js.map │ │ │ ├── bootstrap.js │ │ │ ├── bootstrap.js.map │ │ │ ├── bootstrap.min.js │ │ │ └── bootstrap.min.js.map │ │ ├── jquery-validation-unobtrusive │ │ ├── LICENSE.txt │ │ ├── jquery.validate.unobtrusive.js │ │ └── jquery.validate.unobtrusive.min.js │ │ ├── jquery-validation │ │ ├── LICENSE.md │ │ └── dist │ │ │ ├── additional-methods.js │ │ │ ├── additional-methods.min.js │ │ │ ├── jquery.validate.js │ │ │ └── jquery.validate.min.js │ │ └── jquery │ │ ├── LICENSE.txt │ │ └── dist │ │ ├── jquery.js │ │ ├── jquery.min.js │ │ └── jquery.min.map ├── BigBadBlog.sln └── README.md ├── 1-IntroducingAspire ├── BigBadBlog.AppHost │ ├── BigBadBlog.AppHost.csproj │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── appsettings.Development.json │ └── appsettings.json ├── BigBadBlog.ServiceDefaults │ ├── BigBadBlog.ServiceDefaults.csproj │ └── Extensions.cs ├── BigBadBlog.Web │ ├── Areas │ │ └── Identity │ │ │ └── Pages │ │ │ └── _ViewStart.cshtml │ ├── BigBadBlog.Web.csproj │ ├── Data │ │ ├── ApplicationDbContext.cs │ │ ├── IPostRepository.cs │ │ ├── MarkdownPostRepository.cs │ │ └── Migrations │ │ │ ├── 00000000000000_CreateIdentitySchema.Designer.cs │ │ │ ├── 00000000000000_CreateIdentitySchema.cs │ │ │ └── ApplicationDbContextModelSnapshot.cs │ ├── Pages │ │ ├── Error.cshtml │ │ ├── Error.cshtml.cs │ │ ├── Index.cshtml │ │ ├── Index.cshtml.cs │ │ ├── Post.cshtml │ │ ├── Post.cshtml.cs │ │ ├── Privacy.cshtml │ │ ├── Privacy.cshtml.cs │ │ ├── Shared │ │ │ ├── _Layout.cshtml │ │ │ ├── _Layout.cshtml.css │ │ │ ├── _LoginPartial.cshtml │ │ │ └── _ValidationScriptsPartial.cshtml │ │ ├── _ViewImports.cshtml │ │ └── _ViewStart.cshtml │ ├── Posts │ │ ├── FirstPost.md │ │ └── OrderingPizza.md │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── app.db │ ├── appsettings.Development.json │ ├── appsettings.json │ └── wwwroot │ │ ├── css │ │ └── site.css │ │ ├── favicon.ico │ │ ├── js │ │ └── site.js │ │ └── lib │ │ ├── bootstrap │ │ ├── LICENSE │ │ └── dist │ │ │ ├── css │ │ │ ├── bootstrap-grid.css │ │ │ ├── bootstrap-grid.css.map │ │ │ ├── bootstrap-grid.min.css │ │ │ ├── bootstrap-grid.min.css.map │ │ │ ├── bootstrap-grid.rtl.css │ │ │ ├── bootstrap-grid.rtl.css.map │ │ │ ├── bootstrap-grid.rtl.min.css │ │ │ ├── bootstrap-grid.rtl.min.css.map │ │ │ ├── bootstrap-reboot.css │ │ │ ├── bootstrap-reboot.css.map │ │ │ ├── bootstrap-reboot.min.css │ │ │ ├── bootstrap-reboot.min.css.map │ │ │ ├── bootstrap-reboot.rtl.css │ │ │ ├── bootstrap-reboot.rtl.css.map │ │ │ ├── bootstrap-reboot.rtl.min.css │ │ │ ├── bootstrap-reboot.rtl.min.css.map │ │ │ ├── bootstrap-utilities.css │ │ │ ├── bootstrap-utilities.css.map │ │ │ ├── bootstrap-utilities.min.css │ │ │ ├── bootstrap-utilities.min.css.map │ │ │ ├── bootstrap-utilities.rtl.css │ │ │ ├── bootstrap-utilities.rtl.css.map │ │ │ ├── bootstrap-utilities.rtl.min.css │ │ │ ├── bootstrap-utilities.rtl.min.css.map │ │ │ ├── bootstrap.css │ │ │ ├── bootstrap.css.map │ │ │ ├── bootstrap.min.css │ │ │ ├── bootstrap.min.css.map │ │ │ ├── bootstrap.rtl.css │ │ │ ├── bootstrap.rtl.css.map │ │ │ ├── bootstrap.rtl.min.css │ │ │ └── bootstrap.rtl.min.css.map │ │ │ └── js │ │ │ ├── bootstrap.bundle.js │ │ │ ├── bootstrap.bundle.js.map │ │ │ ├── bootstrap.bundle.min.js │ │ │ ├── bootstrap.bundle.min.js.map │ │ │ ├── bootstrap.esm.js │ │ │ ├── bootstrap.esm.js.map │ │ │ ├── bootstrap.esm.min.js │ │ │ ├── bootstrap.esm.min.js.map │ │ │ ├── bootstrap.js │ │ │ ├── bootstrap.js.map │ │ │ ├── bootstrap.min.js │ │ │ └── bootstrap.min.js.map │ │ ├── jquery-validation-unobtrusive │ │ ├── LICENSE.txt │ │ ├── jquery.validate.unobtrusive.js │ │ └── jquery.validate.unobtrusive.min.js │ │ ├── jquery-validation │ │ ├── LICENSE.md │ │ └── dist │ │ │ ├── additional-methods.js │ │ │ ├── additional-methods.min.js │ │ │ ├── jquery.validate.js │ │ │ └── jquery.validate.min.js │ │ └── jquery │ │ ├── LICENSE.txt │ │ └── dist │ │ ├── jquery.js │ │ ├── jquery.min.js │ │ └── jquery.min.map ├── BigBadBlog.sln └── README.md ├── 2-DatabaseMigrationAndEF ├── BigBadBlog.AppHost │ ├── BigBadBlog.AppHost.csproj │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── appsettings.Development.json │ └── appsettings.json ├── BigBadBlog.Common │ ├── BigBadBlog.Common.csproj │ ├── Constants.cs │ └── Data │ │ ├── IPostRepository.cs │ │ └── PostMetadata.cs ├── BigBadBlog.Data.Postgres │ ├── ApplicationDbContext.cs │ ├── BigBadBlog.Data.Postgres.csproj │ ├── Migrations │ │ ├── 20240529030441_FirstMigration.Designer.cs │ │ ├── 20240529030441_FirstMigration.cs │ │ └── ApplicationDbContextModelSnapshot.cs │ ├── PgPost.cs │ ├── PgPostRepository.cs │ └── Program_Extensions.cs ├── BigBadBlog.Service.DatabaseMigration │ ├── BigBadBlog.Service.DatabaseMigration.csproj │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Worker.cs │ ├── appsettings.Development.json │ └── appsettings.json ├── BigBadBlog.ServiceDefaults │ ├── BigBadBlog.ServiceDefaults.csproj │ └── Extensions.cs ├── BigBadBlog.Web │ ├── Areas │ │ └── Identity │ │ │ └── Pages │ │ │ └── _ViewStart.cshtml │ ├── BigBadBlog.Web.csproj │ ├── Data │ │ └── MarkdownPostRepository.cs │ ├── Pages │ │ ├── Error.cshtml │ │ ├── Error.cshtml.cs │ │ ├── Index.cshtml │ │ ├── Index.cshtml.cs │ │ ├── Post.cshtml │ │ ├── Post.cshtml.cs │ │ ├── Privacy.cshtml │ │ ├── Privacy.cshtml.cs │ │ ├── Shared │ │ │ ├── _Layout.cshtml │ │ │ ├── _Layout.cshtml.css │ │ │ ├── _LoginPartial.cshtml │ │ │ └── _ValidationScriptsPartial.cshtml │ │ ├── _ViewImports.cshtml │ │ └── _ViewStart.cshtml │ ├── Posts │ │ ├── FirstPost.md │ │ └── OrderingPizza.md │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── app.db │ ├── appsettings.Development.json │ ├── appsettings.json │ └── wwwroot │ │ ├── css │ │ └── site.css │ │ ├── favicon.ico │ │ ├── js │ │ └── site.js │ │ └── lib │ │ ├── bootstrap │ │ ├── LICENSE │ │ └── dist │ │ │ ├── css │ │ │ ├── bootstrap-grid.css │ │ │ ├── bootstrap-grid.css.map │ │ │ ├── bootstrap-grid.min.css │ │ │ ├── bootstrap-grid.min.css.map │ │ │ ├── bootstrap-grid.rtl.css │ │ │ ├── bootstrap-grid.rtl.css.map │ │ │ ├── bootstrap-grid.rtl.min.css │ │ │ ├── bootstrap-grid.rtl.min.css.map │ │ │ ├── bootstrap-reboot.css │ │ │ ├── bootstrap-reboot.css.map │ │ │ ├── bootstrap-reboot.min.css │ │ │ ├── bootstrap-reboot.min.css.map │ │ │ ├── bootstrap-reboot.rtl.css │ │ │ ├── bootstrap-reboot.rtl.css.map │ │ │ ├── bootstrap-reboot.rtl.min.css │ │ │ ├── bootstrap-reboot.rtl.min.css.map │ │ │ ├── bootstrap-utilities.css │ │ │ ├── bootstrap-utilities.css.map │ │ │ ├── bootstrap-utilities.min.css │ │ │ ├── bootstrap-utilities.min.css.map │ │ │ ├── bootstrap-utilities.rtl.css │ │ │ ├── bootstrap-utilities.rtl.css.map │ │ │ ├── bootstrap-utilities.rtl.min.css │ │ │ ├── bootstrap-utilities.rtl.min.css.map │ │ │ ├── bootstrap.css │ │ │ ├── bootstrap.css.map │ │ │ ├── bootstrap.min.css │ │ │ ├── bootstrap.min.css.map │ │ │ ├── bootstrap.rtl.css │ │ │ ├── bootstrap.rtl.css.map │ │ │ ├── bootstrap.rtl.min.css │ │ │ └── bootstrap.rtl.min.css.map │ │ │ └── js │ │ │ ├── bootstrap.bundle.js │ │ │ ├── bootstrap.bundle.js.map │ │ │ ├── bootstrap.bundle.min.js │ │ │ ├── bootstrap.bundle.min.js.map │ │ │ ├── bootstrap.esm.js │ │ │ ├── bootstrap.esm.js.map │ │ │ ├── bootstrap.esm.min.js │ │ │ ├── bootstrap.esm.min.js.map │ │ │ ├── bootstrap.js │ │ │ ├── bootstrap.js.map │ │ │ ├── bootstrap.min.js │ │ │ └── bootstrap.min.js.map │ │ ├── jquery-validation-unobtrusive │ │ ├── LICENSE.txt │ │ ├── jquery.validate.unobtrusive.js │ │ └── jquery.validate.unobtrusive.min.js │ │ ├── jquery-validation │ │ ├── LICENSE.md │ │ └── dist │ │ │ ├── additional-methods.js │ │ │ ├── additional-methods.min.js │ │ │ ├── jquery.validate.js │ │ │ └── jquery.validate.min.js │ │ └── jquery │ │ ├── LICENSE.txt │ │ └── dist │ │ ├── jquery.js │ │ ├── jquery.min.js │ │ └── jquery.min.map └── BigBadBlog.sln ├── 3-MicroserviceAndMongoDB ├── BigBadBlog.AppHost │ ├── BigBadBlog.AppHost.csproj │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── appsettings.Development.json │ └── appsettings.json ├── BigBadBlog.Common │ ├── BigBadBlog.Common.csproj │ ├── Constants.cs │ └── Data │ │ ├── IPostRepository.cs │ │ └── PostMetadata.cs ├── BigBadBlog.Data.Postgres │ ├── ApplicationDbContext.cs │ ├── BigBadBlog.Data.Postgres.csproj │ ├── Migrations │ │ ├── 20240529030441_FirstMigration.Designer.cs │ │ ├── 20240529030441_FirstMigration.cs │ │ └── ApplicationDbContextModelSnapshot.cs │ ├── PgPost.cs │ ├── PgPostRepository.cs │ └── Program_Extensions.cs ├── BigBadBlog.Service.DatabaseMigration │ ├── BigBadBlog.Service.DatabaseMigration.csproj │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Worker.cs │ ├── appsettings.Development.json │ └── appsettings.json ├── BigBadBlog.ServiceDefaults │ ├── BigBadBlog.ServiceDefaults.csproj │ └── Extensions.cs ├── BigBadBlog.Web │ ├── Areas │ │ └── Identity │ │ │ └── Pages │ │ │ └── _ViewStart.cshtml │ ├── BigBadBlog.Web.csproj │ ├── Data │ │ └── MarkdownPostRepository.cs │ ├── Pages │ │ ├── Error.cshtml │ │ ├── Error.cshtml.cs │ │ ├── Index.cshtml │ │ ├── Index.cshtml.cs │ │ ├── Post.cshtml │ │ ├── Post.cshtml.cs │ │ ├── Privacy.cshtml │ │ ├── Privacy.cshtml.cs │ │ ├── Shared │ │ │ ├── _Layout.cshtml │ │ │ ├── _Layout.cshtml.css │ │ │ ├── _LoginPartial.cshtml │ │ │ └── _ValidationScriptsPartial.cshtml │ │ ├── _ViewImports.cshtml │ │ └── _ViewStart.cshtml │ ├── PostApi.cs │ ├── Posts │ │ ├── FirstPost.md │ │ └── OrderingPizza.md │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── app.db │ ├── appsettings.Development.json │ ├── appsettings.json │ └── wwwroot │ │ ├── css │ │ └── site.css │ │ ├── favicon.ico │ │ ├── js │ │ └── site.js │ │ └── lib │ │ ├── bootstrap │ │ ├── LICENSE │ │ └── dist │ │ │ ├── css │ │ │ ├── bootstrap-grid.css │ │ │ ├── bootstrap-grid.css.map │ │ │ ├── bootstrap-grid.min.css │ │ │ ├── bootstrap-grid.min.css.map │ │ │ ├── bootstrap-grid.rtl.css │ │ │ ├── bootstrap-grid.rtl.css.map │ │ │ ├── bootstrap-grid.rtl.min.css │ │ │ ├── bootstrap-grid.rtl.min.css.map │ │ │ ├── bootstrap-reboot.css │ │ │ ├── bootstrap-reboot.css.map │ │ │ ├── bootstrap-reboot.min.css │ │ │ ├── bootstrap-reboot.min.css.map │ │ │ ├── bootstrap-reboot.rtl.css │ │ │ ├── bootstrap-reboot.rtl.css.map │ │ │ ├── bootstrap-reboot.rtl.min.css │ │ │ ├── bootstrap-reboot.rtl.min.css.map │ │ │ ├── bootstrap-utilities.css │ │ │ ├── bootstrap-utilities.css.map │ │ │ ├── bootstrap-utilities.min.css │ │ │ ├── bootstrap-utilities.min.css.map │ │ │ ├── bootstrap-utilities.rtl.css │ │ │ ├── bootstrap-utilities.rtl.css.map │ │ │ ├── bootstrap-utilities.rtl.min.css │ │ │ ├── bootstrap-utilities.rtl.min.css.map │ │ │ ├── bootstrap.css │ │ │ ├── bootstrap.css.map │ │ │ ├── bootstrap.min.css │ │ │ ├── bootstrap.min.css.map │ │ │ ├── bootstrap.rtl.css │ │ │ ├── bootstrap.rtl.css.map │ │ │ ├── bootstrap.rtl.min.css │ │ │ └── bootstrap.rtl.min.css.map │ │ │ └── js │ │ │ ├── bootstrap.bundle.js │ │ │ ├── bootstrap.bundle.js.map │ │ │ ├── bootstrap.bundle.min.js │ │ │ ├── bootstrap.bundle.min.js.map │ │ │ ├── bootstrap.esm.js │ │ │ ├── bootstrap.esm.js.map │ │ │ ├── bootstrap.esm.min.js │ │ │ ├── bootstrap.esm.min.js.map │ │ │ ├── bootstrap.js │ │ │ ├── bootstrap.js.map │ │ │ ├── bootstrap.min.js │ │ │ └── bootstrap.min.js.map │ │ ├── jquery-validation-unobtrusive │ │ ├── LICENSE.txt │ │ ├── jquery.validate.unobtrusive.js │ │ └── jquery.validate.unobtrusive.min.js │ │ ├── jquery-validation │ │ ├── LICENSE.md │ │ └── dist │ │ │ ├── additional-methods.js │ │ │ ├── additional-methods.min.js │ │ │ ├── jquery.validate.js │ │ │ └── jquery.validate.min.js │ │ └── jquery │ │ ├── LICENSE.txt │ │ └── dist │ │ ├── jquery.js │ │ ├── jquery.min.js │ │ └── jquery.min.map ├── BigBadBlog.sln └── swagger.json ├── LICENSE ├── README.md ├── completed_application ├── BigBadBlog.AppHost │ ├── BigBadBlog.AppHost.csproj │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── appsettings.Development.json │ └── appsettings.json ├── BigBadBlog.Common │ ├── BigBadBlog.Common.csproj │ ├── Constants.cs │ └── Data │ │ ├── IPostRepository.cs │ │ └── PostMetadata.cs ├── BigBadBlog.Data.Postgres │ ├── ApplicationDbContext.cs │ ├── BigBadBlog.Data.Postgres.csproj │ ├── Migrations │ │ ├── 20240529030441_FirstMigration.Designer.cs │ │ ├── 20240529030441_FirstMigration.cs │ │ └── ApplicationDbContextModelSnapshot.cs │ ├── PgPost.cs │ ├── PgPostRepository.cs │ └── Program_Extensions.cs ├── BigBadBlog.Service.DatabaseMigration │ ├── BigBadBlog.Service.DatabaseMigration.csproj │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Worker.cs │ ├── appsettings.Development.json │ └── appsettings.json ├── BigBadBlog.ServiceDefaults │ ├── BigBadBlog.ServiceDefaults.csproj │ └── Extensions.cs ├── BigBadBlog.Web │ ├── Areas │ │ └── Identity │ │ │ └── Pages │ │ │ └── _ViewStart.cshtml │ ├── BigBadBlog.Web.csproj │ ├── Data │ │ └── MarkdownPostRepository.cs │ ├── Pages │ │ ├── Error.cshtml │ │ ├── Error.cshtml.cs │ │ ├── Index.cshtml │ │ ├── Index.cshtml.cs │ │ ├── Post.cshtml │ │ ├── Post.cshtml.cs │ │ ├── Privacy.cshtml │ │ ├── Privacy.cshtml.cs │ │ ├── Shared │ │ │ ├── _Layout.cshtml │ │ │ ├── _Layout.cshtml.css │ │ │ ├── _LoginPartial.cshtml │ │ │ └── _ValidationScriptsPartial.cshtml │ │ ├── _ViewImports.cshtml │ │ └── _ViewStart.cshtml │ ├── PostApi.cs │ ├── Posts │ │ ├── FirstPost.md │ │ └── OrderingPizza.md │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── app.db │ ├── appsettings.Development.json │ ├── appsettings.json │ └── wwwroot │ │ ├── css │ │ └── site.css │ │ ├── favicon.ico │ │ ├── js │ │ └── site.js │ │ └── lib │ │ ├── bootstrap │ │ ├── LICENSE │ │ └── dist │ │ │ ├── css │ │ │ ├── bootstrap-grid.css │ │ │ ├── bootstrap-grid.css.map │ │ │ ├── bootstrap-grid.min.css │ │ │ ├── bootstrap-grid.min.css.map │ │ │ ├── bootstrap-grid.rtl.css │ │ │ ├── bootstrap-grid.rtl.css.map │ │ │ ├── bootstrap-grid.rtl.min.css │ │ │ ├── bootstrap-grid.rtl.min.css.map │ │ │ ├── bootstrap-reboot.css │ │ │ ├── bootstrap-reboot.css.map │ │ │ ├── bootstrap-reboot.min.css │ │ │ ├── bootstrap-reboot.min.css.map │ │ │ ├── bootstrap-reboot.rtl.css │ │ │ ├── bootstrap-reboot.rtl.css.map │ │ │ ├── bootstrap-reboot.rtl.min.css │ │ │ ├── bootstrap-reboot.rtl.min.css.map │ │ │ ├── bootstrap-utilities.css │ │ │ ├── bootstrap-utilities.css.map │ │ │ ├── bootstrap-utilities.min.css │ │ │ ├── bootstrap-utilities.min.css.map │ │ │ ├── bootstrap-utilities.rtl.css │ │ │ ├── bootstrap-utilities.rtl.css.map │ │ │ ├── bootstrap-utilities.rtl.min.css │ │ │ ├── bootstrap-utilities.rtl.min.css.map │ │ │ ├── bootstrap.css │ │ │ ├── bootstrap.css.map │ │ │ ├── bootstrap.min.css │ │ │ ├── bootstrap.min.css.map │ │ │ ├── bootstrap.rtl.css │ │ │ ├── bootstrap.rtl.css.map │ │ │ ├── bootstrap.rtl.min.css │ │ │ └── bootstrap.rtl.min.css.map │ │ │ └── js │ │ │ ├── bootstrap.bundle.js │ │ │ ├── bootstrap.bundle.js.map │ │ │ ├── bootstrap.bundle.min.js │ │ │ ├── bootstrap.bundle.min.js.map │ │ │ ├── bootstrap.esm.js │ │ │ ├── bootstrap.esm.js.map │ │ │ ├── bootstrap.esm.min.js │ │ │ ├── bootstrap.esm.min.js.map │ │ │ ├── bootstrap.js │ │ │ ├── bootstrap.js.map │ │ │ ├── bootstrap.min.js │ │ │ └── bootstrap.min.js.map │ │ ├── jquery-validation-unobtrusive │ │ ├── LICENSE.txt │ │ ├── jquery.validate.unobtrusive.js │ │ └── jquery.validate.unobtrusive.min.js │ │ ├── jquery-validation │ │ ├── LICENSE.md │ │ └── dist │ │ │ ├── additional-methods.js │ │ │ ├── additional-methods.min.js │ │ │ ├── jquery.validate.js │ │ │ └── jquery.validate.min.js │ │ └── jquery │ │ ├── LICENSE.txt │ │ └── dist │ │ ├── jquery.js │ │ ├── jquery.min.js │ │ └── jquery.min.map ├── BigBadBlog.sln └── swagger.json └── docs ├── 1-Introduction.md ├── 2-Database.md ├── 3-Microservices.md └── img ├── 0-DashboardWithRedis.png ├── 0-FirstLookAtDashboard.png ├── 0-Metrics.png ├── 0-SolutionExplorer.png ├── 0-Starter.png ├── 0-StructuredLogs.png ├── 0-TraceWithRedis.png ├── 0-Traces.png ├── 0-Workloads.png ├── 2-DashboardWithDb-1.png ├── 2-DashboardWithDb-2.png └── 3-Reddit.png /0-Start/BigBadBlog.Web/Areas/Identity/Pages/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "/Pages/Shared/_Layout.cshtml"; 3 | } 4 | -------------------------------------------------------------------------------- /0-Start/BigBadBlog.Web/BigBadBlog.Web.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | aspnet-BigBadBlog.Web-c0b48993-c310-4519-8acf-b5b7242032d2 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /0-Start/BigBadBlog.Web/Data/ApplicationDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore; 3 | 4 | namespace BigBadBlog.Web.Data; 5 | 6 | public class ApplicationDbContext : IdentityDbContext 7 | { 8 | public ApplicationDbContext(DbContextOptions options) 9 | : base(options) 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /0-Start/BigBadBlog.Web/Data/IPostRepository.cs: -------------------------------------------------------------------------------- 1 | namespace BigBadBlog.Web.Data; 2 | 3 | /// 4 | /// A definition of a simple interaction with blog posts 5 | /// 6 | public interface IPostRepository 7 | { 8 | 9 | Task> GetPostsAsync(int count, int page); 10 | 11 | Task<(PostMetadata,string)> GetPostAsync(string slug); 12 | 13 | } 14 | 15 | public record PostMetadata(string Filename, string Title, string Author, DateTime Date) 16 | { 17 | 18 | public string Slug => Uri.EscapeDataString(Title.ToLower()); 19 | 20 | } -------------------------------------------------------------------------------- /0-Start/BigBadBlog.Web/Data/MarkdownPostRepository.cs: -------------------------------------------------------------------------------- 1 | using Markdig; 2 | using Markdig.Extensions.Yaml; 3 | using Markdig.Syntax; 4 | 5 | namespace BigBadBlog.Web.Data; 6 | 7 | /// 8 | /// A simple repository for blog posts that are saved as markdown files in the Posts folder 9 | /// 10 | public class MarkdownPostRepository : IPostRepository 11 | { 12 | 13 | private static readonly Dictionary _posts = new(); 14 | private readonly MarkdownPipeline _MarkdownPipeline; 15 | 16 | public MarkdownPostRepository() 17 | { 18 | 19 | _MarkdownPipeline = new MarkdownPipelineBuilder() 20 | .UseYamlFrontMatter() 21 | .Build(); 22 | 23 | if (!_posts.Any()) 24 | { 25 | var files = Directory.GetFiles("Posts", "*.md"); 26 | foreach (var file in files) 27 | { 28 | var newPost = ExtractMetadataFromFile(file); 29 | _posts.Add(newPost.Item1, newPost.Item2); 30 | } 31 | } 32 | 33 | } 34 | 35 | public Task<(PostMetadata, string)> GetPostAsync(string slug) 36 | { 37 | 38 | var toCheck = Uri.EscapeDataString(slug.ToLower()); 39 | var thePost = _posts.FirstOrDefault(p => p.Key.Slug == toCheck); 40 | 41 | return Task.FromResult((thePost.Key, thePost.Value)); 42 | 43 | } 44 | 45 | public Task> GetPostsAsync(int count, int page) 46 | { 47 | 48 | count = count < 1 ? 10 : count; 49 | page = page < 1 ? 1 : page; 50 | 51 | return _posts.Any() ? 52 | Task.FromResult(_posts 53 | .OrderByDescending(p => p.Key.Date) 54 | .Skip((page - 1) * count) 55 | .Take(count) 56 | .Select(p => (p.Key, p.Value)) 57 | .AsEnumerable()) 58 | : Task.FromResult(Enumerable.Empty<(PostMetadata, string)>()); 59 | } 60 | 61 | private (PostMetadata, string) ExtractMetadataFromFile(string fileName) 62 | { 63 | 64 | // Read all content from the file 65 | var content = File.ReadAllText(fileName); 66 | 67 | var yamlBlock = Markdown.Parse(content, _MarkdownPipeline) 68 | .Descendants() 69 | .FirstOrDefault(); 70 | 71 | var yamlDictionary = new Dictionary(); 72 | foreach (var line in yamlBlock.Lines) 73 | { 74 | if (line.ToString().Contains(": ")) 75 | { 76 | var values = line.ToString().Split(": ", StringSplitOptions.TrimEntries); 77 | yamlDictionary.Add(values[0].ToLower(), values[1]); 78 | } 79 | } 80 | 81 | return (new PostMetadata( 82 | fileName, 83 | yamlDictionary["title"], 84 | yamlDictionary["author"], 85 | DateTime.Parse(yamlDictionary["date"]) 86 | ), content); 87 | 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /0-Start/BigBadBlog.Web/Pages/Error.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ErrorModel 3 | @{ 4 | ViewData["Title"] = "Error"; 5 | } 6 | 7 |

Error.

8 |

An error occurred while processing your request.

9 | 10 | @if (Model.ShowRequestId) 11 | { 12 |

13 | Request ID: @Model.RequestId 14 |

15 | } 16 | 17 |

Development Mode

18 |

19 | Swapping to the Development environment displays detailed information about the error that occurred. 20 |

21 |

22 | The Development environment shouldn't be enabled for deployed applications. 23 | It can result in displaying sensitive information from exceptions to end users. 24 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development 25 | and restarting the app. 26 |

27 | -------------------------------------------------------------------------------- /0-Start/BigBadBlog.Web/Pages/Error.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.AspNetCore.Mvc.RazorPages; 4 | 5 | namespace BigBadBlog.Web.Pages; 6 | 7 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] 8 | [IgnoreAntiforgeryToken] 9 | public class ErrorModel : PageModel 10 | { 11 | public string? RequestId { get; set; } 12 | 13 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 14 | 15 | private readonly ILogger _logger; 16 | 17 | public ErrorModel(ILogger logger) 18 | { 19 | _logger = logger; 20 | } 21 | 22 | public void OnGet() 23 | { 24 | RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /0-Start/BigBadBlog.Web/Pages/Index.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @using Microsoft.AspNetCore.Components 3 | @model IndexModel 4 | @{ 5 | ViewData["Title"] = "Home page"; 6 | } 7 | 8 |
9 |

Welcome to the Big Bad Blog

10 |
11 | 12 | @foreach (var post in Model.Posts) 13 | { 14 |
15 |
16 |
@post.Metadata.Title
17 |
by @post.Metadata.Author on @post.Metadata.Date.ToShortDateString()
18 |

@Html.Raw(Markdig.Markdown.ToHtml(post.Content, Model.MarkdownPipeline))

19 |
20 |
21 | } -------------------------------------------------------------------------------- /0-Start/BigBadBlog.Web/Pages/Index.cshtml.cs: -------------------------------------------------------------------------------- 1 | using Markdig; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.AspNetCore.Mvc.RazorPages; 4 | 5 | namespace BigBadBlog.Web.Pages; 6 | 7 | public class IndexModel : PageModel 8 | { 9 | private readonly ILogger _logger; 10 | public readonly MarkdownPipeline MarkdownPipeline; 11 | private readonly IPostRepository _postRepository; 12 | public IEnumerable<(PostMetadata Metadata, string Content)> Posts; 13 | 14 | public IndexModel(IPostRepository postRepository, ILogger logger) 15 | { 16 | _postRepository = postRepository; 17 | _logger = logger; 18 | 19 | MarkdownPipeline = new MarkdownPipelineBuilder() 20 | .UseYamlFrontMatter() 21 | .Build(); 22 | 23 | } 24 | 25 | public async Task OnGetAsync() 26 | { 27 | 28 | Posts = await _postRepository.GetPostsAsync(10, 1); 29 | 30 | return Page(); 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /0-Start/BigBadBlog.Web/Pages/Post.cshtml: -------------------------------------------------------------------------------- 1 | @page "/p/{slug}" 2 | @model BigBadBlog.Web.Pages.PostModel 3 | @{ 4 | } 5 | 6 | 7 |
@Model.Post.Metadata.Title
8 |
by @Model.Post.Metadata.Author on @Model.Post.Metadata.Date.ToShortDateString()
9 |

@Html.Raw(Markdig.Markdown.ToHtml(Model.Post.Content, Model.MarkdownPipeline))

10 | -------------------------------------------------------------------------------- /0-Start/BigBadBlog.Web/Pages/Post.cshtml.cs: -------------------------------------------------------------------------------- 1 | using BigBadBlog.Web.Data; 2 | using Markdig; 3 | using Microsoft.AspNetCore.Mvc; 4 | using Microsoft.AspNetCore.Mvc.RazorPages; 5 | using Microsoft.Extensions.Hosting; 6 | 7 | namespace BigBadBlog.Web.Pages; 8 | 9 | public class PostModel : PageModel 10 | { 11 | private readonly IPostRepository _postRepository; 12 | public readonly MarkdownPipeline MarkdownPipeline; 13 | 14 | public PostModel(IPostRepository postRepository, IWebHostEnvironment host) 15 | { 16 | _postRepository = postRepository; 17 | 18 | MarkdownPipeline = new MarkdownPipelineBuilder() 19 | .UseYamlFrontMatter() 20 | .Build(); 21 | 22 | } 23 | 24 | public (PostMetadata Metadata, string Content) Post { get; private set; } 25 | 26 | public async Task OnGetAsync(string slug) 27 | { 28 | 29 | Post = await _postRepository.GetPostAsync(slug); 30 | 31 | if (Post == default) 32 | { 33 | return NotFound(); 34 | } 35 | 36 | return Page(); 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /0-Start/BigBadBlog.Web/Pages/Privacy.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model PrivacyModel 3 | @{ 4 | ViewData["Title"] = "Privacy Policy"; 5 | } 6 |

@ViewData["Title"]

7 | 8 |

Use this page to detail your site's privacy policy.

9 | -------------------------------------------------------------------------------- /0-Start/BigBadBlog.Web/Pages/Privacy.cshtml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.AspNetCore.Mvc.RazorPages; 3 | 4 | namespace BigBadBlog.Web.Pages; 5 | 6 | public class PrivacyModel : PageModel 7 | { 8 | private readonly ILogger _logger; 9 | 10 | public PrivacyModel(ILogger logger) 11 | { 12 | _logger = logger; 13 | } 14 | 15 | public void OnGet() 16 | { 17 | } 18 | } 19 | 20 | -------------------------------------------------------------------------------- /0-Start/BigBadBlog.Web/Pages/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | @ViewData["Title"] - The Big Bad Blog 7 | 8 | 9 | 10 | 11 | 12 |
13 | 33 |
34 |
35 |
36 | @RenderBody() 37 |
38 |
39 | 40 |
41 |
42 | © 2024 - The Big Bad Blog - Privacy 43 |
44 |
45 | 46 | 47 | 48 | 49 | 50 | @await RenderSectionAsync("Scripts", required: false) 51 | 52 | -------------------------------------------------------------------------------- /0-Start/BigBadBlog.Web/Pages/Shared/_Layout.cshtml.css: -------------------------------------------------------------------------------- 1 | /* Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification 2 | for details on configuring this project to bundle and minify static web assets. */ 3 | 4 | a.navbar-brand { 5 | white-space: normal; 6 | text-align: center; 7 | word-break: break-all; 8 | } 9 | 10 | a { 11 | color: #0077cc; 12 | } 13 | 14 | .btn-primary { 15 | color: #fff; 16 | background-color: #1b6ec2; 17 | border-color: #1861ac; 18 | } 19 | 20 | .nav-pills .nav-link.active, .nav-pills .show > .nav-link { 21 | color: #fff; 22 | background-color: #1b6ec2; 23 | border-color: #1861ac; 24 | } 25 | 26 | .border-top { 27 | border-top: 1px solid #e5e5e5; 28 | } 29 | .border-bottom { 30 | border-bottom: 1px solid #e5e5e5; 31 | } 32 | 33 | .box-shadow { 34 | box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05); 35 | } 36 | 37 | button.accept-policy { 38 | font-size: 1rem; 39 | line-height: inherit; 40 | } 41 | 42 | .footer { 43 | position: absolute; 44 | bottom: 0; 45 | width: 100%; 46 | white-space: nowrap; 47 | line-height: 60px; 48 | } 49 | -------------------------------------------------------------------------------- /0-Start/BigBadBlog.Web/Pages/Shared/_LoginPartial.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Identity 2 | @inject SignInManager SignInManager 3 | @inject UserManager UserManager 4 | 5 | 27 | -------------------------------------------------------------------------------- /0-Start/BigBadBlog.Web/Pages/Shared/_ValidationScriptsPartial.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | -------------------------------------------------------------------------------- /0-Start/BigBadBlog.Web/Pages/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Identity 2 | @using BigBadBlog.Web 3 | @using BigBadBlog.Web.Data 4 | @namespace BigBadBlog.Web.Pages 5 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 6 | -------------------------------------------------------------------------------- /0-Start/BigBadBlog.Web/Pages/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /0-Start/BigBadBlog.Web/Posts/FirstPost.md: -------------------------------------------------------------------------------- 1 | --- 2 | Title: This is my first post 3 | Date: 2024-05-15 4 | Author: Arthur The Author 5 | --- 6 | 7 | This is the content of my first post. It's a very interesting post, I promise. 8 | -------------------------------------------------------------------------------- /0-Start/BigBadBlog.Web/Posts/OrderingPizza.md: -------------------------------------------------------------------------------- 1 | --- 2 | Title: I think I'll order some pizza 3 | Date: 2024-05-16 4 | Author: Arthur The Author 5 | --- 6 | 7 | I'm feeling lazy today, so I think I'll order some pizza. I'm not sure what I want yet, but I'll figure it out. I'm thinking about getting a large pepperoni pizza, but I might change my mind. I'll let you know what I decide. 8 | 9 | There's this really cool new pizza shop that opened down the street called Blazing Pizza. I think I'll give them a try 10 | -------------------------------------------------------------------------------- /0-Start/BigBadBlog.Web/Program.cs: -------------------------------------------------------------------------------- 1 | global using BigBadBlog.Web.Data; 2 | 3 | using Microsoft.AspNetCore.Identity; 4 | using Microsoft.EntityFrameworkCore; 5 | using BigBadBlog.Web.Data; 6 | using System.Net; 7 | 8 | var builder = WebApplication.CreateBuilder(args); 9 | 10 | // Add my repository for posts 11 | builder.Services.AddTransient(); 12 | 13 | // Add services to the container. 14 | var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found."); 15 | builder.Services.AddDbContext(options => 16 | options.UseSqlite(connectionString)); 17 | builder.Services.AddDatabaseDeveloperPageExceptionFilter(); 18 | 19 | builder.Services.AddDefaultIdentity(options => options.SignIn.RequireConfirmedAccount = true) 20 | .AddEntityFrameworkStores(); 21 | builder.Services.AddRazorPages(); 22 | 23 | var app = builder.Build(); 24 | 25 | // Configure the HTTP request pipeline. 26 | if (app.Environment.IsDevelopment()) 27 | { 28 | app.UseMigrationsEndPoint(); 29 | } 30 | else 31 | { 32 | app.UseExceptionHandler("/Error"); 33 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. 34 | app.UseHsts(); 35 | } 36 | 37 | app.Map("/Posts", (HttpContext ctx) => 38 | { 39 | return HttpStatusCode.NotFound; 40 | }); 41 | 42 | app.UseHttpsRedirection(); 43 | app.UseStaticFiles(); 44 | 45 | app.UseRouting(); 46 | 47 | app.UseAuthorization(); 48 | 49 | app.MapRazorPages(); 50 | 51 | app.Run(); 52 | -------------------------------------------------------------------------------- /0-Start/BigBadBlog.Web/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:51799", 8 | "sslPort": 44354 9 | } 10 | }, 11 | "profiles": { 12 | "http": { 13 | "commandName": "Project", 14 | "dotnetRunMessages": true, 15 | "launchBrowser": true, 16 | "applicationUrl": "http://localhost:5055", 17 | "environmentVariables": { 18 | "ASPNETCORE_ENVIRONMENT": "Development" 19 | } 20 | }, 21 | "https": { 22 | "commandName": "Project", 23 | "dotnetRunMessages": true, 24 | "launchBrowser": true, 25 | "applicationUrl": "https://localhost:7008;http://localhost:5055", 26 | "environmentVariables": { 27 | "ASPNETCORE_ENVIRONMENT": "Development" 28 | } 29 | }, 30 | "IIS Express": { 31 | "commandName": "IISExpress", 32 | "launchBrowser": true, 33 | "environmentVariables": { 34 | "ASPNETCORE_ENVIRONMENT": "Development" 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /0-Start/BigBadBlog.Web/app.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/BigBadBlog/39336acf4e7cb464ecba49487144d5eff190292f/0-Start/BigBadBlog.Web/app.db -------------------------------------------------------------------------------- /0-Start/BigBadBlog.Web/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "DetailedErrors": true, 3 | "Logging": { 4 | "LogLevel": { 5 | "Default": "Information", 6 | "Microsoft.AspNetCore": "Warning" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /0-Start/BigBadBlog.Web/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "DefaultConnection": "DataSource=app.db;Cache=Shared" 4 | }, 5 | "Logging": { 6 | "LogLevel": { 7 | "Default": "Information", 8 | "Microsoft.AspNetCore": "Warning" 9 | } 10 | }, 11 | "AllowedHosts": "*" 12 | } 13 | -------------------------------------------------------------------------------- /0-Start/BigBadBlog.Web/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-size: 14px; 3 | } 4 | 5 | @media (min-width: 768px) { 6 | html { 7 | font-size: 16px; 8 | } 9 | } 10 | 11 | .btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus { 12 | box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb; 13 | } 14 | 15 | html { 16 | position: relative; 17 | min-height: 100%; 18 | } 19 | 20 | body { 21 | margin-bottom: 60px; 22 | } -------------------------------------------------------------------------------- /0-Start/BigBadBlog.Web/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/BigBadBlog/39336acf4e7cb464ecba49487144d5eff190292f/0-Start/BigBadBlog.Web/wwwroot/favicon.ico -------------------------------------------------------------------------------- /0-Start/BigBadBlog.Web/wwwroot/js/site.js: -------------------------------------------------------------------------------- 1 | // Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification 2 | // for details on configuring this project to bundle and minify static web assets. 3 | 4 | // Write your JavaScript code. 5 | -------------------------------------------------------------------------------- /0-Start/BigBadBlog.Web/wwwroot/lib/bootstrap/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2011-2021 Twitter, Inc. 4 | Copyright (c) 2011-2021 The Bootstrap Authors 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /0-Start/BigBadBlog.Web/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) .NET Foundation and Contributors 4 | 5 | All rights reserved. 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /0-Start/BigBadBlog.Web/wwwroot/lib/jquery-validation/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright Jörn Zaefferer 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /0-Start/BigBadBlog.Web/wwwroot/lib/jquery/LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Copyright OpenJS Foundation and other contributors, https://openjsf.org/ 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /0-Start/BigBadBlog.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31903.59 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BigBadBlog.Web", "BigBadBlog.Web\BigBadBlog.Web.csproj", "{0FD2607E-0983-406E-A078-2A681860117B}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {0FD2607E-0983-406E-A078-2A681860117B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {0FD2607E-0983-406E-A078-2A681860117B}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {0FD2607E-0983-406E-A078-2A681860117B}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {0FD2607E-0983-406E-A078-2A681860117B}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /0-Start/README.md: -------------------------------------------------------------------------------- 1 | # 0 - Start 2 | 3 | This is the entry point to follow along with the Big Bag Blog series. Inside this folder is a simple blog engine written with ASP.NET Core Razor Pages. It is configured to be able to read markdown files from a folder called Posts, read the metadata from the YAML header, and transform that markdown to HTML. 4 | 5 | There's an `IPostRepository` interface that hides all of the interaction with the filesystem that's implemented in the `MarkdownPostRepository` class. There's a little bit of in-memory caching of the posts to help reduce the amount of times the blog needs to read from disk. 6 | 7 | The original author of this blog thought it would be easy to just write a new markdown file for each new entry and redeploy the website to a cloud provider like Azure. It's easy to follow, but clearly will not be able to handle hosting many blog posts or a lot of traffic from the internet. 8 | 9 | Let's get started with [Module 1](../docs/1-Introduction.md) to improve this blog engine! -------------------------------------------------------------------------------- /1-IntroducingAspire/BigBadBlog.AppHost/BigBadBlog.AppHost.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | true 9 | 3e9797fc-af6f-46a6-a999-4ebc8157b5de 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /1-IntroducingAspire/BigBadBlog.AppHost/Program.cs: -------------------------------------------------------------------------------- 1 | using Projects; 2 | 3 | var builder = DistributedApplication.CreateBuilder(args); 4 | 5 | var cache = builder.AddRedis("outputcache", 65028) 6 | .WithRedisCommander(); 7 | 8 | var webApp = builder.AddProject("web") 9 | .WithReference(cache) 10 | .WithExternalHttpEndpoints(); 11 | 12 | builder.Build().Run(); 13 | -------------------------------------------------------------------------------- /1-IntroducingAspire/BigBadBlog.AppHost/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "profiles": { 4 | "https": { 5 | "commandName": "Project", 6 | "dotnetRunMessages": true, 7 | "launchBrowser": true, 8 | "applicationUrl": "https://localhost:17164;http://localhost:15115", 9 | "environmentVariables": { 10 | "ASPNETCORE_ENVIRONMENT": "Development", 11 | "DOTNET_ENVIRONMENT": "Development", 12 | "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21248", 13 | "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22194" 14 | } 15 | }, 16 | "http": { 17 | "commandName": "Project", 18 | "dotnetRunMessages": true, 19 | "launchBrowser": true, 20 | "applicationUrl": "http://localhost:15115", 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development", 23 | "DOTNET_ENVIRONMENT": "Development", 24 | "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19289", 25 | "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20184" 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /1-IntroducingAspire/BigBadBlog.AppHost/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /1-IntroducingAspire/BigBadBlog.AppHost/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning", 6 | "Aspire.Hosting.Dcp": "Warning" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /1-IntroducingAspire/BigBadBlog.ServiceDefaults/BigBadBlog.ServiceDefaults.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /1-IntroducingAspire/BigBadBlog.Web/Areas/Identity/Pages/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "/Pages/Shared/_Layout.cshtml"; 3 | } 4 | -------------------------------------------------------------------------------- /1-IntroducingAspire/BigBadBlog.Web/BigBadBlog.Web.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | aspnet-BigBadBlog.Web-c0b48993-c310-4519-8acf-b5b7242032d2 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /1-IntroducingAspire/BigBadBlog.Web/Data/ApplicationDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore; 3 | 4 | namespace BigBadBlog.Web.Data; 5 | 6 | public class ApplicationDbContext : IdentityDbContext 7 | { 8 | public ApplicationDbContext(DbContextOptions options) 9 | : base(options) 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /1-IntroducingAspire/BigBadBlog.Web/Data/IPostRepository.cs: -------------------------------------------------------------------------------- 1 | namespace BigBadBlog.Web.Data; 2 | 3 | /// 4 | /// A definition of a simple interaction with blog posts 5 | /// 6 | public interface IPostRepository 7 | { 8 | 9 | Task> GetPostsAsync(int count, int page); 10 | 11 | Task<(PostMetadata,string)> GetPostAsync(string slug); 12 | 13 | } 14 | 15 | public record PostMetadata(string Filename, string Title, string Author, DateTime Date) 16 | { 17 | 18 | public string Slug => Uri.EscapeDataString(Title.ToLower()); 19 | 20 | } -------------------------------------------------------------------------------- /1-IntroducingAspire/BigBadBlog.Web/Data/MarkdownPostRepository.cs: -------------------------------------------------------------------------------- 1 | using Markdig; 2 | using Markdig.Extensions.Yaml; 3 | using Markdig.Syntax; 4 | 5 | namespace BigBadBlog.Web.Data; 6 | 7 | /// 8 | /// A simple repository for blog posts that are saved as markdown files in the Posts folder 9 | /// 10 | public class MarkdownPostRepository : IPostRepository 11 | { 12 | 13 | private static readonly Dictionary _posts = new(); 14 | private readonly MarkdownPipeline _MarkdownPipeline; 15 | 16 | public MarkdownPostRepository() 17 | { 18 | 19 | _MarkdownPipeline = new MarkdownPipelineBuilder() 20 | .UseYamlFrontMatter() 21 | .Build(); 22 | 23 | if (!_posts.Any()) 24 | { 25 | var files = Directory.GetFiles("Posts", "*.md"); 26 | foreach (var file in files) 27 | { 28 | var newPost = ExtractMetadataFromFile(file); 29 | _posts.Add(newPost.Item1, newPost.Item2); 30 | } 31 | } 32 | 33 | } 34 | 35 | public Task<(PostMetadata, string)> GetPostAsync(string slug) 36 | { 37 | 38 | var toCheck = Uri.EscapeDataString(slug.ToLower()); 39 | var thePost = _posts.FirstOrDefault(p => p.Key.Slug == toCheck); 40 | 41 | return Task.FromResult((thePost.Key, thePost.Value)); 42 | 43 | } 44 | 45 | public Task> GetPostsAsync(int count, int page) 46 | { 47 | 48 | count = count < 1 ? 10 : count; 49 | page = page < 1 ? 1 : page; 50 | 51 | return _posts.Any() ? 52 | Task.FromResult(_posts 53 | .OrderByDescending(p => p.Key.Date) 54 | .Skip((page - 1) * count) 55 | .Take(count) 56 | .Select(p => (p.Key, p.Value)) 57 | .AsEnumerable()) 58 | : Task.FromResult(Enumerable.Empty<(PostMetadata, string)>()); 59 | } 60 | 61 | private (PostMetadata, string) ExtractMetadataFromFile(string fileName) 62 | { 63 | 64 | // Read all content from the file 65 | var content = File.ReadAllText(fileName); 66 | 67 | var yamlBlock = Markdown.Parse(content, _MarkdownPipeline) 68 | .Descendants() 69 | .FirstOrDefault(); 70 | 71 | var yamlDictionary = new Dictionary(); 72 | foreach (var line in yamlBlock.Lines) 73 | { 74 | if (line.ToString().Contains(": ")) 75 | { 76 | var values = line.ToString().Split(": ", StringSplitOptions.TrimEntries); 77 | yamlDictionary.Add(values[0].ToLower(), values[1]); 78 | } 79 | } 80 | 81 | return (new PostMetadata( 82 | fileName, 83 | yamlDictionary["title"], 84 | yamlDictionary["author"], 85 | DateTime.Parse(yamlDictionary["date"]) 86 | ), content); 87 | 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /1-IntroducingAspire/BigBadBlog.Web/Pages/Error.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ErrorModel 3 | @{ 4 | ViewData["Title"] = "Error"; 5 | } 6 | 7 |

Error.

8 |

An error occurred while processing your request.

9 | 10 | @if (Model.ShowRequestId) 11 | { 12 |

13 | Request ID: @Model.RequestId 14 |

15 | } 16 | 17 |

Development Mode

18 |

19 | Swapping to the Development environment displays detailed information about the error that occurred. 20 |

21 |

22 | The Development environment shouldn't be enabled for deployed applications. 23 | It can result in displaying sensitive information from exceptions to end users. 24 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development 25 | and restarting the app. 26 |

27 | -------------------------------------------------------------------------------- /1-IntroducingAspire/BigBadBlog.Web/Pages/Error.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.AspNetCore.Mvc.RazorPages; 4 | 5 | namespace BigBadBlog.Web.Pages; 6 | 7 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] 8 | [IgnoreAntiforgeryToken] 9 | public class ErrorModel : PageModel 10 | { 11 | public string? RequestId { get; set; } 12 | 13 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 14 | 15 | private readonly ILogger _logger; 16 | 17 | public ErrorModel(ILogger logger) 18 | { 19 | _logger = logger; 20 | } 21 | 22 | public void OnGet() 23 | { 24 | RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /1-IntroducingAspire/BigBadBlog.Web/Pages/Index.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @using Microsoft.AspNetCore.Components 3 | @model IndexModel 4 | @{ 5 | ViewData["Title"] = "Home page"; 6 | } 7 | 8 |
9 |

Welcome to the Big Bad Blog

10 |
11 | 12 | @foreach (var post in Model.Posts) 13 | { 14 |
15 |
16 |
@post.Metadata.Title
17 |
by @post.Metadata.Author on @post.Metadata.Date.ToShortDateString()
18 |

@Html.Raw(Markdig.Markdown.ToHtml(post.Content, Model.MarkdownPipeline))

19 |
20 |
21 | } -------------------------------------------------------------------------------- /1-IntroducingAspire/BigBadBlog.Web/Pages/Index.cshtml.cs: -------------------------------------------------------------------------------- 1 | using Markdig; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.AspNetCore.Mvc.RazorPages; 4 | using Microsoft.AspNetCore.OutputCaching; 5 | 6 | namespace BigBadBlog.Web.Pages; 7 | 8 | [OutputCache(PolicyName = "Home")] 9 | public class IndexModel : PageModel 10 | { 11 | private readonly ILogger _logger; 12 | public readonly MarkdownPipeline MarkdownPipeline; 13 | private readonly IPostRepository _postRepository; 14 | public IEnumerable<(PostMetadata Metadata, string Content)> Posts; 15 | 16 | public IndexModel(IPostRepository postRepository, ILogger logger) 17 | { 18 | _postRepository = postRepository; 19 | _logger = logger; 20 | 21 | MarkdownPipeline = new MarkdownPipelineBuilder() 22 | .UseYamlFrontMatter() 23 | .Build(); 24 | 25 | } 26 | 27 | public async Task OnGetAsync() 28 | { 29 | 30 | await Task.Delay(TimeSpan.FromSeconds(1)); 31 | 32 | Posts = await _postRepository.GetPostsAsync(10, 1); 33 | 34 | return Page(); 35 | 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /1-IntroducingAspire/BigBadBlog.Web/Pages/Post.cshtml: -------------------------------------------------------------------------------- 1 | @page "/p/{slug}" 2 | @model BigBadBlog.Web.Pages.PostModel 3 | @{ 4 | } 5 | 6 | 7 |
@Model.Post.Metadata.Title
8 |
by @Model.Post.Metadata.Author on @Model.Post.Metadata.Date.ToShortDateString()
9 |

@Html.Raw(Markdig.Markdown.ToHtml(Model.Post.Content, Model.MarkdownPipeline))

10 | -------------------------------------------------------------------------------- /1-IntroducingAspire/BigBadBlog.Web/Pages/Post.cshtml.cs: -------------------------------------------------------------------------------- 1 | using BigBadBlog.Web.Data; 2 | using Markdig; 3 | using Microsoft.AspNetCore.Mvc; 4 | using Microsoft.AspNetCore.Mvc.RazorPages; 5 | using Microsoft.AspNetCore.OutputCaching; 6 | using Microsoft.Extensions.Hosting; 7 | 8 | namespace BigBadBlog.Web.Pages; 9 | 10 | [OutputCache(PolicyName = "Post")] 11 | public class PostModel : PageModel 12 | { 13 | private readonly IPostRepository _postRepository; 14 | public readonly MarkdownPipeline MarkdownPipeline; 15 | 16 | public PostModel(IPostRepository postRepository, IWebHostEnvironment host) 17 | { 18 | _postRepository = postRepository; 19 | 20 | MarkdownPipeline = new MarkdownPipelineBuilder() 21 | .UseYamlFrontMatter() 22 | .Build(); 23 | 24 | } 25 | 26 | public (PostMetadata Metadata, string Content) Post { get; private set; } 27 | 28 | public async Task OnGetAsync(string slug) 29 | { 30 | 31 | Post = await _postRepository.GetPostAsync(slug); 32 | 33 | if (Post == default) 34 | { 35 | return NotFound(); 36 | } 37 | 38 | return Page(); 39 | 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /1-IntroducingAspire/BigBadBlog.Web/Pages/Privacy.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model PrivacyModel 3 | @{ 4 | ViewData["Title"] = "Privacy Policy"; 5 | } 6 |

@ViewData["Title"]

7 | 8 |

Use this page to detail your site's privacy policy.

9 | -------------------------------------------------------------------------------- /1-IntroducingAspire/BigBadBlog.Web/Pages/Privacy.cshtml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.AspNetCore.Mvc.RazorPages; 3 | 4 | namespace BigBadBlog.Web.Pages; 5 | 6 | public class PrivacyModel : PageModel 7 | { 8 | private readonly ILogger _logger; 9 | 10 | public PrivacyModel(ILogger logger) 11 | { 12 | _logger = logger; 13 | } 14 | 15 | public void OnGet() 16 | { 17 | } 18 | } 19 | 20 | -------------------------------------------------------------------------------- /1-IntroducingAspire/BigBadBlog.Web/Pages/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | @ViewData["Title"] - The Big Bad Blog 7 | 8 | 9 | 10 | 11 | 12 |
13 | 33 |
34 |
35 |
36 | @RenderBody() 37 |
38 |
39 | 40 |
41 |
42 | © 2024 - The Big Bad Blog - Privacy - Rendered at: @DateTime.UtcNow 43 |
44 |
45 | 46 | 47 | 48 | 49 | 50 | @await RenderSectionAsync("Scripts", required: false) 51 | 52 | -------------------------------------------------------------------------------- /1-IntroducingAspire/BigBadBlog.Web/Pages/Shared/_Layout.cshtml.css: -------------------------------------------------------------------------------- 1 | /* Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification 2 | for details on configuring this project to bundle and minify static web assets. */ 3 | 4 | a.navbar-brand { 5 | white-space: normal; 6 | text-align: center; 7 | word-break: break-all; 8 | } 9 | 10 | a { 11 | color: #0077cc; 12 | } 13 | 14 | .btn-primary { 15 | color: #fff; 16 | background-color: #1b6ec2; 17 | border-color: #1861ac; 18 | } 19 | 20 | .nav-pills .nav-link.active, .nav-pills .show > .nav-link { 21 | color: #fff; 22 | background-color: #1b6ec2; 23 | border-color: #1861ac; 24 | } 25 | 26 | .border-top { 27 | border-top: 1px solid #e5e5e5; 28 | } 29 | .border-bottom { 30 | border-bottom: 1px solid #e5e5e5; 31 | } 32 | 33 | .box-shadow { 34 | box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05); 35 | } 36 | 37 | button.accept-policy { 38 | font-size: 1rem; 39 | line-height: inherit; 40 | } 41 | 42 | .footer { 43 | position: absolute; 44 | bottom: 0; 45 | width: 100%; 46 | white-space: nowrap; 47 | line-height: 60px; 48 | } 49 | -------------------------------------------------------------------------------- /1-IntroducingAspire/BigBadBlog.Web/Pages/Shared/_LoginPartial.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Identity 2 | @inject SignInManager SignInManager 3 | @inject UserManager UserManager 4 | 5 | 27 | -------------------------------------------------------------------------------- /1-IntroducingAspire/BigBadBlog.Web/Pages/Shared/_ValidationScriptsPartial.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | -------------------------------------------------------------------------------- /1-IntroducingAspire/BigBadBlog.Web/Pages/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Identity 2 | @using BigBadBlog.Web 3 | @using BigBadBlog.Web.Data 4 | @namespace BigBadBlog.Web.Pages 5 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 6 | -------------------------------------------------------------------------------- /1-IntroducingAspire/BigBadBlog.Web/Pages/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /1-IntroducingAspire/BigBadBlog.Web/Posts/FirstPost.md: -------------------------------------------------------------------------------- 1 | --- 2 | Title: This is my first post 3 | Date: 2024-05-15 4 | Author: Arthur The Author 5 | --- 6 | 7 | This is the content of my first post. It's a very interesting post, I promise. 8 | -------------------------------------------------------------------------------- /1-IntroducingAspire/BigBadBlog.Web/Posts/OrderingPizza.md: -------------------------------------------------------------------------------- 1 | --- 2 | Title: I think I'll order some pizza 3 | Date: 2024-05-16 4 | Author: Arthur The Author 5 | --- 6 | 7 | I'm feeling lazy today, so I think I'll order some pizza. I'm not sure what I want yet, but I'll figure it out. I'm thinking about getting a large pepperoni pizza, but I might change my mind. I'll let you know what I decide. 8 | 9 | There's this really cool new pizza shop that opened down the street called Blazing Pizza. I think I'll give them a try 10 | -------------------------------------------------------------------------------- /1-IntroducingAspire/BigBadBlog.Web/Program.cs: -------------------------------------------------------------------------------- 1 | global using BigBadBlog.Web.Data; 2 | 3 | using Microsoft.AspNetCore.Identity; 4 | using Microsoft.EntityFrameworkCore; 5 | using BigBadBlog.Web.Data; 6 | using System.Net; 7 | 8 | var builder = WebApplication.CreateBuilder(args); 9 | 10 | // Add Aspire service defaults 11 | builder.AddServiceDefaults(); 12 | 13 | // Add OutputCache service 14 | builder.AddRedisOutputCache("outputcache"); 15 | builder.Services.AddOutputCache(options => 16 | { 17 | options.AddBasePolicy(policy => policy.Tag("ALL").Expire(TimeSpan.FromMinutes(5))); 18 | options.AddPolicy("Home", policy => policy.Tag("Home").Expire(TimeSpan.FromSeconds(30))); 19 | options.AddPolicy("Post", policy => policy.Tag("Post").SetVaryByRouteValue("slug").Expire(TimeSpan.FromSeconds(30))); 20 | }); 21 | 22 | // Add my repository for posts 23 | builder.Services.AddTransient(); 24 | 25 | // Add services to the container. 26 | var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found."); 27 | builder.Services.AddDbContext(options => 28 | options.UseSqlite(connectionString)); 29 | builder.Services.AddDatabaseDeveloperPageExceptionFilter(); 30 | 31 | builder.Services.AddDefaultIdentity(options => options.SignIn.RequireConfirmedAccount = true) 32 | .AddEntityFrameworkStores(); 33 | builder.Services.AddRazorPages(); 34 | 35 | var app = builder.Build(); 36 | 37 | // Configure the HTTP request pipeline. 38 | if (app.Environment.IsDevelopment()) 39 | { 40 | app.UseMigrationsEndPoint(); 41 | } 42 | else 43 | { 44 | app.UseExceptionHandler("/Error"); 45 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. 46 | app.UseHsts(); 47 | } 48 | 49 | app.Map("/Posts", (HttpContext ctx) => 50 | { 51 | return HttpStatusCode.NotFound; 52 | }); 53 | 54 | app.UseHttpsRedirection(); 55 | app.UseStaticFiles(); 56 | 57 | app.UseRouting(); 58 | 59 | app.UseAuthorization(); 60 | 61 | app.UseOutputCache(); 62 | 63 | app.MapRazorPages(); 64 | 65 | app.Run(); 66 | -------------------------------------------------------------------------------- /1-IntroducingAspire/BigBadBlog.Web/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:51799", 8 | "sslPort": 44354 9 | } 10 | }, 11 | "profiles": { 12 | "http": { 13 | "commandName": "Project", 14 | "dotnetRunMessages": true, 15 | "launchBrowser": true, 16 | "applicationUrl": "http://localhost:5055", 17 | "environmentVariables": { 18 | "ASPNETCORE_ENVIRONMENT": "Development" 19 | } 20 | }, 21 | "https": { 22 | "commandName": "Project", 23 | "dotnetRunMessages": true, 24 | "launchBrowser": true, 25 | "applicationUrl": "https://localhost:7008;http://localhost:5055", 26 | "environmentVariables": { 27 | "ASPNETCORE_ENVIRONMENT": "Development" 28 | } 29 | }, 30 | "IIS Express": { 31 | "commandName": "IISExpress", 32 | "launchBrowser": true, 33 | "environmentVariables": { 34 | "ASPNETCORE_ENVIRONMENT": "Development" 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /1-IntroducingAspire/BigBadBlog.Web/app.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/BigBadBlog/39336acf4e7cb464ecba49487144d5eff190292f/1-IntroducingAspire/BigBadBlog.Web/app.db -------------------------------------------------------------------------------- /1-IntroducingAspire/BigBadBlog.Web/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "DetailedErrors": true, 3 | "Logging": { 4 | "LogLevel": { 5 | "Default": "Information", 6 | "Microsoft.AspNetCore": "Warning" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /1-IntroducingAspire/BigBadBlog.Web/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "DefaultConnection": "DataSource=app.db;Cache=Shared" 4 | }, 5 | "Logging": { 6 | "LogLevel": { 7 | "Default": "Information", 8 | "Microsoft.AspNetCore": "Warning" 9 | } 10 | }, 11 | "AllowedHosts": "*" 12 | } 13 | -------------------------------------------------------------------------------- /1-IntroducingAspire/BigBadBlog.Web/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-size: 14px; 3 | } 4 | 5 | @media (min-width: 768px) { 6 | html { 7 | font-size: 16px; 8 | } 9 | } 10 | 11 | .btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus { 12 | box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb; 13 | } 14 | 15 | html { 16 | position: relative; 17 | min-height: 100%; 18 | } 19 | 20 | body { 21 | margin-bottom: 60px; 22 | } -------------------------------------------------------------------------------- /1-IntroducingAspire/BigBadBlog.Web/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/BigBadBlog/39336acf4e7cb464ecba49487144d5eff190292f/1-IntroducingAspire/BigBadBlog.Web/wwwroot/favicon.ico -------------------------------------------------------------------------------- /1-IntroducingAspire/BigBadBlog.Web/wwwroot/js/site.js: -------------------------------------------------------------------------------- 1 | // Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification 2 | // for details on configuring this project to bundle and minify static web assets. 3 | 4 | // Write your JavaScript code. 5 | -------------------------------------------------------------------------------- /1-IntroducingAspire/BigBadBlog.Web/wwwroot/lib/bootstrap/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2011-2021 Twitter, Inc. 4 | Copyright (c) 2011-2021 The Bootstrap Authors 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /1-IntroducingAspire/BigBadBlog.Web/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) .NET Foundation and Contributors 4 | 5 | All rights reserved. 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /1-IntroducingAspire/BigBadBlog.Web/wwwroot/lib/jquery-validation/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright Jörn Zaefferer 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /1-IntroducingAspire/BigBadBlog.Web/wwwroot/lib/jquery/LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Copyright OpenJS Foundation and other contributors, https://openjsf.org/ 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /1-IntroducingAspire/BigBadBlog.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31903.59 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BigBadBlog.Web", "BigBadBlog.Web\BigBadBlog.Web.csproj", "{0FD2607E-0983-406E-A078-2A681860117B}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BigBadBlog.AppHost", "BigBadBlog.AppHost\BigBadBlog.AppHost.csproj", "{BA2877DF-4349-4BD6-8712-36B1F58DC913}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BigBadBlog.ServiceDefaults", "BigBadBlog.ServiceDefaults\BigBadBlog.ServiceDefaults.csproj", "{87CB57AD-2754-4F87-8D46-21E7CA1F4161}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {0FD2607E-0983-406E-A078-2A681860117B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {0FD2607E-0983-406E-A078-2A681860117B}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {0FD2607E-0983-406E-A078-2A681860117B}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {0FD2607E-0983-406E-A078-2A681860117B}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {BA2877DF-4349-4BD6-8712-36B1F58DC913}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {BA2877DF-4349-4BD6-8712-36B1F58DC913}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {BA2877DF-4349-4BD6-8712-36B1F58DC913}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {BA2877DF-4349-4BD6-8712-36B1F58DC913}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {87CB57AD-2754-4F87-8D46-21E7CA1F4161}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {87CB57AD-2754-4F87-8D46-21E7CA1F4161}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {87CB57AD-2754-4F87-8D46-21E7CA1F4161}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {87CB57AD-2754-4F87-8D46-21E7CA1F4161}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {21CED12B-C9F6-45B0-A0C0-CAC9438E9841} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /1-IntroducingAspire/README.md: -------------------------------------------------------------------------------- 1 | # 1 - Introducing Aspire 2 | 3 | Well... that was a LOT of changes and [updates we've implemented](../docs/1-Introduction.md) at this point. 4 | 5 | - The blog engine now runs in a .NET Aspire system that's started from within the `BigBadBlog.AppHost` project. 6 | - We've added a Redis database to the system 7 | - The Redis database is connected to the web project and used as an OutputCache so the blog doesn't need to re-render blog posts all the time 8 | - We added a RedisCommander instance to allow us to look inside the Redis database 9 | 10 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.AppHost/BigBadBlog.AppHost.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | true 9 | 3e9797fc-af6f-46a6-a999-4ebc8157b5de 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.AppHost/Program.cs: -------------------------------------------------------------------------------- 1 | using BigBadBlog; 2 | using Projects; 3 | 4 | var builder = DistributedApplication.CreateBuilder(args); 5 | 6 | #region Posts Database 7 | 8 | var postgresPassword = builder.AddParameter("postgresspassword", secret: true); 9 | var db = builder.AddPostgres(ServiceNames.DATABASE_POSTS.SERVERNAME, password: postgresPassword) 10 | .WithDataVolume() 11 | .AddDatabase(ServiceNames.DATABASE_POSTS.NAME); 12 | 13 | var migrationService = builder.AddProject(ServiceNames.MIGRATION) 14 | .WithReference(db); 15 | 16 | #endregion 17 | 18 | #region Redis Cache 19 | 20 | var cache = builder.AddRedis(ServiceNames.OUTPUTCACHE, 65028) 21 | .WithRedisCommander(); 22 | 23 | #endregion 24 | 25 | #region Web Application 26 | 27 | var webApp = builder.AddProject("web") 28 | .WithReference(cache) 29 | .WithReference(db) 30 | .WithExternalHttpEndpoints(); 31 | 32 | #endregion 33 | 34 | builder.Build().Run(); 35 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.AppHost/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "profiles": { 4 | "https": { 5 | "commandName": "Project", 6 | "dotnetRunMessages": true, 7 | "launchBrowser": true, 8 | "applicationUrl": "https://localhost:17164;http://localhost:15115", 9 | "environmentVariables": { 10 | "ASPNETCORE_ENVIRONMENT": "Development", 11 | "DOTNET_ENVIRONMENT": "Development", 12 | "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21248", 13 | "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22194" 14 | } 15 | }, 16 | "http": { 17 | "commandName": "Project", 18 | "dotnetRunMessages": true, 19 | "launchBrowser": true, 20 | "applicationUrl": "http://localhost:15115", 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development", 23 | "DOTNET_ENVIRONMENT": "Development", 24 | "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19289", 25 | "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20184" 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.AppHost/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.AppHost/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning", 6 | "Aspire.Hosting.Dcp": "Warning" 7 | } 8 | }, 9 | "Parameters":{ 10 | "postgresspassword": "MyP@55w0rd!" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Common/BigBadBlog.Common.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Common/Constants.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace BigBadBlog; 8 | 9 | public static class ServiceNames 10 | { 11 | 12 | /// 13 | /// Constants for referencing the database containing blog posts 14 | /// 15 | public static class DATABASE_POSTS { 16 | 17 | public const string SERVERNAME = "bigbad-db"; 18 | public const string NAME = "bigbad-database"; 19 | 20 | } 21 | 22 | public const string MIGRATION = "database-migration"; 23 | 24 | public const string OUTPUTCACHE = "outputcache"; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Common/Data/IPostRepository.cs: -------------------------------------------------------------------------------- 1 | namespace BigBadBlog.Common.Data; 2 | 3 | /// 4 | /// A definition of a simple interaction with blog posts 5 | /// 6 | public interface IPostRepository 7 | { 8 | 9 | Task> GetPostsAsync(int count, int page); 10 | 11 | Task<(PostMetadata,string)> GetPostAsync(string slug); 12 | 13 | Task AddPostAsync(PostMetadata metadata, string content); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Common/Data/PostMetadata.cs: -------------------------------------------------------------------------------- 1 | namespace BigBadBlog.Common.Data; 2 | 3 | public record PostMetadata(string Filename, string Title, string Author, DateTime Date) 4 | { 5 | 6 | public string Slug => Uri.EscapeDataString(Title.ToLower()); 7 | 8 | } -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Data.Postgres/ApplicationDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore; 3 | 4 | namespace BigBadBlog.Data.Postgres; 5 | 6 | public class ApplicationDbContext : IdentityDbContext 7 | { 8 | public ApplicationDbContext(DbContextOptions options) 9 | : base(options) 10 | { 11 | } 12 | 13 | public DbSet Posts { get; set; } 14 | 15 | protected override void OnModelCreating(ModelBuilder builder) 16 | { 17 | 18 | // Add an index to the PgPost slug column 19 | builder.Entity() 20 | .HasIndex(p => p.Slug) 21 | .IsUnique(); 22 | 23 | // Add an index to the PgPost date column 24 | builder.Entity() 25 | .HasIndex(p => p.Date); 26 | 27 | base.OnModelCreating(builder); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Data.Postgres/BigBadBlog.Data.Postgres.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Data.Postgres/PgPost.cs: -------------------------------------------------------------------------------- 1 | using BigBadBlog.Common.Data; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace BigBadBlog.Data.Postgres; 5 | 6 | public class PgPost 7 | { 8 | 9 | [Key] 10 | public int Id { get; set; } 11 | 12 | [Required, MaxLength(100)] 13 | public string Title { get; set; } 14 | 15 | [Required, MaxLength(100)] 16 | public string Author { get; set; } 17 | 18 | [Required] 19 | public DateTime Date { get; set; } 20 | 21 | [Required] 22 | public string Content { get; set; } 23 | 24 | [Required, MaxLength(150)] 25 | public string Slug { get; set; } 26 | 27 | // explicit conversion to PostMetadata 28 | public static explicit operator PostMetadata(PgPost post) 29 | { 30 | return new PostMetadata("", post.Title, post.Author, post.Date); 31 | } 32 | 33 | // explicit conversation from PostMetadata 34 | public static explicit operator PgPost((PostMetadata, string) post) 35 | { 36 | return new PgPost 37 | { 38 | Title = post.Item1.Title, 39 | Author = post.Item1.Author, 40 | Date = post.Item1.Date, 41 | Content = post.Item2, 42 | Slug = post.Item1.Slug 43 | }; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Data.Postgres/PgPostRepository.cs: -------------------------------------------------------------------------------- 1 | using BigBadBlog.Common.Data; 2 | using Microsoft.EntityFrameworkCore; 3 | 4 | namespace BigBadBlog.Data.Postgres; 5 | 6 | internal class PgPostRepository : IPostRepository 7 | { 8 | private readonly ApplicationDbContext _context; 9 | 10 | public PgPostRepository(ApplicationDbContext context) 11 | { 12 | _context = context; 13 | } 14 | 15 | public async Task<(PostMetadata, string)> GetPostAsync(string slug) 16 | { 17 | var post = await _context.Posts 18 | .Where(p => p.Slug == slug) 19 | .Select(p => (PostMetadata)p) 20 | .FirstOrDefaultAsync(); 21 | 22 | if (post == default) 23 | { 24 | return default; 25 | } 26 | 27 | var content = await _context.Posts 28 | .Where(p => p.Slug == slug) 29 | .Select(p => p.Content) 30 | .FirstOrDefaultAsync(); 31 | 32 | return (post, content); 33 | } 34 | 35 | public async Task> GetPostsAsync(int count, int page) 36 | { 37 | // fetch the posts from the database and convert them to PostMetadata 38 | var posts = await _context.Posts 39 | .OrderByDescending(p => p.Date) 40 | .Skip(count * (page - 1)) 41 | .Take(count) 42 | .ToListAsync(); 43 | 44 | return posts.Select(p => ((PostMetadata)p, p.Content)); 45 | 46 | } 47 | 48 | public async Task AddPostAsync(PostMetadata metadata, string content) 49 | { 50 | var post = (PgPost)(metadata, content); 51 | 52 | post.Date = new DateTime(DateTime.Now.Ticks, DateTimeKind.Utc); 53 | _context.Posts.Add(post); 54 | 55 | await _context.SaveChangesAsync(); 56 | } 57 | 58 | public async Task UpdatePostAsync(PostMetadata metadata, string content) 59 | { 60 | var post = (PgPost)(metadata, content); 61 | 62 | _context.Posts.Update(post); 63 | 64 | await _context.SaveChangesAsync(); 65 | } 66 | 67 | public async Task DeletePostAsync(string slug) 68 | { 69 | var post = await _context.Posts 70 | .Where(p => p.Slug == slug) 71 | .FirstOrDefaultAsync(); 72 | 73 | if (post != default) 74 | { 75 | _context.Posts.Remove(post); 76 | await _context.SaveChangesAsync(); 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Data.Postgres/Program_Extensions.cs: -------------------------------------------------------------------------------- 1 | using BigBadBlog.Common.Data; 2 | using BigBadBlog.Data.Postgres; 3 | using Microsoft.AspNetCore.Identity; 4 | using Microsoft.Extensions.Configuration; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Microsoft.Extensions.Hosting; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | 13 | namespace BigBadBlog; 14 | 15 | public static class Program_Extensions 16 | { 17 | 18 | public static IHostApplicationBuilder? AddPostgresDatabaseServices(this IHostApplicationBuilder? host) 19 | { 20 | 21 | host.AddNpgsqlDbContext(ServiceNames.DATABASE_POSTS.NAME); 22 | 23 | host.Services.AddScoped(); 24 | 25 | return host; 26 | 27 | } 28 | 29 | public static IHostApplicationBuilder? AddIdentityServices(this IHostApplicationBuilder? builder) 30 | { 31 | 32 | 33 | builder.Services.AddDefaultIdentity(options => options.SignIn.RequireConfirmedAccount = true) 34 | .AddEntityFrameworkStores(); 35 | 36 | return builder; 37 | 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Service.DatabaseMigration/BigBadBlog.Service.DatabaseMigration.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | dotnet-BigBadBlog.Service.DatabaseMigration-a0576ffb-d599-4117-9441-d034f300a0d7 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Service.DatabaseMigration/Program.cs: -------------------------------------------------------------------------------- 1 | using BigBadBlog; 2 | using BigBadBlog.Data.Postgres; 3 | using BigBadBlog.Service.DatabaseMigration; 4 | using Microsoft.EntityFrameworkCore; 5 | 6 | var builder = Host.CreateApplicationBuilder(args); 7 | 8 | builder.AddServiceDefaults(); 9 | 10 | builder.Services.AddHostedService(); 11 | 12 | builder.Services.AddDbContext( 13 | options => options.UseNpgsql(builder.Configuration.GetConnectionString(ServiceNames.DATABASE_POSTS.NAME)) 14 | ); 15 | 16 | builder.Services.AddOpenTelemetry() 17 | .WithTracing(tracing => tracing.AddSource(Worker.ActivityName)); 18 | 19 | var host = builder.Build(); 20 | host.Run(); 21 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Service.DatabaseMigration/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "profiles": { 4 | "BigBadBlog.Service.DatabaseMigration": { 5 | "commandName": "Project", 6 | "dotnetRunMessages": true, 7 | "environmentVariables": { 8 | "DOTNET_ENVIRONMENT": "Development" 9 | } 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Service.DatabaseMigration/Worker.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using BigBadBlog.Data.Postgres; 3 | using Microsoft.EntityFrameworkCore; 4 | using OpenTelemetry.Trace; 5 | 6 | namespace BigBadBlog.Service.DatabaseMigration; 7 | 8 | public class Worker : BackgroundService 9 | { 10 | private readonly IServiceProvider serviceProvider; 11 | private readonly IHostApplicationLifetime hostApplicationLifetime; 12 | private readonly ILogger _logger; 13 | 14 | internal const string ActivityName = "MigrationService"; 15 | private static readonly ActivitySource _activitySource = new(ActivityName); 16 | 17 | public Worker(IServiceProvider serviceProvider, 18 | IHostApplicationLifetime hostApplicationLifetime, 19 | ILogger logger) 20 | { 21 | this.serviceProvider = serviceProvider; 22 | this.hostApplicationLifetime = hostApplicationLifetime; 23 | _logger = logger; 24 | } 25 | 26 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 27 | { 28 | using var activity = _activitySource.StartActivity("Migrating database", ActivityKind.Client); 29 | 30 | try 31 | { 32 | using var scope = serviceProvider.CreateScope(); 33 | var dbContext = scope.ServiceProvider.GetRequiredService(); 34 | 35 | await dbContext.Database.MigrateAsync(stoppingToken); 36 | 37 | } 38 | catch (Exception ex) 39 | { 40 | activity?.RecordException(ex); 41 | throw; 42 | } 43 | 44 | hostApplicationLifetime.StopApplication(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Service.DatabaseMigration/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.Hosting.Lifetime": "Information" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Service.DatabaseMigration/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.Hosting.Lifetime": "Information" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.ServiceDefaults/BigBadBlog.ServiceDefaults.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Web/Areas/Identity/Pages/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "/Pages/Shared/_Layout.cshtml"; 3 | } 4 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Web/BigBadBlog.Web.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | aspnet-BigBadBlog.Web-c0b48993-c310-4519-8acf-b5b7242032d2 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | all 23 | runtime; build; native; contentfiles; analyzers; buildtransitive 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Web/Pages/Error.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ErrorModel 3 | @{ 4 | ViewData["Title"] = "Error"; 5 | } 6 | 7 |

Error.

8 |

An error occurred while processing your request.

9 | 10 | @if (Model.ShowRequestId) 11 | { 12 |

13 | Request ID: @Model.RequestId 14 |

15 | } 16 | 17 |

Development Mode

18 |

19 | Swapping to the Development environment displays detailed information about the error that occurred. 20 |

21 |

22 | The Development environment shouldn't be enabled for deployed applications. 23 | It can result in displaying sensitive information from exceptions to end users. 24 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development 25 | and restarting the app. 26 |

27 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Web/Pages/Error.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.AspNetCore.Mvc.RazorPages; 4 | 5 | namespace BigBadBlog.Web.Pages; 6 | 7 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] 8 | [IgnoreAntiforgeryToken] 9 | public class ErrorModel : PageModel 10 | { 11 | public string? RequestId { get; set; } 12 | 13 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 14 | 15 | private readonly ILogger _logger; 16 | 17 | public ErrorModel(ILogger logger) 18 | { 19 | _logger = logger; 20 | } 21 | 22 | public void OnGet() 23 | { 24 | RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Web/Pages/Index.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @using Microsoft.AspNetCore.Components 3 | @model IndexModel 4 | @{ 5 | ViewData["Title"] = "Home page"; 6 | } 7 | 8 |
9 |

Welcome to the Big Bad Blog

10 |
11 | 12 | @foreach (var post in Model.Posts) 13 | { 14 |
15 |
16 |
@post.Metadata.Title
17 |
by @post.Metadata.Author on @post.Metadata.Date.ToShortDateString()
18 |

@Html.Raw(Markdig.Markdown.ToHtml(post.Content, Model.MarkdownPipeline))

19 |
20 |
21 | } -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Web/Pages/Index.cshtml.cs: -------------------------------------------------------------------------------- 1 | using BigBadBlog.Common.Data; 2 | using Markdig; 3 | using Microsoft.AspNetCore.Mvc; 4 | using Microsoft.AspNetCore.Mvc.RazorPages; 5 | using Microsoft.AspNetCore.OutputCaching; 6 | 7 | namespace BigBadBlog.Web.Pages; 8 | 9 | [OutputCache(PolicyName = "Home")] 10 | public class IndexModel : PageModel 11 | { 12 | private readonly ILogger _logger; 13 | public readonly MarkdownPipeline MarkdownPipeline; 14 | private readonly IPostRepository _postRepository; 15 | public IEnumerable<(PostMetadata Metadata, string Content)> Posts; 16 | 17 | public IndexModel(IPostRepository postRepository, ILogger logger) 18 | { 19 | _postRepository = postRepository; 20 | _logger = logger; 21 | 22 | MarkdownPipeline = new MarkdownPipelineBuilder() 23 | .UseYamlFrontMatter() 24 | .Build(); 25 | 26 | } 27 | 28 | public async Task OnGetAsync() 29 | { 30 | 31 | await Task.Delay(TimeSpan.FromSeconds(1)); 32 | 33 | Posts = await _postRepository.GetPostsAsync(10, 1); 34 | 35 | return Page(); 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Web/Pages/Post.cshtml: -------------------------------------------------------------------------------- 1 | @page "/p/{slug}" 2 | @model BigBadBlog.Web.Pages.PostModel 3 | @{ 4 | } 5 | 6 | 7 |
@Model.Post.Metadata.Title
8 |
by @Model.Post.Metadata.Author on @Model.Post.Metadata.Date.ToShortDateString()
9 |

@Html.Raw(Markdig.Markdown.ToHtml(Model.Post.Content, Model.MarkdownPipeline))

10 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Web/Pages/Post.cshtml.cs: -------------------------------------------------------------------------------- 1 | using BigBadBlog.Common.Data; 2 | using BigBadBlog.Web.Data; 3 | using Markdig; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.AspNetCore.Mvc.RazorPages; 6 | using Microsoft.AspNetCore.OutputCaching; 7 | using Microsoft.Extensions.Hosting; 8 | 9 | namespace BigBadBlog.Web.Pages; 10 | 11 | [OutputCache(PolicyName = "Post")] 12 | public class PostModel : PageModel 13 | { 14 | private readonly IPostRepository _postRepository; 15 | public readonly MarkdownPipeline MarkdownPipeline; 16 | 17 | public PostModel(IPostRepository postRepository, IWebHostEnvironment host) 18 | { 19 | _postRepository = postRepository; 20 | 21 | MarkdownPipeline = new MarkdownPipelineBuilder() 22 | .UseYamlFrontMatter() 23 | .Build(); 24 | 25 | } 26 | 27 | public (PostMetadata Metadata, string Content) Post { get; private set; } 28 | 29 | public async Task OnGetAsync(string slug) 30 | { 31 | 32 | Post = await _postRepository.GetPostAsync(slug); 33 | 34 | if (Post == default) 35 | { 36 | return NotFound(); 37 | } 38 | 39 | return Page(); 40 | 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Web/Pages/Privacy.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model PrivacyModel 3 | @{ 4 | ViewData["Title"] = "Privacy Policy"; 5 | } 6 |

@ViewData["Title"]

7 | 8 |

Use this page to detail your site's privacy policy.

9 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Web/Pages/Privacy.cshtml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.AspNetCore.Mvc.RazorPages; 3 | 4 | namespace BigBadBlog.Web.Pages; 5 | 6 | public class PrivacyModel : PageModel 7 | { 8 | private readonly ILogger _logger; 9 | 10 | public PrivacyModel(ILogger logger) 11 | { 12 | _logger = logger; 13 | } 14 | 15 | public void OnGet() 16 | { 17 | } 18 | } 19 | 20 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Web/Pages/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | @ViewData["Title"] - The Big Bad Blog 7 | 8 | 9 | 10 | 11 | 12 |
13 | 33 |
34 |
35 |
36 | @RenderBody() 37 |
38 |
39 | 40 |
41 |
42 | © 2024 - The Big Bad Blog - Privacy - Rendered at: @DateTime.UtcNow 43 |
44 |
45 | 46 | 47 | 48 | 49 | 50 | @await RenderSectionAsync("Scripts", required: false) 51 | 52 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Web/Pages/Shared/_Layout.cshtml.css: -------------------------------------------------------------------------------- 1 | /* Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification 2 | for details on configuring this project to bundle and minify static web assets. */ 3 | 4 | a.navbar-brand { 5 | white-space: normal; 6 | text-align: center; 7 | word-break: break-all; 8 | } 9 | 10 | a { 11 | color: #0077cc; 12 | } 13 | 14 | .btn-primary { 15 | color: #fff; 16 | background-color: #1b6ec2; 17 | border-color: #1861ac; 18 | } 19 | 20 | .nav-pills .nav-link.active, .nav-pills .show > .nav-link { 21 | color: #fff; 22 | background-color: #1b6ec2; 23 | border-color: #1861ac; 24 | } 25 | 26 | .border-top { 27 | border-top: 1px solid #e5e5e5; 28 | } 29 | .border-bottom { 30 | border-bottom: 1px solid #e5e5e5; 31 | } 32 | 33 | .box-shadow { 34 | box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05); 35 | } 36 | 37 | button.accept-policy { 38 | font-size: 1rem; 39 | line-height: inherit; 40 | } 41 | 42 | .footer { 43 | position: absolute; 44 | bottom: 0; 45 | width: 100%; 46 | white-space: nowrap; 47 | line-height: 60px; 48 | } 49 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Web/Pages/Shared/_LoginPartial.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Identity 2 | @inject SignInManager SignInManager 3 | @inject UserManager UserManager 4 | 5 | 27 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Web/Pages/Shared/_ValidationScriptsPartial.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Web/Pages/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Identity 2 | @using BigBadBlog.Common 3 | @using BigBadBlog.Common.Data 4 | @using BigBadBlog.Web 5 | @using BigBadBlog.Web.Data 6 | @namespace BigBadBlog.Web.Pages 7 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 8 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Web/Pages/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Web/Posts/FirstPost.md: -------------------------------------------------------------------------------- 1 | --- 2 | Title: This is my first post 3 | Date: 2024-05-15 4 | Author: Arthur The Author 5 | --- 6 | 7 | This is the content of my first post. It's a very interesting post, I promise. 8 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Web/Posts/OrderingPizza.md: -------------------------------------------------------------------------------- 1 | --- 2 | Title: I think I'll order some pizza 3 | Date: 2024-05-16 4 | Author: Arthur The Author 5 | --- 6 | 7 | I'm feeling lazy today, so I think I'll order some pizza. I'm not sure what I want yet, but I'll figure it out. I'm thinking about getting a large pepperoni pizza, but I might change my mind. I'll let you know what I decide. 8 | 9 | There's this really cool new pizza shop that opened down the street called Blazing Pizza. I think I'll give them a try 10 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Web/Program.cs: -------------------------------------------------------------------------------- 1 | global using BigBadBlog.Web.Data; 2 | 3 | using Microsoft.AspNetCore.Identity; 4 | using Microsoft.EntityFrameworkCore; 5 | using BigBadBlog.Web.Data; 6 | using System.Net; 7 | using BigBadBlog; 8 | using BigBadBlog.Common.Data; 9 | 10 | var builder = WebApplication.CreateBuilder(args); 11 | 12 | // Add Aspire service defaults 13 | builder.AddServiceDefaults(); 14 | 15 | builder.AddPostgresDatabaseServices(); 16 | 17 | // Add OutputCache service 18 | builder.AddRedisOutputCache(ServiceNames.OUTPUTCACHE); 19 | builder.Services.AddOutputCache(options => 20 | { 21 | options.AddBasePolicy(policy => policy.Tag("ALL").Expire(TimeSpan.FromMinutes(5))); 22 | options.AddPolicy("Home", policy => policy.Tag("Home").Expire(TimeSpan.FromSeconds(30))); 23 | options.AddPolicy("Post", policy => policy.Tag("Post").SetVaryByRouteValue("slug").Expire(TimeSpan.FromSeconds(30))); 24 | }); 25 | 26 | // Add services to the container. 27 | builder.AddIdentityServices(); 28 | 29 | builder.Services.AddRazorPages(); 30 | 31 | var app = builder.Build(); 32 | 33 | // Configure the HTTP request pipeline. 34 | if (app.Environment.IsDevelopment()) 35 | { 36 | app.UseMigrationsEndPoint(); 37 | } 38 | else 39 | { 40 | app.UseExceptionHandler("/Error"); 41 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. 42 | app.UseHsts(); 43 | } 44 | 45 | app.Map("/Posts", (HttpContext ctx) => 46 | { 47 | return HttpStatusCode.NotFound; 48 | }); 49 | 50 | app.UseHttpsRedirection(); 51 | app.UseStaticFiles(); 52 | 53 | app.UseRouting(); 54 | 55 | app.UseAuthorization(); 56 | 57 | app.UseOutputCache(); 58 | 59 | app.MapRazorPages(); 60 | 61 | var mdRepo = new MarkdownPostRepository(); 62 | var pgRepo = app.Services.CreateScope().ServiceProvider.GetRequiredService(); 63 | 64 | var pgPosts = await pgRepo.GetPostsAsync(10, 1); 65 | 66 | if (!pgPosts.Any()) 67 | { 68 | var existingPosts = await mdRepo.GetPostsAsync(10,1); 69 | foreach (var post in existingPosts) 70 | { 71 | await pgRepo.AddPostAsync(post.Item1, post.Item2); 72 | } 73 | } 74 | 75 | app.Run(); 76 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Web/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:51799", 8 | "sslPort": 44354 9 | } 10 | }, 11 | "profiles": { 12 | "http": { 13 | "commandName": "Project", 14 | "dotnetRunMessages": true, 15 | "launchBrowser": true, 16 | "applicationUrl": "http://localhost:5055", 17 | "environmentVariables": { 18 | "ASPNETCORE_ENVIRONMENT": "Development" 19 | } 20 | }, 21 | "https": { 22 | "commandName": "Project", 23 | "dotnetRunMessages": true, 24 | "launchBrowser": true, 25 | "applicationUrl": "https://localhost:7008;http://localhost:5055", 26 | "environmentVariables": { 27 | "ASPNETCORE_ENVIRONMENT": "Development" 28 | } 29 | }, 30 | "IIS Express": { 31 | "commandName": "IISExpress", 32 | "launchBrowser": true, 33 | "environmentVariables": { 34 | "ASPNETCORE_ENVIRONMENT": "Development" 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Web/app.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/BigBadBlog/39336acf4e7cb464ecba49487144d5eff190292f/2-DatabaseMigrationAndEF/BigBadBlog.Web/app.db -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Web/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "DetailedErrors": true, 3 | "Logging": { 4 | "LogLevel": { 5 | "Default": "Information", 6 | "Microsoft.AspNetCore": "Warning" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Web/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "DefaultConnection": "DataSource=app.db;Cache=Shared" 4 | }, 5 | "Logging": { 6 | "LogLevel": { 7 | "Default": "Information", 8 | "Microsoft.AspNetCore": "Warning" 9 | } 10 | }, 11 | "AllowedHosts": "*" 12 | } 13 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Web/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-size: 14px; 3 | } 4 | 5 | @media (min-width: 768px) { 6 | html { 7 | font-size: 16px; 8 | } 9 | } 10 | 11 | .btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus { 12 | box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb; 13 | } 14 | 15 | html { 16 | position: relative; 17 | min-height: 100%; 18 | } 19 | 20 | body { 21 | margin-bottom: 60px; 22 | } -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Web/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/BigBadBlog/39336acf4e7cb464ecba49487144d5eff190292f/2-DatabaseMigrationAndEF/BigBadBlog.Web/wwwroot/favicon.ico -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Web/wwwroot/js/site.js: -------------------------------------------------------------------------------- 1 | // Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification 2 | // for details on configuring this project to bundle and minify static web assets. 3 | 4 | // Write your JavaScript code. 5 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Web/wwwroot/lib/bootstrap/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2011-2021 Twitter, Inc. 4 | Copyright (c) 2011-2021 The Bootstrap Authors 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Web/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) .NET Foundation and Contributors 4 | 5 | All rights reserved. 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Web/wwwroot/lib/jquery-validation/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright Jörn Zaefferer 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /2-DatabaseMigrationAndEF/BigBadBlog.Web/wwwroot/lib/jquery/LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Copyright OpenJS Foundation and other contributors, https://openjsf.org/ 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.AppHost/BigBadBlog.AppHost.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | true 9 | 3e9797fc-af6f-46a6-a999-4ebc8157b5de 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.AppHost/Program.cs: -------------------------------------------------------------------------------- 1 | using BigBadBlog; 2 | using Projects; 3 | 4 | var builder = DistributedApplication.CreateBuilder(args); 5 | 6 | #region Posts Database 7 | 8 | var postgresPassword = builder.AddParameter("postgresspassword", secret: true); 9 | var db = builder.AddPostgres(ServiceNames.DATABASE_POSTS.SERVERNAME, password: postgresPassword) 10 | .WithDataVolume() 11 | .AddDatabase(ServiceNames.DATABASE_POSTS.NAME); 12 | 13 | var migrationService = builder.AddProject(ServiceNames.MIGRATION) 14 | .WithReference(db); 15 | 16 | #endregion 17 | 18 | #region Redis Cache 19 | 20 | var cache = builder.AddRedis(ServiceNames.OUTPUTCACHE, 65028) 21 | .WithRedisCommander(); 22 | 23 | #endregion 24 | 25 | #region Post API 26 | 27 | var postDb = builder.AddMongoDB("postDb") 28 | .WithDataVolume() 29 | .WithMongoExpress() 30 | .AddDatabase("posts-database"); 31 | 32 | var api = builder.AddProject("postApi", @"..\..\..\BigBadBlog.PostService\BigBadBlog.PostService.Api\BigBadBlog.PostService.Api.csproj") 33 | .WithReference(postDb); 34 | 35 | #endregion 36 | 37 | #region Web Application 38 | 39 | var webApp = builder.AddProject("web") 40 | .WithReference(cache) 41 | .WithReference(db) 42 | .WithReference(api) 43 | .WithExternalHttpEndpoints(); 44 | 45 | #endregion 46 | 47 | builder.Build().Run(); 48 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.AppHost/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "profiles": { 4 | "https": { 5 | "commandName": "Project", 6 | "dotnetRunMessages": true, 7 | "launchBrowser": true, 8 | "applicationUrl": "https://localhost:17164;http://localhost:15115", 9 | "environmentVariables": { 10 | "ASPNETCORE_ENVIRONMENT": "Development", 11 | "DOTNET_ENVIRONMENT": "Development", 12 | "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21248", 13 | "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22194" 14 | } 15 | }, 16 | "http": { 17 | "commandName": "Project", 18 | "dotnetRunMessages": true, 19 | "launchBrowser": true, 20 | "applicationUrl": "http://localhost:15115", 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development", 23 | "DOTNET_ENVIRONMENT": "Development", 24 | "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19289", 25 | "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20184" 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.AppHost/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.AppHost/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning", 6 | "Aspire.Hosting.Dcp": "Warning" 7 | } 8 | }, 9 | "Parameters":{ 10 | "postgresspassword": "MyP@55w0rd!" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Common/BigBadBlog.Common.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Common/Constants.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace BigBadBlog; 8 | 9 | public static class ServiceNames 10 | { 11 | 12 | /// 13 | /// Constants for referencing the database containing blog posts 14 | /// 15 | public static class DATABASE_POSTS { 16 | 17 | public const string SERVERNAME = "posts"; 18 | public const string NAME = "post-database"; 19 | 20 | } 21 | 22 | public const string MIGRATION = "database-migration"; 23 | 24 | public const string OUTPUTCACHE = "outputcache"; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Common/Data/IPostRepository.cs: -------------------------------------------------------------------------------- 1 | namespace BigBadBlog.Common.Data; 2 | 3 | /// 4 | /// A definition of a simple interaction with blog posts 5 | /// 6 | public interface IPostRepository 7 | { 8 | 9 | Task> GetPostsAsync(int count, int page); 10 | 11 | Task<(PostMetadata,string)> GetPostAsync(string slug); 12 | 13 | Task AddPostAsync(PostMetadata metadata, string content); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Common/Data/PostMetadata.cs: -------------------------------------------------------------------------------- 1 | namespace BigBadBlog.Common.Data; 2 | 3 | public record PostMetadata(string Filename, string Title, string Author, DateTime Date) 4 | { 5 | 6 | public string Slug => Uri.EscapeDataString(Title.ToLower()); 7 | 8 | } -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Data.Postgres/ApplicationDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore; 3 | 4 | namespace BigBadBlog.Data.Postgres; 5 | 6 | public class ApplicationDbContext : IdentityDbContext 7 | { 8 | public ApplicationDbContext(DbContextOptions options) 9 | : base(options) 10 | { 11 | } 12 | 13 | public DbSet Posts { get; set; } 14 | 15 | protected override void OnModelCreating(ModelBuilder builder) 16 | { 17 | 18 | // Add an index to the PgPost slug column 19 | builder.Entity() 20 | .HasIndex(p => p.Slug) 21 | .IsUnique(); 22 | 23 | // Add an index to the PgPost date column 24 | builder.Entity() 25 | .HasIndex(p => p.Date); 26 | 27 | base.OnModelCreating(builder); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Data.Postgres/BigBadBlog.Data.Postgres.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Data.Postgres/PgPost.cs: -------------------------------------------------------------------------------- 1 | using BigBadBlog.Common.Data; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace BigBadBlog.Data.Postgres; 5 | 6 | public class PgPost 7 | { 8 | 9 | [Key] 10 | public int Id { get; set; } 11 | 12 | [Required, MaxLength(100)] 13 | public string Title { get; set; } 14 | 15 | [Required, MaxLength(100)] 16 | public string Author { get; set; } 17 | 18 | [Required] 19 | public DateTime Date { get; set; } 20 | 21 | [Required] 22 | public string Content { get; set; } 23 | 24 | [Required, MaxLength(150)] 25 | public string Slug { get; set; } 26 | 27 | // explicit conversion to PostMetadata 28 | public static explicit operator PostMetadata(PgPost post) 29 | { 30 | return new PostMetadata("", post.Title, post.Author, post.Date); 31 | } 32 | 33 | // explicit conversation from PostMetadata 34 | public static explicit operator PgPost((PostMetadata, string) post) 35 | { 36 | return new PgPost 37 | { 38 | Title = post.Item1.Title, 39 | Author = post.Item1.Author, 40 | Date = post.Item1.Date, 41 | Content = post.Item2, 42 | Slug = post.Item1.Slug 43 | }; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Data.Postgres/PgPostRepository.cs: -------------------------------------------------------------------------------- 1 | using BigBadBlog.Common.Data; 2 | using Microsoft.EntityFrameworkCore; 3 | 4 | namespace BigBadBlog.Data.Postgres; 5 | 6 | internal class PgPostRepository : IPostRepository 7 | { 8 | private readonly ApplicationDbContext _context; 9 | 10 | public PgPostRepository(ApplicationDbContext context) 11 | { 12 | _context = context; 13 | } 14 | 15 | public async Task<(PostMetadata, string)> GetPostAsync(string slug) 16 | { 17 | var post = await _context.Posts 18 | .Where(p => p.Slug == slug) 19 | .Select(p => (PostMetadata)p) 20 | .FirstOrDefaultAsync(); 21 | 22 | if (post == default) 23 | { 24 | return default; 25 | } 26 | 27 | var content = await _context.Posts 28 | .Where(p => p.Slug == slug) 29 | .Select(p => p.Content) 30 | .FirstOrDefaultAsync(); 31 | 32 | return (post, content); 33 | } 34 | 35 | public async Task> GetPostsAsync(int count, int page) 36 | { 37 | // fetch the posts from the database and convert them to PostMetadata 38 | var posts = await _context.Posts 39 | .OrderByDescending(p => p.Date) 40 | .Skip(count * (page - 1)) 41 | .Take(count) 42 | .ToListAsync(); 43 | 44 | return posts.Select(p => ((PostMetadata)p, p.Content)); 45 | 46 | } 47 | 48 | public async Task AddPostAsync(PostMetadata metadata, string content) 49 | { 50 | var post = (PgPost)(metadata, content); 51 | 52 | post.Date = new DateTime(DateTime.Now.Ticks, DateTimeKind.Utc); 53 | _context.Posts.Add(post); 54 | 55 | await _context.SaveChangesAsync(); 56 | } 57 | 58 | public async Task UpdatePostAsync(PostMetadata metadata, string content) 59 | { 60 | var post = (PgPost)(metadata, content); 61 | 62 | _context.Posts.Update(post); 63 | 64 | await _context.SaveChangesAsync(); 65 | } 66 | 67 | public async Task DeletePostAsync(string slug) 68 | { 69 | var post = await _context.Posts 70 | .Where(p => p.Slug == slug) 71 | .FirstOrDefaultAsync(); 72 | 73 | if (post != default) 74 | { 75 | _context.Posts.Remove(post); 76 | await _context.SaveChangesAsync(); 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Data.Postgres/Program_Extensions.cs: -------------------------------------------------------------------------------- 1 | using BigBadBlog.Common.Data; 2 | using BigBadBlog.Data.Postgres; 3 | using Microsoft.AspNetCore.Identity; 4 | using Microsoft.Extensions.Configuration; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Microsoft.Extensions.Hosting; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | 13 | namespace BigBadBlog; 14 | 15 | public static class Program_Extensions 16 | { 17 | 18 | public static IHostApplicationBuilder? AddPostgresDatabaseServices(this IHostApplicationBuilder? host) 19 | { 20 | 21 | host.AddNpgsqlDbContext(ServiceNames.DATABASE_POSTS.NAME); 22 | 23 | host.Services.AddScoped(); 24 | 25 | return host; 26 | 27 | } 28 | 29 | public static IHostApplicationBuilder? AddIdentityServices(this IHostApplicationBuilder? builder) 30 | { 31 | 32 | 33 | builder.Services.AddDefaultIdentity(options => options.SignIn.RequireConfirmedAccount = true) 34 | .AddEntityFrameworkStores(); 35 | 36 | return builder; 37 | 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Service.DatabaseMigration/BigBadBlog.Service.DatabaseMigration.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | dotnet-BigBadBlog.Service.DatabaseMigration-a0576ffb-d599-4117-9441-d034f300a0d7 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Service.DatabaseMigration/Program.cs: -------------------------------------------------------------------------------- 1 | using BigBadBlog; 2 | using BigBadBlog.Data.Postgres; 3 | using BigBadBlog.Service.DatabaseMigration; 4 | using Microsoft.EntityFrameworkCore; 5 | 6 | var builder = Host.CreateApplicationBuilder(args); 7 | 8 | builder.AddServiceDefaults(); 9 | 10 | builder.Services.AddHostedService(); 11 | 12 | builder.Services.AddDbContext( 13 | options => options.UseNpgsql(builder.Configuration.GetConnectionString(ServiceNames.DATABASE_POSTS.NAME)) 14 | ); 15 | 16 | builder.Services.AddOpenTelemetry() 17 | .WithTracing(tracing => tracing.AddSource(Worker.ActivityName)); 18 | 19 | var host = builder.Build(); 20 | host.Run(); 21 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Service.DatabaseMigration/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "profiles": { 4 | "BigBadBlog.Service.DatabaseMigration": { 5 | "commandName": "Project", 6 | "dotnetRunMessages": true, 7 | "environmentVariables": { 8 | "DOTNET_ENVIRONMENT": "Development" 9 | } 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Service.DatabaseMigration/Worker.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using BigBadBlog.Data.Postgres; 3 | using Microsoft.EntityFrameworkCore; 4 | using OpenTelemetry.Trace; 5 | 6 | namespace BigBadBlog.Service.DatabaseMigration; 7 | 8 | public class Worker : BackgroundService 9 | { 10 | private readonly IServiceProvider serviceProvider; 11 | private readonly IHostApplicationLifetime hostApplicationLifetime; 12 | private readonly ILogger _logger; 13 | 14 | internal const string ActivityName = "MigrationService"; 15 | private static readonly ActivitySource _activitySource = new(ActivityName); 16 | 17 | public Worker(IServiceProvider serviceProvider, 18 | IHostApplicationLifetime hostApplicationLifetime, 19 | ILogger logger) 20 | { 21 | this.serviceProvider = serviceProvider; 22 | this.hostApplicationLifetime = hostApplicationLifetime; 23 | _logger = logger; 24 | } 25 | 26 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 27 | { 28 | using var activity = _activitySource.StartActivity("Migrating database", ActivityKind.Client); 29 | 30 | try 31 | { 32 | using var scope = serviceProvider.CreateScope(); 33 | var dbContext = scope.ServiceProvider.GetRequiredService(); 34 | 35 | await dbContext.Database.MigrateAsync(stoppingToken); 36 | 37 | } 38 | catch (Exception ex) 39 | { 40 | activity?.RecordException(ex); 41 | throw; 42 | } 43 | 44 | hostApplicationLifetime.StopApplication(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Service.DatabaseMigration/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.Hosting.Lifetime": "Information" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Service.DatabaseMigration/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.Hosting.Lifetime": "Information" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.ServiceDefaults/BigBadBlog.ServiceDefaults.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Web/Areas/Identity/Pages/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "/Pages/Shared/_Layout.cshtml"; 3 | } 4 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Web/BigBadBlog.Web.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | aspnet-BigBadBlog.Web-c0b48993-c310-4519-8acf-b5b7242032d2 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | all 23 | runtime; build; native; contentfiles; analyzers; buildtransitive 24 | 25 | 26 | all 27 | runtime; build; native; contentfiles; analyzers; buildtransitive 28 | 29 | 30 | 31 | all 32 | runtime; build; native; contentfiles; analyzers; buildtransitive 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Web/Pages/Error.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ErrorModel 3 | @{ 4 | ViewData["Title"] = "Error"; 5 | } 6 | 7 |

Error.

8 |

An error occurred while processing your request.

9 | 10 | @if (Model.ShowRequestId) 11 | { 12 |

13 | Request ID: @Model.RequestId 14 |

15 | } 16 | 17 |

Development Mode

18 |

19 | Swapping to the Development environment displays detailed information about the error that occurred. 20 |

21 |

22 | The Development environment shouldn't be enabled for deployed applications. 23 | It can result in displaying sensitive information from exceptions to end users. 24 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development 25 | and restarting the app. 26 |

27 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Web/Pages/Error.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.AspNetCore.Mvc.RazorPages; 4 | 5 | namespace BigBadBlog.Web.Pages; 6 | 7 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] 8 | [IgnoreAntiforgeryToken] 9 | public class ErrorModel : PageModel 10 | { 11 | public string? RequestId { get; set; } 12 | 13 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 14 | 15 | private readonly ILogger _logger; 16 | 17 | public ErrorModel(ILogger logger) 18 | { 19 | _logger = logger; 20 | } 21 | 22 | public void OnGet() 23 | { 24 | RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Web/Pages/Index.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @using Microsoft.AspNetCore.Components 3 | @model IndexModel 4 | @{ 5 | ViewData["Title"] = "Home page"; 6 | } 7 | 8 |
9 |

Welcome to the Big Bad Blog

10 |
11 | 12 | @foreach (var post in Model.Posts) 13 | { 14 |
15 |
16 |
@post.Metadata.Title
17 |
by @post.Metadata.Author on @post.Metadata.Date.ToShortDateString()
18 |

@Html.Raw(Markdig.Markdown.ToHtml(post.Content, Model.MarkdownPipeline))

19 |
20 |
21 | } -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Web/Pages/Index.cshtml.cs: -------------------------------------------------------------------------------- 1 | using BigBadBlog.Common.Data; 2 | using Markdig; 3 | using Microsoft.AspNetCore.Mvc; 4 | using Microsoft.AspNetCore.Mvc.RazorPages; 5 | using Microsoft.AspNetCore.OutputCaching; 6 | 7 | namespace BigBadBlog.Web.Pages; 8 | 9 | [OutputCache(PolicyName = "Home")] 10 | public class IndexModel : PageModel 11 | { 12 | private readonly ILogger _logger; 13 | public readonly MarkdownPipeline MarkdownPipeline; 14 | private readonly IPostRepository _postRepository; 15 | public IEnumerable<(PostMetadata Metadata, string Content)> Posts; 16 | 17 | public IndexModel(IPostRepository postRepository, ILogger logger) 18 | { 19 | _postRepository = postRepository; 20 | _logger = logger; 21 | 22 | MarkdownPipeline = new MarkdownPipelineBuilder() 23 | .UseYamlFrontMatter() 24 | .Build(); 25 | 26 | } 27 | 28 | public async Task OnGetAsync() 29 | { 30 | 31 | await Task.Delay(TimeSpan.FromSeconds(1)); 32 | 33 | Posts = await _postRepository.GetPostsAsync(10, 1); 34 | 35 | return Page(); 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Web/Pages/Post.cshtml: -------------------------------------------------------------------------------- 1 | @page "/p/{slug}" 2 | @model BigBadBlog.Web.Pages.PostModel 3 | @{ 4 | } 5 | 6 | 7 |
@Model.Post.Metadata.Title
8 |
by @Model.Post.Metadata.Author on @Model.Post.Metadata.Date.ToShortDateString()
9 |

@Html.Raw(Markdig.Markdown.ToHtml(Model.Post.Content, Model.MarkdownPipeline))

10 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Web/Pages/Post.cshtml.cs: -------------------------------------------------------------------------------- 1 | using BigBadBlog.Common.Data; 2 | using BigBadBlog.Web.Data; 3 | using Markdig; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.AspNetCore.Mvc.RazorPages; 6 | using Microsoft.AspNetCore.OutputCaching; 7 | using Microsoft.Extensions.Hosting; 8 | 9 | namespace BigBadBlog.Web.Pages; 10 | 11 | [OutputCache(PolicyName = "Post")] 12 | public class PostModel : PageModel 13 | { 14 | private readonly IPostRepository _postRepository; 15 | public readonly MarkdownPipeline MarkdownPipeline; 16 | 17 | public PostModel(IPostRepository postRepository, IWebHostEnvironment host) 18 | { 19 | _postRepository = postRepository; 20 | 21 | MarkdownPipeline = new MarkdownPipelineBuilder() 22 | .UseYamlFrontMatter() 23 | .Build(); 24 | 25 | } 26 | 27 | public (PostMetadata Metadata, string Content) Post { get; private set; } 28 | 29 | public async Task OnGetAsync(string slug) 30 | { 31 | 32 | Post = await _postRepository.GetPostAsync(slug); 33 | 34 | if (Post == default) 35 | { 36 | return NotFound(); 37 | } 38 | 39 | return Page(); 40 | 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Web/Pages/Privacy.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model PrivacyModel 3 | @{ 4 | ViewData["Title"] = "Privacy Policy"; 5 | } 6 |

@ViewData["Title"]

7 | 8 |

Use this page to detail your site's privacy policy.

9 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Web/Pages/Privacy.cshtml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.AspNetCore.Mvc.RazorPages; 3 | 4 | namespace BigBadBlog.Web.Pages; 5 | 6 | public class PrivacyModel : PageModel 7 | { 8 | private readonly ILogger _logger; 9 | 10 | public PrivacyModel(ILogger logger) 11 | { 12 | _logger = logger; 13 | } 14 | 15 | public void OnGet() 16 | { 17 | } 18 | } 19 | 20 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Web/Pages/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | @ViewData["Title"] - The Big Bad Blog 7 | 8 | 9 | 10 | 11 | 12 |
13 | 33 |
34 |
35 |
36 | @RenderBody() 37 |
38 |
39 | 40 |
41 |
42 | © 2024 - The Big Bad Blog - Privacy - Rendered at: @DateTime.UtcNow 43 |
44 |
45 | 46 | 47 | 48 | 49 | 50 | @await RenderSectionAsync("Scripts", required: false) 51 | 52 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Web/Pages/Shared/_Layout.cshtml.css: -------------------------------------------------------------------------------- 1 | /* Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification 2 | for details on configuring this project to bundle and minify static web assets. */ 3 | 4 | a.navbar-brand { 5 | white-space: normal; 6 | text-align: center; 7 | word-break: break-all; 8 | } 9 | 10 | a { 11 | color: #0077cc; 12 | } 13 | 14 | .btn-primary { 15 | color: #fff; 16 | background-color: #1b6ec2; 17 | border-color: #1861ac; 18 | } 19 | 20 | .nav-pills .nav-link.active, .nav-pills .show > .nav-link { 21 | color: #fff; 22 | background-color: #1b6ec2; 23 | border-color: #1861ac; 24 | } 25 | 26 | .border-top { 27 | border-top: 1px solid #e5e5e5; 28 | } 29 | .border-bottom { 30 | border-bottom: 1px solid #e5e5e5; 31 | } 32 | 33 | .box-shadow { 34 | box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05); 35 | } 36 | 37 | button.accept-policy { 38 | font-size: 1rem; 39 | line-height: inherit; 40 | } 41 | 42 | .footer { 43 | position: absolute; 44 | bottom: 0; 45 | width: 100%; 46 | white-space: nowrap; 47 | line-height: 60px; 48 | } 49 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Web/Pages/Shared/_LoginPartial.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Identity 2 | @inject SignInManager SignInManager 3 | @inject UserManager UserManager 4 | 5 | 27 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Web/Pages/Shared/_ValidationScriptsPartial.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Web/Pages/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Identity 2 | @using BigBadBlog.Common 3 | @using BigBadBlog.Common.Data 4 | @using BigBadBlog.Web 5 | @using BigBadBlog.Web.Data 6 | @namespace BigBadBlog.Web.Pages 7 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 8 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Web/Pages/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Web/PostApi.cs: -------------------------------------------------------------------------------- 1 | using BigBadBlog.Common.Data; 2 | 3 | namespace BigBadBlog.Web; 4 | 5 | //public partial class PostApi 6 | //{ 7 | 8 | // public PostApi(HttpClient client) : this(client.BaseAddress.ToString(), client) { } 9 | 10 | //} 11 | 12 | public class PostMicroserviceRepository(PostApi api) : IPostRepository 13 | { 14 | public async Task AddPostAsync(PostMetadata metadata, string content) 15 | { 16 | 17 | // Add the post using PostApi 18 | var newPost = new Post() 19 | { 20 | Author = metadata.Author, 21 | Content = content, 22 | Date = metadata.Date, 23 | Title = metadata.Title, 24 | }; 25 | 26 | await api.CreatePostAsync(newPost); 27 | 28 | } 29 | 30 | public async Task<(PostMetadata, string)> GetPostAsync(string slug) 31 | { 32 | 33 | // get the post using PostApi 34 | var post = await api.GetPostBySlugAsync(Uri.EscapeDataString(slug.ToLowerInvariant())); 35 | 36 | // convert the Post to a PostMetadata 37 | var metadata = new PostMetadata("", post.Title, post.Author, post.Date.Date); 38 | 39 | return (metadata, post.Content); 40 | 41 | } 42 | 43 | public async Task> GetPostsAsync(int count, int page) 44 | { 45 | 46 | // get the posts using PostApi 47 | var posts = (await api.GetAllPostsAsync()) 48 | .OrderByDescending(page => page.Date) 49 | .Skip((page-1)*count) 50 | .Take(count) 51 | .ToList(); 52 | 53 | var outCollection = posts.Count == 0 ? 54 | Enumerable.Empty<(PostMetadata, string)>() : 55 | posts.Select(p => (new PostMetadata("", p.Title, p.Author, p.Date.Date), p.Content)); 56 | 57 | return outCollection; 58 | 59 | } 60 | } -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Web/Posts/FirstPost.md: -------------------------------------------------------------------------------- 1 | --- 2 | Title: This is my first post 3 | Date: 2024-05-15 4 | Author: Arthur The Author 5 | --- 6 | 7 | This is the content of my first post. It's a very interesting post, I promise. 8 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Web/Posts/OrderingPizza.md: -------------------------------------------------------------------------------- 1 | --- 2 | Title: I think I'll order some pizza 3 | Date: 2024-05-16 4 | Author: Arthur The Author 5 | --- 6 | 7 | I'm feeling lazy today, so I think I'll order some pizza. I'm not sure what I want yet, but I'll figure it out. I'm thinking about getting a large pepperoni pizza, but I might change my mind. I'll let you know what I decide. 8 | 9 | There's this really cool new pizza shop that opened down the street called Blazing Pizza. I think I'll give them a try 10 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Web/Program.cs: -------------------------------------------------------------------------------- 1 | global using BigBadBlog.Web.Data; 2 | 3 | using Microsoft.AspNetCore.Identity; 4 | using Microsoft.EntityFrameworkCore; 5 | using BigBadBlog.Web.Data; 6 | using System.Net; 7 | using BigBadBlog; 8 | using BigBadBlog.Common.Data; 9 | using BigBadBlog.Web; 10 | 11 | var builder = WebApplication.CreateBuilder(args); 12 | 13 | // Add Aspire service defaults 14 | builder.AddServiceDefaults(); 15 | 16 | builder.AddPostgresDatabaseServices(); 17 | 18 | // Add OutputCache service 19 | builder.AddRedisOutputCache(ServiceNames.OUTPUTCACHE); 20 | builder.Services.AddOutputCache(options => 21 | { 22 | options.AddBasePolicy(policy => policy.Tag("ALL").Expire(TimeSpan.FromMinutes(5))); 23 | options.AddPolicy("Home", policy => policy.Tag("Home").Expire(TimeSpan.FromSeconds(30))); 24 | options.AddPolicy("Post", policy => policy.Tag("Post").SetVaryByRouteValue("slug").Expire(TimeSpan.FromSeconds(30))); 25 | }); 26 | 27 | // Add services to the container. 28 | builder.AddIdentityServices(); 29 | 30 | builder.Services.AddHttpClient(client => 31 | { 32 | // This URL uses "https+http://" to indicate HTTPS is preferred over HTTP. 33 | // Learn more about service discovery scheme resolution at https://aka.ms/dotnet/sdschemes. 34 | client.BaseAddress = new("https+http://postApi"); 35 | }); 36 | 37 | builder.Services.AddScoped(); 38 | 39 | builder.Services.AddRazorPages(); 40 | 41 | var app = builder.Build(); 42 | 43 | // Configure the HTTP request pipeline. 44 | if (app.Environment.IsDevelopment()) 45 | { 46 | app.UseMigrationsEndPoint(); 47 | } 48 | else 49 | { 50 | app.UseExceptionHandler("/Error"); 51 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. 52 | app.UseHsts(); 53 | } 54 | 55 | app.Map("/Posts", (HttpContext ctx) => 56 | { 57 | return HttpStatusCode.NotFound; 58 | }); 59 | 60 | app.UseHttpsRedirection(); 61 | app.UseStaticFiles(); 62 | 63 | app.UseRouting(); 64 | 65 | app.UseAuthorization(); 66 | 67 | //app.UseOutputCache(); 68 | 69 | app.MapRazorPages(); 70 | 71 | var mdRepo = new MarkdownPostRepository(); 72 | var pgRepo = app.Services.CreateScope().ServiceProvider.GetRequiredService(); 73 | 74 | var pgPosts = await pgRepo.GetPostsAsync(10, 1); 75 | 76 | if (!pgPosts.Any()) 77 | { 78 | var existingPosts = await mdRepo.GetPostsAsync(10,1); 79 | foreach (var post in existingPosts) 80 | { 81 | await pgRepo.AddPostAsync(post.Item1, post.Item2); 82 | } 83 | } 84 | 85 | app.Run(); 86 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Web/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:51799", 8 | "sslPort": 44354 9 | } 10 | }, 11 | "profiles": { 12 | "http": { 13 | "commandName": "Project", 14 | "dotnetRunMessages": true, 15 | "launchBrowser": true, 16 | "applicationUrl": "http://localhost:5055", 17 | "environmentVariables": { 18 | "ASPNETCORE_ENVIRONMENT": "Development" 19 | } 20 | }, 21 | "https": { 22 | "commandName": "Project", 23 | "dotnetRunMessages": true, 24 | "launchBrowser": true, 25 | "applicationUrl": "https://localhost:7008;http://localhost:5055", 26 | "environmentVariables": { 27 | "ASPNETCORE_ENVIRONMENT": "Development" 28 | } 29 | }, 30 | "IIS Express": { 31 | "commandName": "IISExpress", 32 | "launchBrowser": true, 33 | "environmentVariables": { 34 | "ASPNETCORE_ENVIRONMENT": "Development" 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Web/app.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/BigBadBlog/39336acf4e7cb464ecba49487144d5eff190292f/3-MicroserviceAndMongoDB/BigBadBlog.Web/app.db -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Web/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "DetailedErrors": true, 3 | "Logging": { 4 | "LogLevel": { 5 | "Default": "Information", 6 | "Microsoft.AspNetCore": "Warning" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Web/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "DefaultConnection": "DataSource=app.db;Cache=Shared" 4 | }, 5 | "Logging": { 6 | "LogLevel": { 7 | "Default": "Information", 8 | "Microsoft.AspNetCore": "Warning" 9 | } 10 | }, 11 | "AllowedHosts": "*" 12 | } 13 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Web/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-size: 14px; 3 | } 4 | 5 | @media (min-width: 768px) { 6 | html { 7 | font-size: 16px; 8 | } 9 | } 10 | 11 | .btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus { 12 | box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb; 13 | } 14 | 15 | html { 16 | position: relative; 17 | min-height: 100%; 18 | } 19 | 20 | body { 21 | margin-bottom: 60px; 22 | } -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Web/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/BigBadBlog/39336acf4e7cb464ecba49487144d5eff190292f/3-MicroserviceAndMongoDB/BigBadBlog.Web/wwwroot/favicon.ico -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Web/wwwroot/js/site.js: -------------------------------------------------------------------------------- 1 | // Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification 2 | // for details on configuring this project to bundle and minify static web assets. 3 | 4 | // Write your JavaScript code. 5 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Web/wwwroot/lib/bootstrap/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2011-2021 Twitter, Inc. 4 | Copyright (c) 2011-2021 The Bootstrap Authors 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Web/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) .NET Foundation and Contributors 4 | 5 | All rights reserved. 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Web/wwwroot/lib/jquery-validation/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright Jörn Zaefferer 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /3-MicroserviceAndMongoDB/BigBadBlog.Web/wwwroot/lib/jquery/LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Copyright OpenJS Foundation and other contributors, https://openjsf.org/ 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Jeffrey T. Fritz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The Big Bad Blog 2 | The Big Bad Blog is a tutorial series that could be presented as a workshop that demonstrates how to take a simple blog website with articles in markdown on disk, and make it into a large CMS. We've got a faux background story to set the scene and explain each step as the demo evolves 3 | 4 | ## How this got started... 5 | 6 | Our friend Arthur The Author wrote a simple blog engine with C# and ASP.NET Core Razor Pages that he was using to write some notes and publish with the Bootstrap CSS framework. This resulted in a simple blog engine that looked ok for him and his friends to reference. He could post new messages using markdown files that he wrote into the `Posts` folder and the engine would automatically discover, index, and present that content. 7 | 8 | Arthur started to enjoy writing, and wanted to do more. He wanted to share his blog with friends Annie, Chris, and Tyler. He hoped they would start writing on his blog but the engine was getting a little more complicated. He wanted to add security controls, thumbnail management, comments, and so much more. 9 | 10 | This workshop is our walkthrough of helping Arthur to take his simple blog engine and lift it up using .NET Aspire, .NET 8, GitHub, and Azure to make it into a publishing platform that he and his friends could use to share their stories with the internet. 11 | 12 | ![Initial Blog Website](docs/img/0-Starter.png) 13 | 14 | | Module | 15 | | --- | 16 | | [1 - Introducing the Blog and Aspire](docs/1-Introduction.md) | 17 | | [2 - Working with Databases and Entity Framework](docs/2-Database.md) | 18 | | [3 - Microservices and MongoDB](docs/3-Microservices.md) | 19 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.AppHost/BigBadBlog.AppHost.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | true 9 | 3e9797fc-af6f-46a6-a999-4ebc8157b5de 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.AppHost/Program.cs: -------------------------------------------------------------------------------- 1 | using BigBadBlog; 2 | using Projects; 3 | 4 | var builder = DistributedApplication.CreateBuilder(args); 5 | 6 | #region Posts Database 7 | 8 | var postgresPassword = builder.AddParameter("postgresspassword", secret: true); 9 | var db = builder.AddPostgres(ServiceNames.DATABASE_POSTS.SERVERNAME, password: postgresPassword) 10 | .WithDataVolume() 11 | .AddDatabase(ServiceNames.DATABASE_POSTS.NAME); 12 | 13 | var migrationService = builder.AddProject(ServiceNames.MIGRATION) 14 | .WithReference(db); 15 | 16 | #endregion 17 | 18 | #region Redis Cache 19 | 20 | var cache = builder.AddRedis(ServiceNames.OUTPUTCACHE, 65028) 21 | .WithRedisCommander(); 22 | 23 | #endregion 24 | 25 | #region Post API 26 | 27 | var postDb = builder.AddMongoDB("postDb") 28 | .WithDataVolume() 29 | .WithMongoExpress() 30 | .AddDatabase("posts-database"); 31 | 32 | var api = builder.AddProject("postApi", @"..\..\..\BigBadBlog.PostService\BigBadBlog.PostService.Api\BigBadBlog.PostService.Api.csproj") 33 | .WithReference(postDb); 34 | 35 | #endregion 36 | 37 | #region Web Application 38 | 39 | var webApp = builder.AddProject("web") 40 | .WithReference(cache) 41 | .WithReference(db) 42 | .WithReference(api) 43 | .WithExternalHttpEndpoints(); 44 | 45 | #endregion 46 | 47 | builder.Build().Run(); 48 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.AppHost/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "profiles": { 4 | "https": { 5 | "commandName": "Project", 6 | "dotnetRunMessages": true, 7 | "launchBrowser": true, 8 | "applicationUrl": "https://localhost:17164;http://localhost:15115", 9 | "environmentVariables": { 10 | "ASPNETCORE_ENVIRONMENT": "Development", 11 | "DOTNET_ENVIRONMENT": "Development", 12 | "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21248", 13 | "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22194" 14 | } 15 | }, 16 | "http": { 17 | "commandName": "Project", 18 | "dotnetRunMessages": true, 19 | "launchBrowser": true, 20 | "applicationUrl": "http://localhost:15115", 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development", 23 | "DOTNET_ENVIRONMENT": "Development", 24 | "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19289", 25 | "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20184" 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.AppHost/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.AppHost/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning", 6 | "Aspire.Hosting.Dcp": "Warning" 7 | } 8 | }, 9 | "Parameters":{ 10 | "postgresspassword": "MyP@55w0rd!" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Common/BigBadBlog.Common.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Common/Constants.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace BigBadBlog; 8 | 9 | public static class ServiceNames 10 | { 11 | 12 | /// 13 | /// Constants for referencing the database containing blog posts 14 | /// 15 | public static class DATABASE_POSTS { 16 | 17 | public const string SERVERNAME = "posts"; 18 | public const string NAME = "post-database"; 19 | 20 | } 21 | 22 | public const string MIGRATION = "database-migration"; 23 | 24 | public const string OUTPUTCACHE = "outputcache"; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Common/Data/IPostRepository.cs: -------------------------------------------------------------------------------- 1 | namespace BigBadBlog.Common.Data; 2 | 3 | /// 4 | /// A definition of a simple interaction with blog posts 5 | /// 6 | public interface IPostRepository 7 | { 8 | 9 | Task> GetPostsAsync(int count, int page); 10 | 11 | Task<(PostMetadata,string)> GetPostAsync(string slug); 12 | 13 | Task AddPostAsync(PostMetadata metadata, string content); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Common/Data/PostMetadata.cs: -------------------------------------------------------------------------------- 1 | namespace BigBadBlog.Common.Data; 2 | 3 | public record PostMetadata(string Filename, string Title, string Author, DateTime Date) 4 | { 5 | 6 | public string Slug => Uri.EscapeDataString(Title.ToLower()); 7 | 8 | } -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Data.Postgres/ApplicationDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore; 3 | 4 | namespace BigBadBlog.Data.Postgres; 5 | 6 | public class ApplicationDbContext : IdentityDbContext 7 | { 8 | public ApplicationDbContext(DbContextOptions options) 9 | : base(options) 10 | { 11 | } 12 | 13 | public DbSet Posts { get; set; } 14 | 15 | protected override void OnModelCreating(ModelBuilder builder) 16 | { 17 | 18 | // Add an index to the PgPost slug column 19 | builder.Entity() 20 | .HasIndex(p => p.Slug) 21 | .IsUnique(); 22 | 23 | // Add an index to the PgPost date column 24 | builder.Entity() 25 | .HasIndex(p => p.Date); 26 | 27 | base.OnModelCreating(builder); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Data.Postgres/BigBadBlog.Data.Postgres.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Data.Postgres/PgPost.cs: -------------------------------------------------------------------------------- 1 | using BigBadBlog.Common.Data; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace BigBadBlog.Data.Postgres; 5 | 6 | public class PgPost 7 | { 8 | 9 | [Key] 10 | public int Id { get; set; } 11 | 12 | [Required, MaxLength(100)] 13 | public string Title { get; set; } 14 | 15 | [Required, MaxLength(100)] 16 | public string Author { get; set; } 17 | 18 | [Required] 19 | public DateTime Date { get; set; } 20 | 21 | [Required] 22 | public string Content { get; set; } 23 | 24 | [Required, MaxLength(150)] 25 | public string Slug { get; set; } 26 | 27 | // explicit conversion to PostMetadata 28 | public static explicit operator PostMetadata(PgPost post) 29 | { 30 | return new PostMetadata("", post.Title, post.Author, post.Date); 31 | } 32 | 33 | // explicit conversation from PostMetadata 34 | public static explicit operator PgPost((PostMetadata, string) post) 35 | { 36 | return new PgPost 37 | { 38 | Title = post.Item1.Title, 39 | Author = post.Item1.Author, 40 | Date = post.Item1.Date, 41 | Content = post.Item2, 42 | Slug = post.Item1.Slug 43 | }; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Data.Postgres/PgPostRepository.cs: -------------------------------------------------------------------------------- 1 | using BigBadBlog.Common.Data; 2 | using Microsoft.EntityFrameworkCore; 3 | 4 | namespace BigBadBlog.Data.Postgres; 5 | 6 | internal class PgPostRepository : IPostRepository 7 | { 8 | private readonly ApplicationDbContext _context; 9 | 10 | public PgPostRepository(ApplicationDbContext context) 11 | { 12 | _context = context; 13 | } 14 | 15 | public async Task<(PostMetadata, string)> GetPostAsync(string slug) 16 | { 17 | var post = await _context.Posts 18 | .Where(p => p.Slug == slug) 19 | .Select(p => (PostMetadata)p) 20 | .FirstOrDefaultAsync(); 21 | 22 | if (post == default) 23 | { 24 | return default; 25 | } 26 | 27 | var content = await _context.Posts 28 | .Where(p => p.Slug == slug) 29 | .Select(p => p.Content) 30 | .FirstOrDefaultAsync(); 31 | 32 | return (post, content); 33 | } 34 | 35 | public async Task> GetPostsAsync(int count, int page) 36 | { 37 | // fetch the posts from the database and convert them to PostMetadata 38 | var posts = await _context.Posts 39 | .OrderByDescending(p => p.Date) 40 | .Skip(count * (page - 1)) 41 | .Take(count) 42 | .ToListAsync(); 43 | 44 | return posts.Select(p => ((PostMetadata)p, p.Content)); 45 | 46 | } 47 | 48 | public async Task AddPostAsync(PostMetadata metadata, string content) 49 | { 50 | var post = (PgPost)(metadata, content); 51 | 52 | post.Date = new DateTime(DateTime.Now.Ticks, DateTimeKind.Utc); 53 | _context.Posts.Add(post); 54 | 55 | await _context.SaveChangesAsync(); 56 | } 57 | 58 | public async Task UpdatePostAsync(PostMetadata metadata, string content) 59 | { 60 | var post = (PgPost)(metadata, content); 61 | 62 | _context.Posts.Update(post); 63 | 64 | await _context.SaveChangesAsync(); 65 | } 66 | 67 | public async Task DeletePostAsync(string slug) 68 | { 69 | var post = await _context.Posts 70 | .Where(p => p.Slug == slug) 71 | .FirstOrDefaultAsync(); 72 | 73 | if (post != default) 74 | { 75 | _context.Posts.Remove(post); 76 | await _context.SaveChangesAsync(); 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Data.Postgres/Program_Extensions.cs: -------------------------------------------------------------------------------- 1 | using BigBadBlog.Common.Data; 2 | using BigBadBlog.Data.Postgres; 3 | using Microsoft.AspNetCore.Identity; 4 | using Microsoft.Extensions.Configuration; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Microsoft.Extensions.Hosting; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | 13 | namespace BigBadBlog; 14 | 15 | public static class Program_Extensions 16 | { 17 | 18 | public static IHostApplicationBuilder? AddPostgresDatabaseServices(this IHostApplicationBuilder? host) 19 | { 20 | 21 | host.AddNpgsqlDbContext(ServiceNames.DATABASE_POSTS.NAME); 22 | 23 | host.Services.AddScoped(); 24 | 25 | return host; 26 | 27 | } 28 | 29 | public static IHostApplicationBuilder? AddIdentityServices(this IHostApplicationBuilder? builder) 30 | { 31 | 32 | 33 | builder.Services.AddDefaultIdentity(options => options.SignIn.RequireConfirmedAccount = true) 34 | .AddEntityFrameworkStores(); 35 | 36 | return builder; 37 | 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Service.DatabaseMigration/BigBadBlog.Service.DatabaseMigration.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | dotnet-BigBadBlog.Service.DatabaseMigration-a0576ffb-d599-4117-9441-d034f300a0d7 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Service.DatabaseMigration/Program.cs: -------------------------------------------------------------------------------- 1 | using BigBadBlog; 2 | using BigBadBlog.Data.Postgres; 3 | using BigBadBlog.Service.DatabaseMigration; 4 | using Microsoft.EntityFrameworkCore; 5 | 6 | var builder = Host.CreateApplicationBuilder(args); 7 | 8 | builder.AddServiceDefaults(); 9 | 10 | builder.Services.AddHostedService(); 11 | 12 | builder.Services.AddDbContext( 13 | options => options.UseNpgsql(builder.Configuration.GetConnectionString(ServiceNames.DATABASE_POSTS.NAME)) 14 | ); 15 | 16 | builder.Services.AddOpenTelemetry() 17 | .WithTracing(tracing => tracing.AddSource(Worker.ActivityName)); 18 | 19 | var host = builder.Build(); 20 | host.Run(); 21 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Service.DatabaseMigration/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "profiles": { 4 | "BigBadBlog.Service.DatabaseMigration": { 5 | "commandName": "Project", 6 | "dotnetRunMessages": true, 7 | "environmentVariables": { 8 | "DOTNET_ENVIRONMENT": "Development" 9 | } 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Service.DatabaseMigration/Worker.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using BigBadBlog.Data.Postgres; 3 | using Microsoft.EntityFrameworkCore; 4 | using OpenTelemetry.Trace; 5 | 6 | namespace BigBadBlog.Service.DatabaseMigration; 7 | 8 | public class Worker : BackgroundService 9 | { 10 | private readonly IServiceProvider serviceProvider; 11 | private readonly IHostApplicationLifetime hostApplicationLifetime; 12 | private readonly ILogger _logger; 13 | 14 | internal const string ActivityName = "MigrationService"; 15 | private static readonly ActivitySource _activitySource = new(ActivityName); 16 | 17 | public Worker(IServiceProvider serviceProvider, 18 | IHostApplicationLifetime hostApplicationLifetime, 19 | ILogger logger) 20 | { 21 | this.serviceProvider = serviceProvider; 22 | this.hostApplicationLifetime = hostApplicationLifetime; 23 | _logger = logger; 24 | } 25 | 26 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 27 | { 28 | using var activity = _activitySource.StartActivity("Migrating database", ActivityKind.Client); 29 | 30 | try 31 | { 32 | using var scope = serviceProvider.CreateScope(); 33 | var dbContext = scope.ServiceProvider.GetRequiredService(); 34 | 35 | await dbContext.Database.MigrateAsync(stoppingToken); 36 | 37 | } 38 | catch (Exception ex) 39 | { 40 | activity?.RecordException(ex); 41 | throw; 42 | } 43 | 44 | hostApplicationLifetime.StopApplication(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Service.DatabaseMigration/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.Hosting.Lifetime": "Information" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Service.DatabaseMigration/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.Hosting.Lifetime": "Information" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.ServiceDefaults/BigBadBlog.ServiceDefaults.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Web/Areas/Identity/Pages/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "/Pages/Shared/_Layout.cshtml"; 3 | } 4 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Web/BigBadBlog.Web.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | aspnet-BigBadBlog.Web-c0b48993-c310-4519-8acf-b5b7242032d2 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | all 23 | runtime; build; native; contentfiles; analyzers; buildtransitive 24 | 25 | 26 | all 27 | runtime; build; native; contentfiles; analyzers; buildtransitive 28 | 29 | 30 | 31 | all 32 | runtime; build; native; contentfiles; analyzers; buildtransitive 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Web/Pages/Error.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ErrorModel 3 | @{ 4 | ViewData["Title"] = "Error"; 5 | } 6 | 7 |

Error.

8 |

An error occurred while processing your request.

9 | 10 | @if (Model.ShowRequestId) 11 | { 12 |

13 | Request ID: @Model.RequestId 14 |

15 | } 16 | 17 |

Development Mode

18 |

19 | Swapping to the Development environment displays detailed information about the error that occurred. 20 |

21 |

22 | The Development environment shouldn't be enabled for deployed applications. 23 | It can result in displaying sensitive information from exceptions to end users. 24 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development 25 | and restarting the app. 26 |

27 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Web/Pages/Error.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.AspNetCore.Mvc.RazorPages; 4 | 5 | namespace BigBadBlog.Web.Pages; 6 | 7 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] 8 | [IgnoreAntiforgeryToken] 9 | public class ErrorModel : PageModel 10 | { 11 | public string? RequestId { get; set; } 12 | 13 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 14 | 15 | private readonly ILogger _logger; 16 | 17 | public ErrorModel(ILogger logger) 18 | { 19 | _logger = logger; 20 | } 21 | 22 | public void OnGet() 23 | { 24 | RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Web/Pages/Index.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @using Microsoft.AspNetCore.Components 3 | @model IndexModel 4 | @{ 5 | ViewData["Title"] = "Home page"; 6 | } 7 | 8 |
9 |

Welcome to the Big Bad Blog

10 |
11 | 12 | @foreach (var post in Model.Posts) 13 | { 14 |
15 |
16 |
@post.Metadata.Title
17 |
by @post.Metadata.Author on @post.Metadata.Date.ToShortDateString()
18 |

@Html.Raw(Markdig.Markdown.ToHtml(post.Content, Model.MarkdownPipeline))

19 |
20 |
21 | } -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Web/Pages/Index.cshtml.cs: -------------------------------------------------------------------------------- 1 | using BigBadBlog.Common.Data; 2 | using Markdig; 3 | using Microsoft.AspNetCore.Mvc; 4 | using Microsoft.AspNetCore.Mvc.RazorPages; 5 | using Microsoft.AspNetCore.OutputCaching; 6 | 7 | namespace BigBadBlog.Web.Pages; 8 | 9 | [OutputCache(PolicyName = "Home")] 10 | public class IndexModel : PageModel 11 | { 12 | private readonly ILogger _logger; 13 | public readonly MarkdownPipeline MarkdownPipeline; 14 | private readonly IPostRepository _postRepository; 15 | public IEnumerable<(PostMetadata Metadata, string Content)> Posts; 16 | 17 | public IndexModel(IPostRepository postRepository, ILogger logger) 18 | { 19 | _postRepository = postRepository; 20 | _logger = logger; 21 | 22 | MarkdownPipeline = new MarkdownPipelineBuilder() 23 | .UseYamlFrontMatter() 24 | .Build(); 25 | 26 | } 27 | 28 | public async Task OnGetAsync() 29 | { 30 | 31 | await Task.Delay(TimeSpan.FromSeconds(1)); 32 | 33 | Posts = await _postRepository.GetPostsAsync(10, 1); 34 | 35 | return Page(); 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Web/Pages/Post.cshtml: -------------------------------------------------------------------------------- 1 | @page "/p/{slug}" 2 | @model BigBadBlog.Web.Pages.PostModel 3 | @{ 4 | } 5 | 6 | 7 |
@Model.Post.Metadata.Title
8 |
by @Model.Post.Metadata.Author on @Model.Post.Metadata.Date.ToShortDateString()
9 |

@Html.Raw(Markdig.Markdown.ToHtml(Model.Post.Content, Model.MarkdownPipeline))

10 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Web/Pages/Post.cshtml.cs: -------------------------------------------------------------------------------- 1 | using BigBadBlog.Common.Data; 2 | using BigBadBlog.Web.Data; 3 | using Markdig; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.AspNetCore.Mvc.RazorPages; 6 | using Microsoft.AspNetCore.OutputCaching; 7 | using Microsoft.Extensions.Hosting; 8 | 9 | namespace BigBadBlog.Web.Pages; 10 | 11 | [OutputCache(PolicyName = "Post")] 12 | public class PostModel : PageModel 13 | { 14 | private readonly IPostRepository _postRepository; 15 | public readonly MarkdownPipeline MarkdownPipeline; 16 | 17 | public PostModel(IPostRepository postRepository, IWebHostEnvironment host) 18 | { 19 | _postRepository = postRepository; 20 | 21 | MarkdownPipeline = new MarkdownPipelineBuilder() 22 | .UseYamlFrontMatter() 23 | .Build(); 24 | 25 | } 26 | 27 | public (PostMetadata Metadata, string Content) Post { get; private set; } 28 | 29 | public async Task OnGetAsync(string slug) 30 | { 31 | 32 | Post = await _postRepository.GetPostAsync(slug); 33 | 34 | if (Post == default) 35 | { 36 | return NotFound(); 37 | } 38 | 39 | return Page(); 40 | 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Web/Pages/Privacy.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model PrivacyModel 3 | @{ 4 | ViewData["Title"] = "Privacy Policy"; 5 | } 6 |

@ViewData["Title"]

7 | 8 |

Use this page to detail your site's privacy policy.

9 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Web/Pages/Privacy.cshtml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.AspNetCore.Mvc.RazorPages; 3 | 4 | namespace BigBadBlog.Web.Pages; 5 | 6 | public class PrivacyModel : PageModel 7 | { 8 | private readonly ILogger _logger; 9 | 10 | public PrivacyModel(ILogger logger) 11 | { 12 | _logger = logger; 13 | } 14 | 15 | public void OnGet() 16 | { 17 | } 18 | } 19 | 20 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Web/Pages/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | @ViewData["Title"] - The Big Bad Blog 7 | 8 | 9 | 10 | 11 | 12 |
13 | 33 |
34 |
35 |
36 | @RenderBody() 37 |
38 |
39 | 40 |
41 |
42 | © 2024 - The Big Bad Blog - Privacy - Rendered at: @DateTime.UtcNow 43 |
44 |
45 | 46 | 47 | 48 | 49 | 50 | @await RenderSectionAsync("Scripts", required: false) 51 | 52 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Web/Pages/Shared/_Layout.cshtml.css: -------------------------------------------------------------------------------- 1 | /* Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification 2 | for details on configuring this project to bundle and minify static web assets. */ 3 | 4 | a.navbar-brand { 5 | white-space: normal; 6 | text-align: center; 7 | word-break: break-all; 8 | } 9 | 10 | a { 11 | color: #0077cc; 12 | } 13 | 14 | .btn-primary { 15 | color: #fff; 16 | background-color: #1b6ec2; 17 | border-color: #1861ac; 18 | } 19 | 20 | .nav-pills .nav-link.active, .nav-pills .show > .nav-link { 21 | color: #fff; 22 | background-color: #1b6ec2; 23 | border-color: #1861ac; 24 | } 25 | 26 | .border-top { 27 | border-top: 1px solid #e5e5e5; 28 | } 29 | .border-bottom { 30 | border-bottom: 1px solid #e5e5e5; 31 | } 32 | 33 | .box-shadow { 34 | box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05); 35 | } 36 | 37 | button.accept-policy { 38 | font-size: 1rem; 39 | line-height: inherit; 40 | } 41 | 42 | .footer { 43 | position: absolute; 44 | bottom: 0; 45 | width: 100%; 46 | white-space: nowrap; 47 | line-height: 60px; 48 | } 49 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Web/Pages/Shared/_LoginPartial.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Identity 2 | @inject SignInManager SignInManager 3 | @inject UserManager UserManager 4 | 5 | 27 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Web/Pages/Shared/_ValidationScriptsPartial.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Web/Pages/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Identity 2 | @using BigBadBlog.Common 3 | @using BigBadBlog.Common.Data 4 | @using BigBadBlog.Web 5 | @using BigBadBlog.Web.Data 6 | @namespace BigBadBlog.Web.Pages 7 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 8 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Web/Pages/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Web/PostApi.cs: -------------------------------------------------------------------------------- 1 | using BigBadBlog.Common.Data; 2 | 3 | namespace BigBadBlog.Web; 4 | 5 | //public partial class PostApi 6 | //{ 7 | 8 | // public PostApi(HttpClient client) : this(client.BaseAddress.ToString(), client) { } 9 | 10 | //} 11 | 12 | public class PostMicroserviceRepository(PostApi api) : IPostRepository 13 | { 14 | public async Task AddPostAsync(PostMetadata metadata, string content) 15 | { 16 | 17 | // Add the post using PostApi 18 | var newPost = new Post() 19 | { 20 | Author = metadata.Author, 21 | Content = content, 22 | Date = metadata.Date, 23 | Title = metadata.Title, 24 | }; 25 | 26 | await api.CreatePostAsync(newPost); 27 | 28 | } 29 | 30 | public async Task<(PostMetadata, string)> GetPostAsync(string slug) 31 | { 32 | 33 | // get the post using PostApi 34 | var post = await api.GetPostBySlugAsync(Uri.EscapeDataString(slug.ToLowerInvariant())); 35 | 36 | // convert the Post to a PostMetadata 37 | var metadata = new PostMetadata("", post.Title, post.Author, post.Date.Date); 38 | 39 | return (metadata, post.Content); 40 | 41 | } 42 | 43 | public async Task> GetPostsAsync(int count, int page) 44 | { 45 | 46 | // get the posts using PostApi 47 | var posts = (await api.GetAllPostsAsync()) 48 | .OrderByDescending(page => page.Date) 49 | .Skip((page-1)*count) 50 | .Take(count) 51 | .ToList(); 52 | 53 | var outCollection = posts.Count == 0 ? 54 | Enumerable.Empty<(PostMetadata, string)>() : 55 | posts.Select(p => (new PostMetadata("", p.Title, p.Author, p.Date.Date), p.Content)); 56 | 57 | return outCollection; 58 | 59 | } 60 | } -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Web/Posts/FirstPost.md: -------------------------------------------------------------------------------- 1 | --- 2 | Title: This is my first post 3 | Date: 2024-05-15 4 | Author: Arthur The Author 5 | --- 6 | 7 | This is the content of my first post. It's a very interesting post, I promise. 8 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Web/Posts/OrderingPizza.md: -------------------------------------------------------------------------------- 1 | --- 2 | Title: I think I'll order some pizza 3 | Date: 2024-05-16 4 | Author: Arthur The Author 5 | --- 6 | 7 | I'm feeling lazy today, so I think I'll order some pizza. I'm not sure what I want yet, but I'll figure it out. I'm thinking about getting a large pepperoni pizza, but I might change my mind. I'll let you know what I decide. 8 | 9 | There's this really cool new pizza shop that opened down the street called Blazing Pizza. I think I'll give them a try 10 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Web/Program.cs: -------------------------------------------------------------------------------- 1 | global using BigBadBlog.Web.Data; 2 | 3 | using Microsoft.AspNetCore.Identity; 4 | using Microsoft.EntityFrameworkCore; 5 | using BigBadBlog.Web.Data; 6 | using System.Net; 7 | using BigBadBlog; 8 | using BigBadBlog.Common.Data; 9 | using BigBadBlog.Web; 10 | 11 | var builder = WebApplication.CreateBuilder(args); 12 | 13 | // Add Aspire service defaults 14 | builder.AddServiceDefaults(); 15 | 16 | builder.AddPostgresDatabaseServices(); 17 | 18 | // Add OutputCache service 19 | builder.AddRedisOutputCache(ServiceNames.OUTPUTCACHE); 20 | builder.Services.AddOutputCache(options => 21 | { 22 | options.AddBasePolicy(policy => policy.Tag("ALL").Expire(TimeSpan.FromMinutes(5))); 23 | options.AddPolicy("Home", policy => policy.Tag("Home").Expire(TimeSpan.FromSeconds(30))); 24 | options.AddPolicy("Post", policy => policy.Tag("Post").SetVaryByRouteValue("slug").Expire(TimeSpan.FromSeconds(30))); 25 | }); 26 | 27 | // Add services to the container. 28 | builder.AddIdentityServices(); 29 | 30 | builder.Services.AddHttpClient(client => 31 | { 32 | // This URL uses "https+http://" to indicate HTTPS is preferred over HTTP. 33 | // Learn more about service discovery scheme resolution at https://aka.ms/dotnet/sdschemes. 34 | client.BaseAddress = new("https+http://postApi"); 35 | }); 36 | 37 | builder.Services.AddScoped(); 38 | 39 | builder.Services.AddRazorPages(); 40 | 41 | var app = builder.Build(); 42 | 43 | // Configure the HTTP request pipeline. 44 | if (app.Environment.IsDevelopment()) 45 | { 46 | app.UseMigrationsEndPoint(); 47 | } 48 | else 49 | { 50 | app.UseExceptionHandler("/Error"); 51 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. 52 | app.UseHsts(); 53 | } 54 | 55 | app.Map("/Posts", (HttpContext ctx) => 56 | { 57 | return HttpStatusCode.NotFound; 58 | }); 59 | 60 | app.UseHttpsRedirection(); 61 | app.UseStaticFiles(); 62 | 63 | app.UseRouting(); 64 | 65 | app.UseAuthorization(); 66 | 67 | //app.UseOutputCache(); 68 | 69 | app.MapRazorPages(); 70 | 71 | var mdRepo = new MarkdownPostRepository(); 72 | var pgRepo = app.Services.CreateScope().ServiceProvider.GetRequiredService(); 73 | 74 | var pgPosts = await pgRepo.GetPostsAsync(10, 1); 75 | 76 | if (!pgPosts.Any()) 77 | { 78 | var existingPosts = await mdRepo.GetPostsAsync(10,1); 79 | foreach (var post in existingPosts) 80 | { 81 | await pgRepo.AddPostAsync(post.Item1, post.Item2); 82 | } 83 | } 84 | 85 | app.Run(); 86 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Web/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:51799", 8 | "sslPort": 44354 9 | } 10 | }, 11 | "profiles": { 12 | "http": { 13 | "commandName": "Project", 14 | "dotnetRunMessages": true, 15 | "launchBrowser": true, 16 | "applicationUrl": "http://localhost:5055", 17 | "environmentVariables": { 18 | "ASPNETCORE_ENVIRONMENT": "Development" 19 | } 20 | }, 21 | "https": { 22 | "commandName": "Project", 23 | "dotnetRunMessages": true, 24 | "launchBrowser": true, 25 | "applicationUrl": "https://localhost:7008;http://localhost:5055", 26 | "environmentVariables": { 27 | "ASPNETCORE_ENVIRONMENT": "Development" 28 | } 29 | }, 30 | "IIS Express": { 31 | "commandName": "IISExpress", 32 | "launchBrowser": true, 33 | "environmentVariables": { 34 | "ASPNETCORE_ENVIRONMENT": "Development" 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Web/app.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/BigBadBlog/39336acf4e7cb464ecba49487144d5eff190292f/completed_application/BigBadBlog.Web/app.db -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Web/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "DetailedErrors": true, 3 | "Logging": { 4 | "LogLevel": { 5 | "Default": "Information", 6 | "Microsoft.AspNetCore": "Warning" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Web/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "DefaultConnection": "DataSource=app.db;Cache=Shared" 4 | }, 5 | "Logging": { 6 | "LogLevel": { 7 | "Default": "Information", 8 | "Microsoft.AspNetCore": "Warning" 9 | } 10 | }, 11 | "AllowedHosts": "*" 12 | } 13 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Web/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-size: 14px; 3 | } 4 | 5 | @media (min-width: 768px) { 6 | html { 7 | font-size: 16px; 8 | } 9 | } 10 | 11 | .btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus { 12 | box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb; 13 | } 14 | 15 | html { 16 | position: relative; 17 | min-height: 100%; 18 | } 19 | 20 | body { 21 | margin-bottom: 60px; 22 | } -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Web/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/BigBadBlog/39336acf4e7cb464ecba49487144d5eff190292f/completed_application/BigBadBlog.Web/wwwroot/favicon.ico -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Web/wwwroot/js/site.js: -------------------------------------------------------------------------------- 1 | // Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification 2 | // for details on configuring this project to bundle and minify static web assets. 3 | 4 | // Write your JavaScript code. 5 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Web/wwwroot/lib/bootstrap/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2011-2021 Twitter, Inc. 4 | Copyright (c) 2011-2021 The Bootstrap Authors 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Web/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) .NET Foundation and Contributors 4 | 5 | All rights reserved. 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Web/wwwroot/lib/jquery-validation/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright Jörn Zaefferer 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /completed_application/BigBadBlog.Web/wwwroot/lib/jquery/LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Copyright OpenJS Foundation and other contributors, https://openjsf.org/ 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /docs/3-Microservices.md: -------------------------------------------------------------------------------- 1 | # 3 - Microservices and MongoDB 2 | 3 | Up to this point, we've built and connected services in the same solution and the same repository for our blog. This convenience and typical approach for .NET developers with mono-repos leads to this question and criticism of .NET Aspire: 4 | 5 | ![Reddit Question about using separate repositories](img/3-Reddit.png) 6 | 7 | You're right... that is a typical pattern for microservice development, placing services in separate folders in separate solutions with distinct source control repositories. Let's take a step away from our current demo, and build an API in a separate folder and a new solution using MongoDB to store posts. In fact, the completed sample code for this API isn't even in this repository. I moved it to https://github.com/csharpfritz/BigBadBlog.PostService 8 | 9 | ## Starting the new service 10 | 11 | Let's assume I'm working on another team in conjunction with Arthur the Author to manage the content for the new blog engine. I'm building the PostService with [MongoDB](https://mongodb.com), my favorite NoSQL storage solution, in another repository and I'll expose the contents of the service using OpenAPI. 12 | 13 | 14 | Project reference: https://learn.microsoft.com/en-us/dotnet/api/aspire.hosting.projectresourcebuilderextensions.addproject?view=dotnet-aspire-8.0.1#aspire-hosting-projectresourcebuilderextensions-addproject(aspire-hosting-idistributedapplicationbuilder-system-string-system-string) -------------------------------------------------------------------------------- /docs/img/0-DashboardWithRedis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/BigBadBlog/39336acf4e7cb464ecba49487144d5eff190292f/docs/img/0-DashboardWithRedis.png -------------------------------------------------------------------------------- /docs/img/0-FirstLookAtDashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/BigBadBlog/39336acf4e7cb464ecba49487144d5eff190292f/docs/img/0-FirstLookAtDashboard.png -------------------------------------------------------------------------------- /docs/img/0-Metrics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/BigBadBlog/39336acf4e7cb464ecba49487144d5eff190292f/docs/img/0-Metrics.png -------------------------------------------------------------------------------- /docs/img/0-SolutionExplorer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/BigBadBlog/39336acf4e7cb464ecba49487144d5eff190292f/docs/img/0-SolutionExplorer.png -------------------------------------------------------------------------------- /docs/img/0-Starter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/BigBadBlog/39336acf4e7cb464ecba49487144d5eff190292f/docs/img/0-Starter.png -------------------------------------------------------------------------------- /docs/img/0-StructuredLogs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/BigBadBlog/39336acf4e7cb464ecba49487144d5eff190292f/docs/img/0-StructuredLogs.png -------------------------------------------------------------------------------- /docs/img/0-TraceWithRedis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/BigBadBlog/39336acf4e7cb464ecba49487144d5eff190292f/docs/img/0-TraceWithRedis.png -------------------------------------------------------------------------------- /docs/img/0-Traces.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/BigBadBlog/39336acf4e7cb464ecba49487144d5eff190292f/docs/img/0-Traces.png -------------------------------------------------------------------------------- /docs/img/0-Workloads.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/BigBadBlog/39336acf4e7cb464ecba49487144d5eff190292f/docs/img/0-Workloads.png -------------------------------------------------------------------------------- /docs/img/2-DashboardWithDb-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/BigBadBlog/39336acf4e7cb464ecba49487144d5eff190292f/docs/img/2-DashboardWithDb-1.png -------------------------------------------------------------------------------- /docs/img/2-DashboardWithDb-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/BigBadBlog/39336acf4e7cb464ecba49487144d5eff190292f/docs/img/2-DashboardWithDb-2.png -------------------------------------------------------------------------------- /docs/img/3-Reddit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/BigBadBlog/39336acf4e7cb464ecba49487144d5eff190292f/docs/img/3-Reddit.png --------------------------------------------------------------------------------