├── .editorconfig ├── .gitattributes ├── .gitignore ├── Animation.gif ├── BlazorBlogs.sln ├── BlazorBlogs ├── .config │ └── dotnet-tools.json ├── BlazorBlogs.csproj ├── Classes │ ├── ConfigureWritable.cs │ ├── ConnectionSetting.cs │ ├── ConnectionStrings.cs │ ├── DisqusInterop.cs │ ├── DisqusState.cs │ ├── ExternalConnectionsDTO.cs │ ├── GoogleInterop.cs │ ├── InstallUpdateState.cs │ ├── NewsletterContent.cs │ ├── SearchState.cs │ ├── Utility.cs │ └── Version.cs ├── Components │ ├── Account │ │ ├── ApplicationDbContext.cs │ │ ├── IdentityComponentsEndpointRouteBuilderExtensions.cs │ │ ├── IdentityRedirectManager.cs │ │ ├── IdentityRevalidatingAuthenticationStateProvider.cs │ │ ├── IdentityUserAccessor.cs │ │ ├── Pages │ │ │ ├── AccessDenied.razor │ │ │ ├── ConfirmEmail.razor │ │ │ ├── ConfirmEmailChange.razor │ │ │ ├── ExternalLogin.razor │ │ │ ├── ForgotPassword.razor │ │ │ ├── ForgotPasswordConfirmation.razor │ │ │ ├── InvalidPasswordReset.razor │ │ │ ├── InvalidUser.razor │ │ │ ├── Lockout.razor │ │ │ ├── Login.razor │ │ │ ├── LoginWith2fa.razor │ │ │ ├── LoginWithRecoveryCode.razor │ │ │ ├── Manage │ │ │ │ ├── ChangePassword.razor │ │ │ │ ├── DeletePersonalData.razor │ │ │ │ ├── Disable2fa.razor │ │ │ │ ├── Email.razor │ │ │ │ ├── EnableAuthenticator.razor │ │ │ │ ├── ExternalLogins.razor │ │ │ │ ├── GenerateRecoveryCodes.razor │ │ │ │ ├── Index.razor │ │ │ │ ├── PersonalData.razor │ │ │ │ ├── ResetAuthenticator.razor │ │ │ │ ├── SetPassword.razor │ │ │ │ ├── TwoFactorAuthentication.razor │ │ │ │ └── _Imports.razor │ │ │ ├── Register.razor │ │ │ ├── RegisterConfirmation.razor │ │ │ ├── ResendEmailConfirmation.razor │ │ │ ├── ResetPassword.razor │ │ │ ├── ResetPasswordConfirmation.razor │ │ │ └── _Imports.razor │ │ └── Shared │ │ │ ├── ExternalLoginPicker.razor │ │ │ ├── ManageLayout.razor │ │ │ ├── ManageNavMenu.razor │ │ │ ├── RedirectToLogin.razor │ │ │ ├── ShowRecoveryCodes.razor │ │ │ └── StatusMessage.razor │ ├── App.razor │ ├── Layout │ │ ├── LicenseTerms.razor │ │ ├── LoginDisplay.razor │ │ ├── MainLayout.razor │ │ ├── MainLayout.razor.css │ │ ├── NavMenu.razor │ │ └── NavMenu.razor.css │ ├── Pages │ │ ├── Administration.razor │ │ ├── BlogAdministration.razor │ │ ├── CategoryAdministration.razor │ │ ├── CategoryManager.razor │ │ ├── Error.razor │ │ ├── FileDownloads.razor │ │ ├── FileManager.razor │ │ ├── FileSelector.razor │ │ ├── Importer │ │ │ ├── ImportBlogs.razor │ │ │ ├── ImportBlogsDatabaseConfiguration.razor │ │ │ └── ImportBlogsEdit.razor │ │ ├── Index.razor │ │ ├── InstallUpgrade │ │ │ ├── InstallWizard.razor │ │ │ ├── InstallWizardCreateAdministrator.razor │ │ │ ├── InstallWizardDatabaseConfiguration.razor │ │ │ ├── InstallWizardExecuteScripts.razor │ │ │ ├── InstallWizardLayout.razor │ │ │ ├── InstallWizardLayout.razor.css │ │ │ ├── InstallWizardLoginAdministrator.razor │ │ │ ├── InstallWizardNavMenu.razor │ │ │ └── _Imports.razor │ │ ├── Logs.razor │ │ ├── Newsletter │ │ │ ├── NewsletterCampainEdit.razor │ │ │ ├── NewsletterEdit.razor │ │ │ ├── NewsletterHome.razor │ │ │ ├── NewsletterUnsubscribe.razor │ │ │ └── NewsletterView.razor │ │ ├── Settings.razor │ │ └── ViewBlogPost.razor │ ├── Routes.razor │ └── _Imports.razor ├── Controllers │ ├── FileController.cs │ ├── MetaWeblogService .cs │ ├── RSSFeed.cs │ └── UploadController.cs ├── Data │ ├── BlazorBlogsContext.cs │ ├── BlogsService.cs │ ├── DotNetNukeBlogsContext.cs │ ├── EmailSender.cs │ ├── EmailService.cs │ ├── GeneralSettingsService.cs │ └── Models │ │ ├── ApplicationUser.cs │ │ ├── ApplicationUserPaged.cs │ │ ├── AspNetRoles.cs │ │ ├── AspNetUserRoles.cs │ │ ├── AspNetUsers.cs │ │ ├── BlogBlogs.cs │ │ ├── BlogCategories.cs │ │ ├── BlogCategory.cs │ │ ├── BlogComments.cs │ │ ├── BlogDTO.cs │ │ ├── BlogEntries.cs │ │ ├── BlogEntryCategories.cs │ │ ├── BlogEntryTags.cs │ │ ├── BlogSettings.cs │ │ ├── BlogTags.cs │ │ ├── Blogs.cs │ │ ├── BlogsPaged.cs │ │ ├── CategoryDTO.cs │ │ ├── Categorys.cs │ │ ├── CategorysPaged.cs │ │ ├── Comment.cs │ │ ├── Constants.cs │ │ ├── ExternalConnections.cs │ │ ├── Files.cs │ │ ├── FilesDTO.cs │ │ ├── GeneralSettings.cs │ │ ├── Logs.cs │ │ ├── LogsPaged.cs │ │ ├── NewsletterParser.cs │ │ ├── Newsletters.cs │ │ ├── NewslettersCampain.cs │ │ ├── NewslettersLogs.cs │ │ └── Settings.cs ├── Program.cs ├── Properties │ ├── ServiceDependencies │ │ └── blazor-blogs-test - Web Deploy │ │ │ └── profile.arm.json │ ├── launchSettings.json │ └── serviceDependencies.blazor-blogs-test - Web Deploy.json ├── SQLScripts │ ├── 00.10.00.sql │ ├── 00.20.00.sql │ ├── 01.00.00.sql │ ├── 01.00.20.sql │ ├── 01.00.30.sql │ ├── 01.00.40.sql │ ├── 01.00.50.sql │ ├── 01.00.60.sql │ ├── 01.00.70.sql │ ├── 01.00.80.sql │ ├── 01.00.90.sql │ ├── 01.01.00.sql │ ├── 01.01.10.sql │ ├── 02.00.00.sql │ ├── 03.00.00.sql │ ├── 04.00.00.sql │ └── 09.00.00.sql ├── Uploads │ └── Placeholder.txt ├── appsettings.Development.json ├── appsettings.json └── wwwroot │ ├── app.css │ ├── css │ └── open-iconic │ │ ├── FONT-LICENSE │ │ ├── ICON-LICENSE │ │ ├── README.md │ │ └── font │ │ ├── css │ │ └── open-iconic-bootstrap.min.css │ │ └── fonts │ │ ├── open-iconic.eot │ │ ├── open-iconic.otf │ │ ├── open-iconic.svg │ │ ├── open-iconic.ttf │ │ └── open-iconic.woff │ ├── favicon.ico │ ├── imageIcon.png │ ├── javascript │ └── DisqusFunctions.js │ ├── lib │ └── bootstrap │ │ └── 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 │ ├── uploads │ ├── BlazorHelpWebsite.png │ └── logo.png │ └── wlwmanifest.xml ├── LICENSE ├── README.md ├── Screenshot001.png ├── Screenshot002.png ├── Screenshot003.png ├── Screenshot004.png ├── Screenshot005.png ├── Screenshot006.png └── azuredeploy.json /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | 3 | # Default severity for analyzer diagnostics with category 'Interoperability' 4 | dotnet_analyzer_diagnostic.category-Interoperability.severity = silent 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | *.razor linguist-language=C# 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /Animation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ADefWebserver/Blazor-Blogs/c5d58fcfc27366114eb5ed7b14af561f3bac9c75/Animation.gif -------------------------------------------------------------------------------- /BlazorBlogs.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.12.35527.113 d17.12 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorBlogs", "BlazorBlogs\BlazorBlogs.csproj", "{E639B06E-B3EA-4F36-B667-60B0FE33A00D}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GitHubContent", "GitHubContent", "{B094EED7-9C64-451D-A484-DB3ECC725832}" 9 | ProjectSection(SolutionItems) = preProject 10 | Animation.gif = Animation.gif 11 | azuredeploy.json = azuredeploy.json 12 | README.md = README.md 13 | Screenshot001.png = Screenshot001.png 14 | Screenshot002.png = Screenshot002.png 15 | Screenshot003.png = Screenshot003.png 16 | Screenshot004.png = Screenshot004.png 17 | ..\..\..\..\..\..\TEMP\Screenshot005.png = ..\..\..\..\..\..\TEMP\Screenshot005.png 18 | Screenshot006.png = Screenshot006.png 19 | EndProjectSection 20 | EndProject 21 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{03C4A91F-EDF0-4FCD-B46E-E9B4092A6D68}" 22 | ProjectSection(SolutionItems) = preProject 23 | .editorconfig = .editorconfig 24 | EndProjectSection 25 | EndProject 26 | Global 27 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 28 | Debug|Any CPU = Debug|Any CPU 29 | Release|Any CPU = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 32 | {E639B06E-B3EA-4F36-B667-60B0FE33A00D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {E639B06E-B3EA-4F36-B667-60B0FE33A00D}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {E639B06E-B3EA-4F36-B667-60B0FE33A00D}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {E639B06E-B3EA-4F36-B667-60B0FE33A00D}.Release|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | GlobalSection(ExtensibilityGlobals) = postSolution 41 | SolutionGuid = {689CB94C-FD4E-4A62-84B8-2B15D8FB0B1B} 42 | EndGlobalSection 43 | EndGlobal 44 | -------------------------------------------------------------------------------- /BlazorBlogs/.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "dotnet-ef": { 6 | "version": "3.1.3", 7 | "commands": [ 8 | "dotnet-ef" 9 | ] 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /BlazorBlogs/BlazorBlogs.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | disable 6 | enable 7 | aspnet-BlazorBlogs-A4274235-47DB-4E99-B6E2-7037D91ED90A 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | Always 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | all 35 | runtime; build; native; contentfiles; analyzers; buildtransitive 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | Never 51 | 52 | 53 | Always 54 | 55 | 56 | Always 57 | 58 | 59 | Always 60 | 61 | 62 | Always 63 | 64 | 65 | Always 66 | 67 | 68 | Always 69 | 70 | 71 | Always 72 | 73 | 74 | Always 75 | 76 | 77 | Always 78 | 79 | 80 | Always 81 | 82 | 83 | Always 84 | 85 | 86 | Always 87 | 88 | 89 | Always 90 | 91 | 92 | Always 93 | 94 | 95 | Always 96 | 97 | 98 | Always 99 | 100 | 101 | Always 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /BlazorBlogs/Classes/ConfigureWritable.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.Options; 5 | using Newtonsoft.Json; 6 | using Newtonsoft.Json.Linq; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.IO; 10 | using System.Text; 11 | 12 | // From: https://stackoverflow.com/questions/40970944/how-to-update-values-into-appsetting-json 13 | namespace Microsoft.Extensions.DependencyInjection 14 | { 15 | public static class ServiceCollectionExtensions 16 | { 17 | public static void ConfigureWritable( 18 | this IServiceCollection services, 19 | IConfigurationSection section, 20 | string file = "appsettings.json") where T : class, new() 21 | { 22 | services.Configure(section); 23 | services.AddTransient>(provider => 24 | { 25 | var environment = provider.GetService(); 26 | var options = provider.GetService>(); 27 | return new WritableOptions(environment, options, section.Key, file); 28 | }); 29 | } 30 | } 31 | 32 | public interface IWritableOptions : IOptionsSnapshot where T : class, new() 33 | { 34 | void Update(Action applyChanges); 35 | } 36 | 37 | public class WritableOptions : IWritableOptions where T : class, new() 38 | { 39 | private readonly IWebHostEnvironment _environment; 40 | private readonly IOptionsMonitor _options; 41 | private readonly string _section; 42 | private readonly string _file; 43 | 44 | public WritableOptions( 45 | IWebHostEnvironment environment, 46 | IOptionsMonitor options, 47 | string section, 48 | string file) 49 | { 50 | _environment = environment; 51 | _options = options; 52 | _section = section; 53 | _file = file; 54 | } 55 | 56 | public T Value => _options.CurrentValue; 57 | public T Get(string name) => _options.Get(name); 58 | 59 | public void Update(Action applyChanges) 60 | { 61 | var fileProvider = _environment.ContentRootFileProvider; 62 | var fileInfo = fileProvider.GetFileInfo(_file); 63 | var physicalPath = fileInfo.PhysicalPath; 64 | 65 | var jObject = JsonConvert.DeserializeObject(File.ReadAllText(physicalPath)); 66 | var sectionObject = jObject.TryGetValue(_section, out JToken section) ? 67 | JsonConvert.DeserializeObject(section.ToString()) : (Value ?? new T()); 68 | 69 | applyChanges(sectionObject); 70 | 71 | jObject[_section] = JObject.Parse(JsonConvert.SerializeObject(sectionObject)); 72 | File.WriteAllText(physicalPath, JsonConvert.SerializeObject(jObject, Formatting.Indented)); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /BlazorBlogs/Classes/ConnectionSetting.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace BlazorBlogsLibrary.Classes.Imports 6 | { 7 | public class ConnectionSetting 8 | { 9 | public string DatabaseName { get; set; } 10 | public string ServerName { get; set; } 11 | public bool IntegratedSecurity { get; set; } 12 | public string Username { get; set; } 13 | public string Password { get; set; } 14 | } 15 | 16 | public class DTOConnectionSetting 17 | { 18 | public string DatabaseName { get; set; } 19 | public string ServerName { get; set; } 20 | public bool IntegratedSecurity { get; set; } 21 | public string Username { get; set; } 22 | public string Password { get; set; } 23 | public ConnectionState ConnectionState { get; set; } 24 | } 25 | 26 | public class DTOStatus 27 | { 28 | public string StatusMessage { get; set; } 29 | public bool Success { get; set; } 30 | } 31 | 32 | public enum ConnectionState 33 | { 34 | Add, 35 | Delete, 36 | Update 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /BlazorBlogs/Classes/ConnectionStrings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace Microsoft.Extensions.DependencyInjection 7 | { 8 | public class ConnectionStrings 9 | { 10 | public string DefaultConnection { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /BlazorBlogs/Classes/DisqusInterop.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | using Microsoft.JSInterop; 3 | using System.Threading.Tasks; 4 | 5 | namespace BlazorBlogs 6 | { 7 | public static class DisqusInterop 8 | { 9 | internal static ValueTask CreateDisqus( 10 | IJSRuntime jsRuntime, 11 | ElementReference disqusThreadElement, 12 | string disqusSrc) 13 | { 14 | return jsRuntime.InvokeAsync( 15 | "DisqusFunctions.createDisqus", 16 | disqusThreadElement, 17 | disqusSrc); 18 | } 19 | 20 | internal static ValueTask ResetDisqus( 21 | IJSRuntime jsRuntime, 22 | string newIdentifier, 23 | string newUrl, 24 | string newTitle) 25 | { 26 | return jsRuntime.InvokeAsync( 27 | "DisqusFunctions.resetDisqus", 28 | newIdentifier, 29 | newUrl, 30 | newTitle); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /BlazorBlogs/Classes/DisqusState.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace BlazorBlogs 7 | { 8 | public class DisqusState 9 | { 10 | private bool _DisplayDisqus = false; 11 | 12 | // StateChanged is an event handler other pages 13 | // can subscribe to 14 | public event EventHandler StateChanged; 15 | 16 | public bool getDisplayDisqus() 17 | { 18 | return _DisplayDisqus; 19 | } 20 | 21 | public void SetDisplayDisqus(bool param) 22 | { 23 | _DisplayDisqus = param; 24 | StateHasChanged(); 25 | } 26 | 27 | private void StateHasChanged() 28 | { 29 | // This will update any subscribers 30 | // that the counter state has changed 31 | // so they can update themselves 32 | // and show the current counter value 33 | StateChanged?.Invoke(this, EventArgs.Empty); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /BlazorBlogs/Classes/ExternalConnectionsDTO.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace BlazorBlogsLibrary.Classes 6 | { 7 | public class ExternalConnectionsDTO 8 | { 9 | public int Id { get; set; } 10 | public string ConnectionName { get; set; } 11 | public string ServerName { get; set; } 12 | public string DatabaseName { get; set; } 13 | public string IntegratedSecurity { get; set; } 14 | public string DatabaseUsername { get; set; } 15 | public string DatabasePassword { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /BlazorBlogs/Classes/GoogleInterop.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | using Microsoft.JSInterop; 3 | using System.Threading.Tasks; 4 | 5 | namespace BlazorBlogs 6 | { 7 | public static class GoogleInterop 8 | { 9 | internal static ValueTask gaTracking( 10 | IJSRuntime jsRuntime, 11 | string gaTrackingID) 12 | { 13 | return jsRuntime.InvokeAsync( 14 | "gaFunctions.gaTracking", 15 | gaTrackingID); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /BlazorBlogs/Classes/InstallUpdateState.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace BlazorBlogs 6 | { 7 | class InstallUpdateState 8 | { 9 | public string InstallUpgradeWizardStage { get; set; } 10 | public bool DatabaseReady { get; set; } 11 | public string DatabaseConectionString { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /BlazorBlogs/Classes/NewsletterContent.cs: -------------------------------------------------------------------------------- 1 |  public class NewsletterContent 2 | { 3 | public string ContentType { get; set; } 4 | public string Content { get; set; } 5 | } -------------------------------------------------------------------------------- /BlazorBlogs/Classes/SearchState.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace BlazorBlogs 7 | { 8 | public class SearchState 9 | { 10 | public int CurrentPage { get; set; } 11 | public string CurrentCategoryID { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /BlazorBlogs/Classes/Version.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace BlazorBlogs 5 | { 6 | public class Version 7 | { 8 | [Key] 9 | public string VersionNumber { get; set; } 10 | public string ManifestLowestVersion { get; set; } 11 | public string ManifestHighestVersion { get; set; } 12 | public string ManifestSuccess { get; set; } 13 | public string ManifestFailure { get; set; } 14 | public bool isNewDatabase { get; set; } 15 | public bool isUpToDate { get; set; } 16 | } 17 | } -------------------------------------------------------------------------------- /BlazorBlogs/Components/Account/ApplicationDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore; 3 | 4 | namespace BlazorBlogs.Data 5 | { 6 | public class ApplicationDbContext(DbContextOptions options) : IdentityDbContext(options) 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Account/IdentityComponentsEndpointRouteBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | using System.Text.Json; 3 | using BlazorBlogs.Components.Account.Pages; 4 | using BlazorBlogs.Components.Account.Pages.Manage; 5 | using BlazorBlogs.Data; 6 | using BlazorBlogs.Data.Models; 7 | using Microsoft.AspNetCore.Authentication; 8 | using Microsoft.AspNetCore.Components.Authorization; 9 | using Microsoft.AspNetCore.Http.Extensions; 10 | using Microsoft.AspNetCore.Identity; 11 | using Microsoft.AspNetCore.Mvc; 12 | using Microsoft.Extensions.Primitives; 13 | 14 | namespace Microsoft.AspNetCore.Routing 15 | { 16 | internal static class IdentityComponentsEndpointRouteBuilderExtensions 17 | { 18 | // These endpoints are required by the Identity Razor components defined in the /Components/Account/Pages directory of this project. 19 | public static IEndpointConventionBuilder MapAdditionalIdentityEndpoints(this IEndpointRouteBuilder endpoints) 20 | { 21 | ArgumentNullException.ThrowIfNull(endpoints); 22 | 23 | var accountGroup = endpoints.MapGroup("/Account"); 24 | 25 | accountGroup.MapPost("/PerformExternalLogin", ( 26 | HttpContext context, 27 | [FromServices] SignInManager signInManager, 28 | [FromForm] string provider, 29 | [FromForm] string returnUrl) => 30 | { 31 | IEnumerable> query = [ 32 | new("ReturnUrl", returnUrl), 33 | new("Action", ExternalLogin.LoginCallbackAction)]; 34 | 35 | var redirectUrl = UriHelper.BuildRelative( 36 | context.Request.PathBase, 37 | "/Account/ExternalLogin", 38 | QueryString.Create(query)); 39 | 40 | var properties = signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl); 41 | return TypedResults.Challenge(properties, [provider]); 42 | }); 43 | 44 | accountGroup.MapPost("/Logout", async ( 45 | ClaimsPrincipal user, 46 | [FromServices] SignInManager signInManager, 47 | [FromForm] string returnUrl) => 48 | { 49 | await signInManager.SignOutAsync(); 50 | return TypedResults.LocalRedirect($"~/{returnUrl}"); 51 | }); 52 | 53 | var manageGroup = accountGroup.MapGroup("/Manage").RequireAuthorization(); 54 | 55 | manageGroup.MapPost("/LinkExternalLogin", async ( 56 | HttpContext context, 57 | [FromServices] SignInManager signInManager, 58 | [FromForm] string provider) => 59 | { 60 | // Clear the existing external cookie to ensure a clean login process 61 | await context.SignOutAsync(IdentityConstants.ExternalScheme); 62 | 63 | var redirectUrl = UriHelper.BuildRelative( 64 | context.Request.PathBase, 65 | "/Account/Manage/ExternalLogins", 66 | QueryString.Create("Action", ExternalLogins.LinkLoginCallbackAction)); 67 | 68 | var properties = signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl, signInManager.UserManager.GetUserId(context.User)); 69 | return TypedResults.Challenge(properties, [provider]); 70 | }); 71 | 72 | var loggerFactory = endpoints.ServiceProvider.GetRequiredService(); 73 | var downloadLogger = loggerFactory.CreateLogger("DownloadPersonalData"); 74 | 75 | manageGroup.MapPost("/DownloadPersonalData", async ( 76 | HttpContext context, 77 | [FromServices] UserManager userManager, 78 | [FromServices] AuthenticationStateProvider authenticationStateProvider) => 79 | { 80 | var user = await userManager.GetUserAsync(context.User); 81 | if (user is null) 82 | { 83 | return Results.NotFound($"Unable to load user with ID '{userManager.GetUserId(context.User)}'."); 84 | } 85 | 86 | var userId = await userManager.GetUserIdAsync(user); 87 | downloadLogger.LogInformation("User with ID '{UserId}' asked for their personal data.", userId); 88 | 89 | // Only include personal data for download 90 | var personalData = new Dictionary(); 91 | var personalDataProps = typeof(ApplicationUser).GetProperties().Where( 92 | prop => Attribute.IsDefined(prop, typeof(PersonalDataAttribute))); 93 | foreach (var p in personalDataProps) 94 | { 95 | personalData.Add(p.Name, p.GetValue(user)?.ToString() ?? "null"); 96 | } 97 | 98 | var logins = await userManager.GetLoginsAsync(user); 99 | foreach (var l in logins) 100 | { 101 | personalData.Add($"{l.LoginProvider} external login provider key", l.ProviderKey); 102 | } 103 | 104 | personalData.Add("Authenticator Key", (await userManager.GetAuthenticatorKeyAsync(user))!); 105 | var fileBytes = JsonSerializer.SerializeToUtf8Bytes(personalData); 106 | 107 | context.Response.Headers.TryAdd("Content-Disposition", "attachment; filename=PersonalData.json"); 108 | return TypedResults.File(fileBytes, contentType: "application/json", fileDownloadName: "PersonalData.json"); 109 | }); 110 | 111 | return accountGroup; 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Account/IdentityRedirectManager.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using Microsoft.AspNetCore.Components; 3 | 4 | namespace BlazorBlogs.Components.Account 5 | { 6 | internal sealed class IdentityRedirectManager(NavigationManager navigationManager) 7 | { 8 | public const string StatusCookieName = "Identity.StatusMessage"; 9 | 10 | private static readonly CookieBuilder StatusCookieBuilder = new() 11 | { 12 | SameSite = SameSiteMode.Strict, 13 | HttpOnly = true, 14 | IsEssential = true, 15 | MaxAge = TimeSpan.FromSeconds(5), 16 | }; 17 | 18 | [DoesNotReturn] 19 | public void RedirectTo(string uri) 20 | { 21 | uri ??= ""; 22 | 23 | // Prevent open redirects. 24 | if (!Uri.IsWellFormedUriString(uri, UriKind.Relative)) 25 | { 26 | uri = navigationManager.ToBaseRelativePath(uri); 27 | } 28 | 29 | // During static rendering, NavigateTo throws a NavigationException which is handled by the framework as a redirect. 30 | // So as long as this is called from a statically rendered Identity component, the InvalidOperationException is never thrown. 31 | navigationManager.NavigateTo(uri); 32 | throw new InvalidOperationException($"{nameof(IdentityRedirectManager)} can only be used during static rendering."); 33 | } 34 | 35 | [DoesNotReturn] 36 | public void RedirectTo(string uri, Dictionary queryParameters) 37 | { 38 | var uriWithoutQuery = navigationManager.ToAbsoluteUri(uri).GetLeftPart(UriPartial.Path); 39 | var newUri = navigationManager.GetUriWithQueryParameters(uriWithoutQuery, queryParameters); 40 | RedirectTo(newUri); 41 | } 42 | 43 | [DoesNotReturn] 44 | public void RedirectToWithStatus(string uri, string message, HttpContext context) 45 | { 46 | context.Response.Cookies.Append(StatusCookieName, message, StatusCookieBuilder.Build(context)); 47 | RedirectTo(uri); 48 | } 49 | 50 | private string CurrentPath => navigationManager.ToAbsoluteUri(navigationManager.Uri).GetLeftPart(UriPartial.Path); 51 | 52 | [DoesNotReturn] 53 | public void RedirectToCurrentPage() => RedirectTo(CurrentPath); 54 | 55 | [DoesNotReturn] 56 | public void RedirectToCurrentPageWithStatus(string message, HttpContext context) 57 | => RedirectToWithStatus(CurrentPath, message, context); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Account/IdentityRevalidatingAuthenticationStateProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | using BlazorBlogs.Data; 3 | using Microsoft.AspNetCore.Components.Authorization; 4 | using Microsoft.AspNetCore.Components.Server; 5 | using Microsoft.AspNetCore.Identity; 6 | using Microsoft.Extensions.Options; 7 | 8 | namespace BlazorBlogs.Components.Account 9 | { 10 | // This is a server-side AuthenticationStateProvider that revalidates the security stamp for the connected user 11 | // every 30 minutes an interactive circuit is connected. 12 | internal sealed class IdentityRevalidatingAuthenticationStateProvider( 13 | ILoggerFactory loggerFactory, 14 | IServiceScopeFactory scopeFactory, 15 | IOptions options) 16 | : RevalidatingServerAuthenticationStateProvider(loggerFactory) 17 | { 18 | protected override TimeSpan RevalidationInterval => TimeSpan.FromMinutes(30); 19 | 20 | protected override async Task ValidateAuthenticationStateAsync( 21 | AuthenticationState authenticationState, CancellationToken cancellationToken) 22 | { 23 | // Get the user manager from a new scope to ensure it fetches fresh data 24 | await using var scope = scopeFactory.CreateAsyncScope(); 25 | var userManager = scope.ServiceProvider.GetRequiredService>(); 26 | return await ValidateSecurityStampAsync(userManager, authenticationState.User); 27 | } 28 | 29 | private async Task ValidateSecurityStampAsync(UserManager userManager, ClaimsPrincipal principal) 30 | { 31 | var user = await userManager.GetUserAsync(principal); 32 | if (user is null) 33 | { 34 | return false; 35 | } 36 | else if (!userManager.SupportsUserSecurityStamp) 37 | { 38 | return true; 39 | } 40 | else 41 | { 42 | var principalStamp = principal.FindFirstValue(options.Value.ClaimsIdentity.SecurityStampClaimType); 43 | var userStamp = await userManager.GetSecurityStampAsync(user); 44 | return principalStamp == userStamp; 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Account/IdentityUserAccessor.cs: -------------------------------------------------------------------------------- 1 | using BlazorBlogs.Data; 2 | using Microsoft.AspNetCore.Identity; 3 | 4 | namespace BlazorBlogs.Components.Account 5 | { 6 | internal sealed class IdentityUserAccessor(UserManager userManager, IdentityRedirectManager redirectManager) 7 | { 8 | public async Task GetRequiredUserAsync(HttpContext context) 9 | { 10 | var user = await userManager.GetUserAsync(context.User); 11 | 12 | if (user is null) 13 | { 14 | redirectManager.RedirectToWithStatus("Account/InvalidUser", $"Error: Unable to load user with ID '{userManager.GetUserId(context.User)}'.", context); 15 | } 16 | 17 | return user; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Account/Pages/AccessDenied.razor: -------------------------------------------------------------------------------- 1 | @page "/Account/AccessDenied" 2 | 3 | Access denied 4 | 5 |
6 |

Access denied

7 |

You do not have access to this resource.

8 |
9 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Account/Pages/ConfirmEmail.razor: -------------------------------------------------------------------------------- 1 | @page "/Account/ConfirmEmail" 2 | 3 | @using System.Text 4 | @using Microsoft.AspNetCore.Identity 5 | @using Microsoft.AspNetCore.WebUtilities 6 | @using BlazorBlogs.Data 7 | 8 | @inject UserManager UserManager 9 | @inject IdentityRedirectManager RedirectManager 10 | 11 | Confirm email 12 | 13 |

Confirm email

14 | 15 | 16 | @code { 17 | private string statusMessage; 18 | 19 | [CascadingParameter] 20 | private HttpContext HttpContext { get; set; } = default!; 21 | 22 | [SupplyParameterFromQuery] 23 | private string UserId { get; set; } 24 | 25 | [SupplyParameterFromQuery] 26 | private string Code { get; set; } 27 | 28 | protected override async Task OnInitializedAsync() 29 | { 30 | if (UserId is null || Code is null) 31 | { 32 | RedirectManager.RedirectTo(""); 33 | } 34 | 35 | var user = await UserManager.FindByIdAsync(UserId); 36 | if (user is null) 37 | { 38 | HttpContext.Response.StatusCode = StatusCodes.Status404NotFound; 39 | statusMessage = $"Error loading user with ID {UserId}"; 40 | } 41 | else 42 | { 43 | var code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(Code)); 44 | var result = await UserManager.ConfirmEmailAsync(user, code); 45 | statusMessage = result.Succeeded ? "Thank you for confirming your email." : "Error confirming your email."; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Account/Pages/ConfirmEmailChange.razor: -------------------------------------------------------------------------------- 1 | @page "/Account/ConfirmEmailChange" 2 | 3 | @using System.Text 4 | @using Microsoft.AspNetCore.Identity 5 | @using Microsoft.AspNetCore.WebUtilities 6 | @using BlazorBlogs.Data 7 | 8 | @inject UserManager UserManager 9 | @inject SignInManager SignInManager 10 | @inject IdentityRedirectManager RedirectManager 11 | 12 | Confirm email change 13 | 14 |

Confirm email change

15 | 16 | 17 | 18 | @code { 19 | private string message; 20 | 21 | [CascadingParameter] 22 | private HttpContext HttpContext { get; set; } = default!; 23 | 24 | [SupplyParameterFromQuery] 25 | private string UserId { get; set; } 26 | 27 | [SupplyParameterFromQuery] 28 | private string Email { get; set; } 29 | 30 | [SupplyParameterFromQuery] 31 | private string Code { get; set; } 32 | 33 | protected override async Task OnInitializedAsync() 34 | { 35 | if (UserId is null || Email is null || Code is null) 36 | { 37 | RedirectManager.RedirectToWithStatus( 38 | "Account/Login", "Error: Invalid email change confirmation link.", HttpContext); 39 | } 40 | 41 | var user = await UserManager.FindByIdAsync(UserId); 42 | if (user is null) 43 | { 44 | message = "Unable to find user with Id '{userId}'"; 45 | return; 46 | } 47 | 48 | var code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(Code)); 49 | var result = await UserManager.ChangeEmailAsync(user, Email, code); 50 | if (!result.Succeeded) 51 | { 52 | message = "Error changing email."; 53 | return; 54 | } 55 | 56 | // In our UI email and user name are one and the same, so when we update the email 57 | // we need to update the user name. 58 | var setUserNameResult = await UserManager.SetUserNameAsync(user, Email); 59 | if (!setUserNameResult.Succeeded) 60 | { 61 | message = "Error changing user name."; 62 | return; 63 | } 64 | 65 | await SignInManager.RefreshSignInAsync(user); 66 | message = "Thank you for confirming your email change."; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Account/Pages/ForgotPassword.razor: -------------------------------------------------------------------------------- 1 | @page "/Account/ForgotPassword" 2 | 3 | @using System.ComponentModel.DataAnnotations 4 | @using System.Text 5 | @using System.Text.Encodings.Web 6 | @using Microsoft.AspNetCore.Identity 7 | @using Microsoft.AspNetCore.WebUtilities 8 | @using BlazorBlogs.Data 9 | 10 | @inject UserManager UserManager 11 | @inject IEmailSender EmailSender 12 | @inject NavigationManager NavigationManager 13 | @inject IdentityRedirectManager RedirectManager 14 | 15 | Forgot your password? 16 | 17 |

Forgot your password?

18 |

Enter your email.

19 |
20 |
21 |
22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
31 | 32 |
33 |
34 |
35 | 36 | @code { 37 | [SupplyParameterFromForm] 38 | private InputModel Input { get; set; } = new(); 39 | 40 | private async Task OnValidSubmitAsync() 41 | { 42 | var user = await UserManager.FindByEmailAsync(Input.Email); 43 | if (user is null || !(await UserManager.IsEmailConfirmedAsync(user))) 44 | { 45 | // Don't reveal that the user does not exist or is not confirmed 46 | RedirectManager.RedirectTo("Account/ForgotPasswordConfirmation"); 47 | } 48 | 49 | // For more information on how to enable account confirmation and password reset please 50 | // visit https://go.microsoft.com/fwlink/?LinkID=532713 51 | var code = await UserManager.GeneratePasswordResetTokenAsync(user); 52 | code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); 53 | var callbackUrl = NavigationManager.GetUriWithQueryParameters( 54 | NavigationManager.ToAbsoluteUri("Account/ResetPassword").AbsoluteUri, 55 | new Dictionary { ["code"] = code }); 56 | 57 | await EmailSender.SendPasswordResetLinkAsync(user, Input.Email, HtmlEncoder.Default.Encode(callbackUrl)); 58 | 59 | RedirectManager.RedirectTo("Account/ForgotPasswordConfirmation"); 60 | } 61 | 62 | private sealed class InputModel 63 | { 64 | [Required] 65 | [EmailAddress] 66 | public string Email { get; set; } = ""; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Account/Pages/ForgotPasswordConfirmation.razor: -------------------------------------------------------------------------------- 1 | @page "/Account/ForgotPasswordConfirmation" 2 | 3 | Forgot password confirmation 4 | 5 |

Forgot password confirmation

6 |

7 | Please check your email to reset your password. 8 |

9 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Account/Pages/InvalidPasswordReset.razor: -------------------------------------------------------------------------------- 1 | @page "/Account/InvalidPasswordReset" 2 | 3 | Invalid password reset 4 | 5 |

Invalid password reset

6 |

7 | The password reset link is invalid. 8 |

9 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Account/Pages/InvalidUser.razor: -------------------------------------------------------------------------------- 1 | @page "/Account/InvalidUser" 2 | 3 | Invalid user 4 | 5 |

Invalid user

6 | 7 | 8 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Account/Pages/Lockout.razor: -------------------------------------------------------------------------------- 1 | @page "/Account/Lockout" 2 | 3 | Locked out 4 | 5 |
6 |

Locked out

7 | 8 |
9 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Account/Pages/Login.razor: -------------------------------------------------------------------------------- 1 | @page "/Account/Login" 2 | 3 | @using System.ComponentModel.DataAnnotations 4 | @using Microsoft.AspNetCore.Authentication 5 | @using Microsoft.AspNetCore.Identity 6 | @using BlazorBlogs.Data 7 | 8 | @inject SignInManager SignInManager 9 | @inject ILogger Logger 10 | @inject NavigationManager NavigationManager 11 | @inject IdentityRedirectManager RedirectManager 12 | 13 | Log in 14 | 15 |

Log in

16 |
17 |
18 |
19 | 20 | 21 | 22 |

Use a local account to log in.

23 |
24 | 25 |
26 | 27 | 28 | 29 |
30 |
31 | 32 | 33 | 34 |
35 |
36 | 40 |
41 |
42 | 43 |
44 | 55 |
56 |
57 |
58 |
59 | 60 | @code { 61 | private string errorMessage; 62 | 63 | [CascadingParameter] 64 | private HttpContext HttpContext { get; set; } = default!; 65 | 66 | [SupplyParameterFromForm] 67 | private InputModel Input { get; set; } = new(); 68 | 69 | [SupplyParameterFromQuery] 70 | private string ReturnUrl { get; set; } 71 | 72 | protected override async Task OnInitializedAsync() 73 | { 74 | if (HttpMethods.IsGet(HttpContext.Request.Method)) 75 | { 76 | // Clear the existing external cookie to ensure a clean login process 77 | await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme); 78 | } 79 | } 80 | 81 | public async Task LoginUser() 82 | { 83 | // This doesn't count login failures towards account lockout 84 | // To enable password failures to trigger account lockout, set lockoutOnFailure: true 85 | var result = await SignInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false); 86 | if (result.Succeeded) 87 | { 88 | Logger.LogInformation("User logged in."); 89 | RedirectManager.RedirectTo(ReturnUrl); 90 | } 91 | else if (result.RequiresTwoFactor) 92 | { 93 | RedirectManager.RedirectTo( 94 | "Account/LoginWith2fa", 95 | new() { ["returnUrl"] = ReturnUrl, ["rememberMe"] = Input.RememberMe }); 96 | } 97 | else if (result.IsLockedOut) 98 | { 99 | Logger.LogWarning("User account locked out."); 100 | RedirectManager.RedirectTo("Account/Lockout"); 101 | } 102 | else 103 | { 104 | errorMessage = "Error: Invalid login attempt."; 105 | } 106 | } 107 | 108 | private sealed class InputModel 109 | { 110 | [Required] 111 | [EmailAddress] 112 | public string Email { get; set; } = ""; 113 | 114 | [Required] 115 | [DataType(DataType.Password)] 116 | public string Password { get; set; } = ""; 117 | 118 | [Display(Name = "Remember me?")] 119 | public bool RememberMe { get; set; } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Account/Pages/LoginWith2fa.razor: -------------------------------------------------------------------------------- 1 | @page "/Account/LoginWith2fa" 2 | 3 | @using System.ComponentModel.DataAnnotations 4 | @using Microsoft.AspNetCore.Identity 5 | @using BlazorBlogs.Data 6 | 7 | @inject SignInManager SignInManager 8 | @inject UserManager UserManager 9 | @inject IdentityRedirectManager RedirectManager 10 | @inject ILogger Logger 11 | 12 | Two-factor authentication 13 | 14 |

Two-factor authentication

15 |
16 | 17 |

Your login is protected with an authenticator app. Enter your authenticator code below.

18 |
19 |
20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 |
30 |
31 | 35 |
36 |
37 | 38 |
39 |
40 |
41 |
42 |

43 | Don't have access to your authenticator device? You can 44 | log in with a recovery code. 45 |

46 | 47 | @code { 48 | private string message; 49 | private ApplicationUser user = default!; 50 | 51 | [SupplyParameterFromForm] 52 | private InputModel Input { get; set; } = new(); 53 | 54 | [SupplyParameterFromQuery] 55 | private string ReturnUrl { get; set; } 56 | 57 | [SupplyParameterFromQuery] 58 | private bool RememberMe { get; set; } 59 | 60 | protected override async Task OnInitializedAsync() 61 | { 62 | // Ensure the user has gone through the username & password screen first 63 | user = await SignInManager.GetTwoFactorAuthenticationUserAsync() ?? 64 | throw new InvalidOperationException("Unable to load two-factor authentication user."); 65 | } 66 | 67 | private async Task OnValidSubmitAsync() 68 | { 69 | var authenticatorCode = Input.TwoFactorCode!.Replace(" ", string.Empty).Replace("-", string.Empty); 70 | var result = await SignInManager.TwoFactorAuthenticatorSignInAsync(authenticatorCode, RememberMe, Input.RememberMachine); 71 | var userId = await UserManager.GetUserIdAsync(user); 72 | 73 | if (result.Succeeded) 74 | { 75 | Logger.LogInformation("User with ID '{UserId}' logged in with 2fa.", userId); 76 | RedirectManager.RedirectTo(ReturnUrl); 77 | } 78 | else if (result.IsLockedOut) 79 | { 80 | Logger.LogWarning("User with ID '{UserId}' account locked out.", userId); 81 | RedirectManager.RedirectTo("Account/Lockout"); 82 | } 83 | else 84 | { 85 | Logger.LogWarning("Invalid authenticator code entered for user with ID '{UserId}'.", userId); 86 | message = "Error: Invalid authenticator code."; 87 | } 88 | } 89 | 90 | private sealed class InputModel 91 | { 92 | [Required] 93 | [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 94 | [DataType(DataType.Text)] 95 | [Display(Name = "Authenticator code")] 96 | public string TwoFactorCode { get; set; } 97 | 98 | [Display(Name = "Remember this machine")] 99 | public bool RememberMachine { get; set; } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Account/Pages/LoginWithRecoveryCode.razor: -------------------------------------------------------------------------------- 1 | @page "/Account/LoginWithRecoveryCode" 2 | 3 | @using System.ComponentModel.DataAnnotations 4 | @using Microsoft.AspNetCore.Identity 5 | @using BlazorBlogs.Data 6 | 7 | @inject SignInManager SignInManager 8 | @inject UserManager UserManager 9 | @inject IdentityRedirectManager RedirectManager 10 | @inject ILogger Logger 11 | 12 | Recovery code verification 13 | 14 |

Recovery code verification

15 |
16 | 17 |

18 | You have requested to log in with a recovery code. This login will not be remembered until you provide 19 | an authenticator app code at log in or disable 2FA and log in again. 20 |

21 |
22 |
23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
31 | 32 |
33 |
34 |
35 | 36 | @code { 37 | private string message; 38 | private ApplicationUser user = default!; 39 | 40 | [SupplyParameterFromForm] 41 | private InputModel Input { get; set; } = new(); 42 | 43 | [SupplyParameterFromQuery] 44 | private string ReturnUrl { get; set; } 45 | 46 | protected override async Task OnInitializedAsync() 47 | { 48 | // Ensure the user has gone through the username & password screen first 49 | user = await SignInManager.GetTwoFactorAuthenticationUserAsync() ?? 50 | throw new InvalidOperationException("Unable to load two-factor authentication user."); 51 | } 52 | 53 | private async Task OnValidSubmitAsync() 54 | { 55 | var recoveryCode = Input.RecoveryCode.Replace(" ", string.Empty); 56 | 57 | var result = await SignInManager.TwoFactorRecoveryCodeSignInAsync(recoveryCode); 58 | 59 | var userId = await UserManager.GetUserIdAsync(user); 60 | 61 | if (result.Succeeded) 62 | { 63 | Logger.LogInformation("User with ID '{UserId}' logged in with a recovery code.", userId); 64 | RedirectManager.RedirectTo(ReturnUrl); 65 | } 66 | else if (result.IsLockedOut) 67 | { 68 | Logger.LogWarning("User account locked out."); 69 | RedirectManager.RedirectTo("Account/Lockout"); 70 | } 71 | else 72 | { 73 | Logger.LogWarning("Invalid recovery code entered for user with ID '{UserId}' ", userId); 74 | message = "Error: Invalid recovery code entered."; 75 | } 76 | } 77 | 78 | private sealed class InputModel 79 | { 80 | [Required] 81 | [DataType(DataType.Text)] 82 | [Display(Name = "Recovery Code")] 83 | public string RecoveryCode { get; set; } = ""; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Account/Pages/Manage/ChangePassword.razor: -------------------------------------------------------------------------------- 1 | @page "/Account/Manage/ChangePassword" 2 | 3 | @using System.ComponentModel.DataAnnotations 4 | @using Microsoft.AspNetCore.Identity 5 | @using BlazorBlogs.Data 6 | 7 | @inject UserManager UserManager 8 | @inject SignInManager SignInManager 9 | @inject IdentityUserAccessor UserAccessor 10 | @inject IdentityRedirectManager RedirectManager 11 | @inject ILogger Logger 12 | 13 | Change password 14 | 15 |

Change password

16 | 17 |
18 |
19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 |
27 |
28 | 29 | 30 | 31 |
32 |
33 | 34 | 35 | 36 |
37 | 38 |
39 |
40 |
41 | 42 | @code { 43 | private string message; 44 | private ApplicationUser user = default!; 45 | private bool hasPassword; 46 | 47 | [CascadingParameter] 48 | private HttpContext HttpContext { get; set; } = default!; 49 | 50 | [SupplyParameterFromForm] 51 | private InputModel Input { get; set; } = new(); 52 | 53 | protected override async Task OnInitializedAsync() 54 | { 55 | user = await UserAccessor.GetRequiredUserAsync(HttpContext); 56 | hasPassword = await UserManager.HasPasswordAsync(user); 57 | if (!hasPassword) 58 | { 59 | RedirectManager.RedirectTo("Account/Manage/SetPassword"); 60 | } 61 | } 62 | 63 | private async Task OnValidSubmitAsync() 64 | { 65 | var changePasswordResult = await UserManager.ChangePasswordAsync(user, Input.OldPassword, Input.NewPassword); 66 | if (!changePasswordResult.Succeeded) 67 | { 68 | message = $"Error: {string.Join(",", changePasswordResult.Errors.Select(error => error.Description))}"; 69 | return; 70 | } 71 | 72 | await SignInManager.RefreshSignInAsync(user); 73 | Logger.LogInformation("User changed their password successfully."); 74 | 75 | RedirectManager.RedirectToCurrentPageWithStatus("Your password has been changed", HttpContext); 76 | } 77 | 78 | private sealed class InputModel 79 | { 80 | [Required] 81 | [DataType(DataType.Password)] 82 | [Display(Name = "Current password")] 83 | public string OldPassword { get; set; } = ""; 84 | 85 | [Required] 86 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 87 | [DataType(DataType.Password)] 88 | [Display(Name = "New password")] 89 | public string NewPassword { get; set; } = ""; 90 | 91 | [DataType(DataType.Password)] 92 | [Display(Name = "Confirm new password")] 93 | [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] 94 | public string ConfirmPassword { get; set; } = ""; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Account/Pages/Manage/DeletePersonalData.razor: -------------------------------------------------------------------------------- 1 | @page "/Account/Manage/DeletePersonalData" 2 | 3 | @using System.ComponentModel.DataAnnotations 4 | @using Microsoft.AspNetCore.Identity 5 | @using BlazorBlogs.Data 6 | 7 | @inject UserManager UserManager 8 | @inject SignInManager SignInManager 9 | @inject IdentityUserAccessor UserAccessor 10 | @inject IdentityRedirectManager RedirectManager 11 | @inject ILogger Logger 12 | 13 | Delete Personal Data 14 | 15 | 16 | 17 |

Delete Personal Data

18 | 19 | 24 | 25 |
26 | 27 | 28 | 29 | @if (requirePassword) 30 | { 31 |
32 | 33 | 34 | 35 |
36 | } 37 | 38 |
39 |
40 | 41 | @code { 42 | private string message; 43 | private ApplicationUser user = default!; 44 | private bool requirePassword; 45 | 46 | [CascadingParameter] 47 | private HttpContext HttpContext { get; set; } = default!; 48 | 49 | [SupplyParameterFromForm] 50 | private InputModel Input { get; set; } = new(); 51 | 52 | protected override async Task OnInitializedAsync() 53 | { 54 | Input ??= new(); 55 | user = await UserAccessor.GetRequiredUserAsync(HttpContext); 56 | requirePassword = await UserManager.HasPasswordAsync(user); 57 | } 58 | 59 | private async Task OnValidSubmitAsync() 60 | { 61 | if (requirePassword && !await UserManager.CheckPasswordAsync(user, Input.Password)) 62 | { 63 | message = "Error: Incorrect password."; 64 | return; 65 | } 66 | 67 | var result = await UserManager.DeleteAsync(user); 68 | if (!result.Succeeded) 69 | { 70 | throw new InvalidOperationException("Unexpected error occurred deleting user."); 71 | } 72 | 73 | await SignInManager.SignOutAsync(); 74 | 75 | var userId = await UserManager.GetUserIdAsync(user); 76 | Logger.LogInformation("User with ID '{UserId}' deleted themselves.", userId); 77 | 78 | RedirectManager.RedirectToCurrentPage(); 79 | } 80 | 81 | private sealed class InputModel 82 | { 83 | [DataType(DataType.Password)] 84 | public string Password { get; set; } = ""; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Account/Pages/Manage/Disable2fa.razor: -------------------------------------------------------------------------------- 1 | @page "/Account/Manage/Disable2fa" 2 | 3 | @using Microsoft.AspNetCore.Identity 4 | @using BlazorBlogs.Data 5 | 6 | @inject UserManager UserManager 7 | @inject IdentityUserAccessor UserAccessor 8 | @inject IdentityRedirectManager RedirectManager 9 | @inject ILogger Logger 10 | 11 | Disable two-factor authentication (2FA) 12 | 13 | 14 |

Disable two-factor authentication (2FA)

15 | 16 | 25 | 26 |
27 |
28 | 29 | 30 | 31 |
32 | 33 | @code { 34 | private ApplicationUser user = default!; 35 | 36 | [CascadingParameter] 37 | private HttpContext HttpContext { get; set; } = default!; 38 | 39 | protected override async Task OnInitializedAsync() 40 | { 41 | user = await UserAccessor.GetRequiredUserAsync(HttpContext); 42 | 43 | if (HttpMethods.IsGet(HttpContext.Request.Method) && !await UserManager.GetTwoFactorEnabledAsync(user)) 44 | { 45 | throw new InvalidOperationException("Cannot disable 2FA for user as it's not currently enabled."); 46 | } 47 | } 48 | 49 | private async Task OnSubmitAsync() 50 | { 51 | var disable2faResult = await UserManager.SetTwoFactorEnabledAsync(user, false); 52 | if (!disable2faResult.Succeeded) 53 | { 54 | throw new InvalidOperationException("Unexpected error occurred disabling 2FA."); 55 | } 56 | 57 | var userId = await UserManager.GetUserIdAsync(user); 58 | Logger.LogInformation("User with ID '{UserId}' has disabled 2fa.", userId); 59 | RedirectManager.RedirectToWithStatus( 60 | "Account/Manage/TwoFactorAuthentication", 61 | "2fa has been disabled. You can reenable 2fa when you setup an authenticator app", 62 | HttpContext); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Account/Pages/Manage/Email.razor: -------------------------------------------------------------------------------- 1 | @page "/Account/Manage/Email" 2 | 3 | @using System.ComponentModel.DataAnnotations 4 | @using System.Text 5 | @using System.Text.Encodings.Web 6 | @using Microsoft.AspNetCore.Identity 7 | @using Microsoft.AspNetCore.WebUtilities 8 | @using BlazorBlogs.Data 9 | 10 | @inject UserManager UserManager 11 | @inject IEmailSender EmailSender 12 | @inject IdentityUserAccessor UserAccessor 13 | @inject NavigationManager NavigationManager 14 | 15 | Manage email 16 | 17 |

Manage email

18 | 19 | 20 |
21 |
22 |
23 | 24 | 25 | 26 | 27 | 28 | @if (isEmailConfirmed) 29 | { 30 |
31 | 32 |
33 | 34 |
35 | 36 |
37 | } 38 | else 39 | { 40 |
41 | 42 | 43 | 44 |
45 | } 46 |
47 | 48 | 49 | 50 |
51 | 52 |
53 |
54 |
55 | 56 | @code { 57 | private string message; 58 | private ApplicationUser user = default!; 59 | private string email; 60 | private bool isEmailConfirmed; 61 | 62 | [CascadingParameter] 63 | private HttpContext HttpContext { get; set; } = default!; 64 | 65 | [SupplyParameterFromForm(FormName = "change-email")] 66 | private InputModel Input { get; set; } = new(); 67 | 68 | protected override async Task OnInitializedAsync() 69 | { 70 | user = await UserAccessor.GetRequiredUserAsync(HttpContext); 71 | email = await UserManager.GetEmailAsync(user); 72 | isEmailConfirmed = await UserManager.IsEmailConfirmedAsync(user); 73 | 74 | Input.NewEmail ??= email; 75 | } 76 | 77 | private async Task OnValidSubmitAsync() 78 | { 79 | if (Input.NewEmail is null || Input.NewEmail == email) 80 | { 81 | message = "Your email is unchanged."; 82 | return; 83 | } 84 | 85 | var userId = await UserManager.GetUserIdAsync(user); 86 | var code = await UserManager.GenerateChangeEmailTokenAsync(user, Input.NewEmail); 87 | code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); 88 | var callbackUrl = NavigationManager.GetUriWithQueryParameters( 89 | NavigationManager.ToAbsoluteUri("Account/ConfirmEmailChange").AbsoluteUri, 90 | new Dictionary { ["userId"] = userId, ["email"] = Input.NewEmail, ["code"] = code }); 91 | 92 | await EmailSender.SendConfirmationLinkAsync(user, Input.NewEmail, HtmlEncoder.Default.Encode(callbackUrl)); 93 | 94 | message = "Confirmation link to change email sent. Please check your email."; 95 | } 96 | 97 | private async Task OnSendEmailVerificationAsync() 98 | { 99 | if (email is null) 100 | { 101 | return; 102 | } 103 | 104 | var userId = await UserManager.GetUserIdAsync(user); 105 | var code = await UserManager.GenerateEmailConfirmationTokenAsync(user); 106 | code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); 107 | var callbackUrl = NavigationManager.GetUriWithQueryParameters( 108 | NavigationManager.ToAbsoluteUri("Account/ConfirmEmail").AbsoluteUri, 109 | new Dictionary { ["userId"] = userId, ["code"] = code }); 110 | 111 | await EmailSender.SendConfirmationLinkAsync(user, email, HtmlEncoder.Default.Encode(callbackUrl)); 112 | 113 | message = "Verification email sent. Please check your email."; 114 | } 115 | 116 | private sealed class InputModel 117 | { 118 | [Required] 119 | [EmailAddress] 120 | [Display(Name = "New email")] 121 | public string NewEmail { get; set; } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Account/Pages/Manage/GenerateRecoveryCodes.razor: -------------------------------------------------------------------------------- 1 | @page "/Account/Manage/GenerateRecoveryCodes" 2 | 3 | @using Microsoft.AspNetCore.Identity 4 | @using BlazorBlogs.Data 5 | 6 | @inject UserManager UserManager 7 | @inject IdentityUserAccessor UserAccessor 8 | @inject IdentityRedirectManager RedirectManager 9 | @inject ILogger Logger 10 | 11 | Generate two-factor authentication (2FA) recovery codes 12 | 13 | @if (recoveryCodes is not null) 14 | { 15 | 16 | } 17 | else 18 | { 19 |

Generate two-factor authentication (2FA) recovery codes

20 | 33 |
34 |
35 | 36 | 37 | 38 |
39 | } 40 | 41 | @code { 42 | private string message; 43 | private ApplicationUser user = default!; 44 | private IEnumerable recoveryCodes; 45 | 46 | [CascadingParameter] 47 | private HttpContext HttpContext { get; set; } = default!; 48 | 49 | protected override async Task OnInitializedAsync() 50 | { 51 | user = await UserAccessor.GetRequiredUserAsync(HttpContext); 52 | 53 | var isTwoFactorEnabled = await UserManager.GetTwoFactorEnabledAsync(user); 54 | if (!isTwoFactorEnabled) 55 | { 56 | throw new InvalidOperationException("Cannot generate recovery codes for user because they do not have 2FA enabled."); 57 | } 58 | } 59 | 60 | private async Task OnSubmitAsync() 61 | { 62 | var userId = await UserManager.GetUserIdAsync(user); 63 | recoveryCodes = await UserManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10); 64 | message = "You have generated new recovery codes."; 65 | 66 | Logger.LogInformation("User with ID '{UserId}' has generated new 2FA recovery codes.", userId); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Account/Pages/Manage/Index.razor: -------------------------------------------------------------------------------- 1 | @page "/Account/Manage" 2 | 3 | @using System.ComponentModel.DataAnnotations 4 | @using Microsoft.AspNetCore.Identity 5 | @using BlazorBlogs.Data 6 | 7 | @inject UserManager UserManager 8 | @inject SignInManager SignInManager 9 | @inject IdentityUserAccessor UserAccessor 10 | @inject IdentityRedirectManager RedirectManager 11 | 12 | Profile 13 | 14 |

Profile

15 | 16 | 17 |
18 |
19 | 20 | 21 | 22 |
23 | 24 | 25 |
26 |
27 | 28 | 29 | 30 |
31 | 32 |
33 |
34 |
35 | 36 | @code { 37 | private ApplicationUser user = default!; 38 | private string username; 39 | private string phoneNumber; 40 | 41 | [CascadingParameter] 42 | private HttpContext HttpContext { get; set; } = default!; 43 | 44 | [SupplyParameterFromForm] 45 | private InputModel Input { get; set; } = new(); 46 | 47 | protected override async Task OnInitializedAsync() 48 | { 49 | user = await UserAccessor.GetRequiredUserAsync(HttpContext); 50 | username = await UserManager.GetUserNameAsync(user); 51 | phoneNumber = await UserManager.GetPhoneNumberAsync(user); 52 | 53 | Input.PhoneNumber ??= phoneNumber; 54 | } 55 | 56 | private async Task OnValidSubmitAsync() 57 | { 58 | if (Input.PhoneNumber != phoneNumber) 59 | { 60 | var setPhoneResult = await UserManager.SetPhoneNumberAsync(user, Input.PhoneNumber); 61 | if (!setPhoneResult.Succeeded) 62 | { 63 | RedirectManager.RedirectToCurrentPageWithStatus("Error: Failed to set phone number.", HttpContext); 64 | } 65 | } 66 | 67 | await SignInManager.RefreshSignInAsync(user); 68 | RedirectManager.RedirectToCurrentPageWithStatus("Your profile has been updated", HttpContext); 69 | } 70 | 71 | private sealed class InputModel 72 | { 73 | [Phone] 74 | [Display(Name = "Phone number")] 75 | public string PhoneNumber { get; set; } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Account/Pages/Manage/PersonalData.razor: -------------------------------------------------------------------------------- 1 | @page "/Account/Manage/PersonalData" 2 | 3 | @inject IdentityUserAccessor UserAccessor 4 | 5 | Personal Data 6 | 7 | 8 |

Personal Data

9 | 10 |
11 |
12 |

Your account contains personal data that you have given us. This page allows you to download or delete that data.

13 |

14 | Deleting this data will permanently remove your account, and this cannot be recovered. 15 |

16 |
17 | 18 | 19 | 20 |

21 | Delete 22 |

23 |
24 |
25 | 26 | @code { 27 | [CascadingParameter] 28 | private HttpContext HttpContext { get; set; } = default!; 29 | 30 | protected override async Task OnInitializedAsync() 31 | { 32 | _ = await UserAccessor.GetRequiredUserAsync(HttpContext); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Account/Pages/Manage/ResetAuthenticator.razor: -------------------------------------------------------------------------------- 1 | @page "/Account/Manage/ResetAuthenticator" 2 | 3 | @using Microsoft.AspNetCore.Identity 4 | @using BlazorBlogs.Data 5 | 6 | @inject UserManager UserManager 7 | @inject SignInManager SignInManager 8 | @inject IdentityUserAccessor UserAccessor 9 | @inject IdentityRedirectManager RedirectManager 10 | @inject ILogger Logger 11 | 12 | Reset authenticator key 13 | 14 | 15 |

Reset authenticator key

16 | 26 |
27 |
28 | 29 | 30 | 31 |
32 | 33 | @code { 34 | [CascadingParameter] 35 | private HttpContext HttpContext { get; set; } = default!; 36 | 37 | private async Task OnSubmitAsync() 38 | { 39 | var user = await UserAccessor.GetRequiredUserAsync(HttpContext); 40 | await UserManager.SetTwoFactorEnabledAsync(user, false); 41 | await UserManager.ResetAuthenticatorKeyAsync(user); 42 | var userId = await UserManager.GetUserIdAsync(user); 43 | Logger.LogInformation("User with ID '{UserId}' has reset their authentication app key.", userId); 44 | 45 | await SignInManager.RefreshSignInAsync(user); 46 | 47 | RedirectManager.RedirectToWithStatus( 48 | "Account/Manage/EnableAuthenticator", 49 | "Your authenticator app key has been reset, you will need to configure your authenticator app using the new key.", 50 | HttpContext); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Account/Pages/Manage/SetPassword.razor: -------------------------------------------------------------------------------- 1 | @page "/Account/Manage/SetPassword" 2 | 3 | @using System.ComponentModel.DataAnnotations 4 | @using Microsoft.AspNetCore.Identity 5 | @using BlazorBlogs.Data 6 | 7 | @inject UserManager UserManager 8 | @inject SignInManager SignInManager 9 | @inject IdentityUserAccessor UserAccessor 10 | @inject IdentityRedirectManager RedirectManager 11 | 12 | Set password 13 | 14 |

Set your password

15 | 16 |

17 | You do not have a local username/password for this site. Add a local 18 | account so you can log in without an external login. 19 |

20 |
21 |
22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 |
30 |
31 | 32 | 33 | 34 |
35 | 36 |
37 |
38 |
39 | 40 | @code { 41 | private string message; 42 | private ApplicationUser user = default!; 43 | 44 | [CascadingParameter] 45 | private HttpContext HttpContext { get; set; } = default!; 46 | 47 | [SupplyParameterFromForm] 48 | private InputModel Input { get; set; } = new(); 49 | 50 | protected override async Task OnInitializedAsync() 51 | { 52 | user = await UserAccessor.GetRequiredUserAsync(HttpContext); 53 | 54 | var hasPassword = await UserManager.HasPasswordAsync(user); 55 | if (hasPassword) 56 | { 57 | RedirectManager.RedirectTo("Account/Manage/ChangePassword"); 58 | } 59 | } 60 | 61 | private async Task OnValidSubmitAsync() 62 | { 63 | var addPasswordResult = await UserManager.AddPasswordAsync(user, Input.NewPassword!); 64 | if (!addPasswordResult.Succeeded) 65 | { 66 | message = $"Error: {string.Join(",", addPasswordResult.Errors.Select(error => error.Description))}"; 67 | return; 68 | } 69 | 70 | await SignInManager.RefreshSignInAsync(user); 71 | RedirectManager.RedirectToCurrentPageWithStatus("Your password has been set.", HttpContext); 72 | } 73 | 74 | private sealed class InputModel 75 | { 76 | [Required] 77 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 78 | [DataType(DataType.Password)] 79 | [Display(Name = "New password")] 80 | public string NewPassword { get; set; } 81 | 82 | [DataType(DataType.Password)] 83 | [Display(Name = "Confirm new password")] 84 | [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] 85 | public string ConfirmPassword { get; set; } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Account/Pages/Manage/TwoFactorAuthentication.razor: -------------------------------------------------------------------------------- 1 | @page "/Account/Manage/TwoFactorAuthentication" 2 | 3 | @using Microsoft.AspNetCore.Http.Features 4 | @using Microsoft.AspNetCore.Identity 5 | @using BlazorBlogs.Data 6 | 7 | @inject UserManager UserManager 8 | @inject SignInManager SignInManager 9 | @inject IdentityUserAccessor UserAccessor 10 | @inject IdentityRedirectManager RedirectManager 11 | 12 | Two-factor authentication (2FA) 13 | 14 | 15 |

Two-factor authentication (2FA)

16 | @if (canTrack) 17 | { 18 | if (is2faEnabled) 19 | { 20 | if (recoveryCodesLeft == 0) 21 | { 22 |
23 | You have no recovery codes left. 24 |

You must generate a new set of recovery codes before you can log in with a recovery code.

25 |
26 | } 27 | else if (recoveryCodesLeft == 1) 28 | { 29 |
30 | You have 1 recovery code left. 31 |

You can generate a new set of recovery codes.

32 |
33 | } 34 | else if (recoveryCodesLeft <= 3) 35 | { 36 |
37 | You have @recoveryCodesLeft recovery codes left. 38 |

You should generate a new set of recovery codes.

39 |
40 | } 41 | 42 | if (isMachineRemembered) 43 | { 44 |
45 | 46 | 47 | 48 | } 49 | 50 | Disable 2FA 51 | Reset recovery codes 52 | } 53 | 54 |

Authenticator app

55 | @if (!hasAuthenticator) 56 | { 57 | Add authenticator app 58 | } 59 | else 60 | { 61 | Set up authenticator app 62 | Reset authenticator app 63 | } 64 | } 65 | else 66 | { 67 |
68 | Privacy and cookie policy have not been accepted. 69 |

You must accept the policy before you can enable two factor authentication.

70 |
71 | } 72 | 73 | @code { 74 | private bool canTrack; 75 | private bool hasAuthenticator; 76 | private int recoveryCodesLeft; 77 | private bool is2faEnabled; 78 | private bool isMachineRemembered; 79 | 80 | [CascadingParameter] 81 | private HttpContext HttpContext { get; set; } = default!; 82 | 83 | protected override async Task OnInitializedAsync() 84 | { 85 | var user = await UserAccessor.GetRequiredUserAsync(HttpContext); 86 | canTrack = HttpContext.Features.Get()?.CanTrack ?? true; 87 | hasAuthenticator = await UserManager.GetAuthenticatorKeyAsync(user) is not null; 88 | is2faEnabled = await UserManager.GetTwoFactorEnabledAsync(user); 89 | isMachineRemembered = await SignInManager.IsTwoFactorClientRememberedAsync(user); 90 | recoveryCodesLeft = await UserManager.CountRecoveryCodesAsync(user); 91 | } 92 | 93 | private async Task OnSubmitForgetBrowserAsync() 94 | { 95 | await SignInManager.ForgetTwoFactorClientAsync(); 96 | 97 | RedirectManager.RedirectToCurrentPageWithStatus( 98 | "The current browser has been forgotten. When you login again from this browser you will be prompted for your 2fa code.", 99 | HttpContext); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Account/Pages/Manage/_Imports.razor: -------------------------------------------------------------------------------- 1 | @layout ManageLayout 2 | @attribute [Microsoft.AspNetCore.Authorization.Authorize] 3 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Account/Pages/RegisterConfirmation.razor: -------------------------------------------------------------------------------- 1 | @page "/Account/RegisterConfirmation" 2 | 3 | @using System.Text 4 | @using Microsoft.AspNetCore.Identity 5 | @using Microsoft.AspNetCore.WebUtilities 6 | @using BlazorBlogs.Data 7 | 8 | @inject UserManager UserManager 9 | @inject IEmailSender EmailSender 10 | @inject NavigationManager NavigationManager 11 | @inject IdentityRedirectManager RedirectManager 12 | 13 | Register confirmation 14 | 15 |

Register confirmation

16 | 17 | 18 | 19 |

Please check your email to confirm your account.

20 | 21 | @code { 22 | private string statusMessage; 23 | 24 | [CascadingParameter] 25 | private HttpContext HttpContext { get; set; } = default!; 26 | 27 | [SupplyParameterFromQuery] 28 | private string Email { get; set; } 29 | 30 | [SupplyParameterFromQuery] 31 | private string ReturnUrl { get; set; } 32 | 33 | protected override async Task OnInitializedAsync() 34 | { 35 | if (Email is null) 36 | { 37 | RedirectManager.RedirectTo(""); 38 | } 39 | 40 | var user = await UserManager.FindByEmailAsync(Email); 41 | if (user is null) 42 | { 43 | HttpContext.Response.StatusCode = StatusCodes.Status404NotFound; 44 | statusMessage = "Error finding user for unspecified email"; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Account/Pages/ResendEmailConfirmation.razor: -------------------------------------------------------------------------------- 1 | @page "/Account/ResendEmailConfirmation" 2 | 3 | @using System.ComponentModel.DataAnnotations 4 | @using System.Text 5 | @using System.Text.Encodings.Web 6 | @using Microsoft.AspNetCore.Identity 7 | @using Microsoft.AspNetCore.WebUtilities 8 | @using BlazorBlogs.Data 9 | 10 | @inject UserManager UserManager 11 | @inject IEmailSender EmailSender 12 | @inject NavigationManager NavigationManager 13 | @inject IdentityRedirectManager RedirectManager 14 | 15 | Resend email confirmation 16 | 17 |

Resend email confirmation

18 |

Enter your email.

19 |
20 | 21 |
22 |
23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
31 | 32 |
33 |
34 |
35 | 36 | @code { 37 | private string message; 38 | 39 | [SupplyParameterFromForm] 40 | private InputModel Input { get; set; } = new(); 41 | 42 | private async Task OnValidSubmitAsync() 43 | { 44 | var user = await UserManager.FindByEmailAsync(Input.Email!); 45 | if (user is null) 46 | { 47 | message = "Verification email sent. Please check your email."; 48 | return; 49 | } 50 | 51 | var userId = await UserManager.GetUserIdAsync(user); 52 | var code = await UserManager.GenerateEmailConfirmationTokenAsync(user); 53 | code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); 54 | var callbackUrl = NavigationManager.GetUriWithQueryParameters( 55 | NavigationManager.ToAbsoluteUri("Account/ConfirmEmail").AbsoluteUri, 56 | new Dictionary { ["userId"] = userId, ["code"] = code }); 57 | await EmailSender.SendConfirmationLinkAsync(user, Input.Email, HtmlEncoder.Default.Encode(callbackUrl)); 58 | 59 | message = "Verification email sent. Please check your email."; 60 | } 61 | 62 | private sealed class InputModel 63 | { 64 | [Required] 65 | [EmailAddress] 66 | public string Email { get; set; } = ""; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Account/Pages/ResetPassword.razor: -------------------------------------------------------------------------------- 1 | @page "/Account/ResetPassword" 2 | 3 | @using System.ComponentModel.DataAnnotations 4 | @using System.Text 5 | @using Microsoft.AspNetCore.Identity 6 | @using Microsoft.AspNetCore.WebUtilities 7 | @using BlazorBlogs.Data 8 | 9 | @inject IdentityRedirectManager RedirectManager 10 | @inject UserManager UserManager 11 | 12 | Reset password 13 | 14 |

Reset password

15 |

Reset your password.

16 |
17 |
18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 |
30 |
31 | 32 | 33 | 34 |
35 |
36 | 37 | 38 | 39 |
40 | 41 |
42 |
43 |
44 | 45 | @code { 46 | private IEnumerable identityErrors; 47 | 48 | [SupplyParameterFromForm] 49 | private InputModel Input { get; set; } = new(); 50 | 51 | [SupplyParameterFromQuery] 52 | private string Code { get; set; } 53 | 54 | private string Message => identityErrors is null ? null : $"Error: {string.Join(", ", identityErrors.Select(error => error.Description))}"; 55 | 56 | protected override void OnInitialized() 57 | { 58 | if (Code is null) 59 | { 60 | RedirectManager.RedirectTo("Account/InvalidPasswordReset"); 61 | } 62 | 63 | Input.Code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(Code)); 64 | } 65 | 66 | private async Task OnValidSubmitAsync() 67 | { 68 | var user = await UserManager.FindByEmailAsync(Input.Email); 69 | if (user is null) 70 | { 71 | // Don't reveal that the user does not exist 72 | RedirectManager.RedirectTo("Account/ResetPasswordConfirmation"); 73 | } 74 | 75 | var result = await UserManager.ResetPasswordAsync(user, Input.Code, Input.Password); 76 | if (result.Succeeded) 77 | { 78 | RedirectManager.RedirectTo("Account/ResetPasswordConfirmation"); 79 | } 80 | 81 | identityErrors = result.Errors; 82 | } 83 | 84 | private sealed class InputModel 85 | { 86 | [Required] 87 | [EmailAddress] 88 | public string Email { get; set; } = ""; 89 | 90 | [Required] 91 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 92 | [DataType(DataType.Password)] 93 | public string Password { get; set; } = ""; 94 | 95 | [DataType(DataType.Password)] 96 | [Display(Name = "Confirm password")] 97 | [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] 98 | public string ConfirmPassword { get; set; } = ""; 99 | 100 | [Required] 101 | public string Code { get; set; } = ""; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Account/Pages/ResetPasswordConfirmation.razor: -------------------------------------------------------------------------------- 1 | @page "/Account/ResetPasswordConfirmation" 2 | Reset password confirmation 3 | 4 |

Reset password confirmation

5 |

6 | Your password has been reset. Please click here to log in. 7 |

8 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Account/Pages/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using BlazorBlogs.Components.Account.Shared 2 | @attribute [ExcludeFromInteractiveRouting] 3 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Account/Shared/ExternalLoginPicker.razor: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Authentication 2 | @using Microsoft.AspNetCore.Identity 3 | @using BlazorBlogs.Data 4 | 5 | @inject SignInManager SignInManager 6 | @inject IdentityRedirectManager RedirectManager 7 | 8 | @if (externalLogins.Length == 0) 9 | { 10 |
11 |

12 | There are no external authentication services configured. See this article 13 | about setting up this ASP.NET application to support logging in via external services. 14 |

15 |
16 | } 17 | else 18 | { 19 |
20 |
21 | 22 | 23 |

24 | @foreach (var provider in externalLogins) 25 | { 26 | 27 | } 28 |

29 |
30 |
31 | } 32 | 33 | @code { 34 | private AuthenticationScheme[] externalLogins = []; 35 | 36 | [SupplyParameterFromQuery] 37 | private string ReturnUrl { get; set; } 38 | 39 | protected override async Task OnInitializedAsync() 40 | { 41 | externalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).ToArray(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Account/Shared/ManageLayout.razor: -------------------------------------------------------------------------------- 1 | @inherits LayoutComponentBase 2 | @layout BlazorBlogs.Components.Layout.MainLayout 3 | 4 |

Manage your account

5 | 6 |
7 |

Change your account settings

8 |
9 |
10 |
11 | 12 |
13 |
14 | @Body 15 |
16 |
17 |
18 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Account/Shared/ManageNavMenu.razor: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Identity 2 | @using BlazorBlogs.Data 3 | 4 | @inject SignInManager SignInManager 5 | 6 | 20 | 21 | @code { 22 | private bool hasExternalLogins; 23 | 24 | protected override async Task OnInitializedAsync() 25 | { 26 | hasExternalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).Any(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Account/Shared/RedirectToLogin.razor: -------------------------------------------------------------------------------- 1 | @inject NavigationManager NavigationManager 2 | 3 | @code { 4 | protected override void OnInitialized() 5 | { 6 | NavigationManager.NavigateTo($"Account/Login?returnUrl={Uri.EscapeDataString(NavigationManager.Uri)}", forceLoad: true); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Account/Shared/ShowRecoveryCodes.razor: -------------------------------------------------------------------------------- 1 |  2 |

Recovery codes

3 | 11 |
12 |
13 | @foreach (var recoveryCode in RecoveryCodes) 14 | { 15 |
16 | @recoveryCode 17 |
18 | } 19 |
20 |
21 | 22 | @code { 23 | [Parameter] 24 | public string[] RecoveryCodes { get; set; } = []; 25 | 26 | [Parameter] 27 | public string StatusMessage { get; set; } 28 | } 29 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Account/Shared/StatusMessage.razor: -------------------------------------------------------------------------------- 1 | @if (!string.IsNullOrEmpty(DisplayMessage)) 2 | { 3 | var statusMessageClass = DisplayMessage.StartsWith("Error") ? "danger" : "success"; 4 | 7 | } 8 | 9 | @code { 10 | private string messageFromCookie; 11 | 12 | [Parameter] 13 | public string Message { get; set; } 14 | 15 | [CascadingParameter] 16 | private HttpContext HttpContext { get; set; } = default!; 17 | 18 | private string DisplayMessage => Message ?? messageFromCookie; 19 | 20 | protected override void OnInitialized() 21 | { 22 | messageFromCookie = HttpContext.Request.Cookies[IdentityRedirectManager.StatusCookieName]; 23 | 24 | if (messageFromCookie is not null) 25 | { 26 | HttpContext.Response.Cookies.Delete(IdentityRedirectManager.StatusCookieName); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/App.razor: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 66 | 67 | 68 | 69 | 70 | @code { 71 | [CascadingParameter] 72 | private HttpContext HttpContext { get; set; } = default!; 73 | 74 | private IComponentRenderMode PageRenderMode => 75 | HttpContext.AcceptsInteractiveRouting() ? InteractiveServer : null; 76 | } 77 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Layout/LoginDisplay.razor: -------------------------------------------------------------------------------- 1 | @using BlazorBlogs.Data; 2 | @inject NavigationManager NavigationManager 3 | @inherits OwningComponentBase 4 | 5 | 6 | 7 | Hello, @context.User.Identity.Name! 8 |
9 | 10 | 11 | 12 | 13 |
14 | 15 | @if (AllowRegistration) 16 | { 17 | Register 18 | } 19 | Log in 20 | 21 |
22 | @code { 23 | bool AllowRegistration = false; 24 | private string currentUrl; 25 | 26 | protected override async Task OnInitializedAsync() 27 | { 28 | var objGeneralSettings = await @Service.GetGeneralSettingsAsync(); 29 | AllowRegistration = objGeneralSettings.AllowRegistration; 30 | } 31 | 32 | protected override void OnInitialized() 33 | { 34 | currentUrl = NavigationManager.ToBaseRelativePath(NavigationManager.Uri); 35 | NavigationManager.LocationChanged += OnLocationChanged; 36 | } 37 | 38 | private void OnLocationChanged(object sender, LocationChangedEventArgs e) 39 | { 40 | currentUrl = NavigationManager.ToBaseRelativePath(e.Location); 41 | StateHasChanged(); 42 | } 43 | } -------------------------------------------------------------------------------- /BlazorBlogs/Components/Layout/MainLayout.razor.css: -------------------------------------------------------------------------------- 1 | .page { 2 | position: relative; 3 | display: flex; 4 | flex-direction: column; 5 | } 6 | 7 | main { 8 | flex: 1; 9 | } 10 | 11 | .sidebar { 12 | background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); 13 | } 14 | 15 | .top-row { 16 | background-color: #f7f7f7; 17 | border-bottom: 1px solid #d6d5d5; 18 | justify-content: flex-end; 19 | height: 3.5rem; 20 | display: flex; 21 | align-items: center; 22 | } 23 | 24 | .top-row ::deep a, .top-row ::deep .btn-link { 25 | white-space: nowrap; 26 | margin-left: 1.5rem; 27 | text-decoration: none; 28 | } 29 | 30 | .top-row ::deep a:hover, .top-row ::deep .btn-link:hover { 31 | text-decoration: underline; 32 | } 33 | 34 | .top-row ::deep a:first-child { 35 | overflow: hidden; 36 | text-overflow: ellipsis; 37 | } 38 | 39 | @media (max-width: 640.98px) { 40 | .top-row { 41 | justify-content: space-between; 42 | } 43 | 44 | .top-row ::deep a, .top-row ::deep .btn-link { 45 | margin-left: 0; 46 | } 47 | } 48 | 49 | @media (min-width: 641px) { 50 | .page { 51 | flex-direction: row; 52 | } 53 | 54 | .sidebar { 55 | width: 250px; 56 | height: 100vh; 57 | position: sticky; 58 | top: 0; 59 | } 60 | 61 | .top-row { 62 | position: sticky; 63 | top: 0; 64 | z-index: 1; 65 | } 66 | 67 | .top-row.auth ::deep a:first-child { 68 | flex: 1; 69 | text-align: right; 70 | width: 0; 71 | } 72 | 73 | .top-row, article { 74 | padding-left: 2rem !important; 75 | padding-right: 1.5rem !important; 76 | } 77 | } 78 | 79 | #blazor-error-ui { 80 | color-scheme: light only; 81 | background: lightyellow; 82 | bottom: 0; 83 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); 84 | box-sizing: border-box; 85 | display: none; 86 | left: 0; 87 | padding: 0.6rem 1.25rem 0.7rem 1.25rem; 88 | position: fixed; 89 | width: 100%; 90 | z-index: 1000; 91 | } 92 | 93 | #blazor-error-ui .dismiss { 94 | cursor: pointer; 95 | position: absolute; 96 | right: 0.75rem; 97 | top: 0.5rem; 98 | } 99 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Layout/NavMenu.razor.css: -------------------------------------------------------------------------------- 1 | .navbar-toggler { 2 | background-color: rgba(255, 255, 255, 0.1); 3 | } 4 | 5 | .top-row { 6 | height: 3.5rem; 7 | background-color: rgba(0,0,0,0.4); 8 | } 9 | 10 | .navbar-brand { 11 | font-size: 1.1rem; 12 | } 13 | 14 | .oi { 15 | width: 2rem; 16 | font-size: 1.1rem; 17 | vertical-align: text-top; 18 | top: -2px; 19 | } 20 | 21 | .nav-item { 22 | font-size: 0.9rem; 23 | padding-bottom: 0.5rem; 24 | } 25 | 26 | .nav-item:first-of-type { 27 | padding-top: 1rem; 28 | } 29 | 30 | .nav-item:last-of-type { 31 | padding-bottom: 1rem; 32 | } 33 | 34 | .nav-item ::deep a { 35 | color: #d7d7d7; 36 | border-radius: 4px; 37 | height: 3rem; 38 | display: flex; 39 | align-items: center; 40 | line-height: 3rem; 41 | } 42 | 43 | .nav-item ::deep a.active { 44 | background-color: rgba(255,255,255,0.25); 45 | color: white; 46 | } 47 | 48 | .nav-item ::deep a:hover { 49 | background-color: rgba(255,255,255,0.1); 50 | color: white; 51 | } 52 | 53 | @media (min-width: 641px) { 54 | .navbar-toggler { 55 | display: none; 56 | } 57 | 58 | .collapse { 59 | /* Never collapse the sidebar for wide screens */ 60 | display: block; 61 | } 62 | 63 | .nav-scrollable { 64 | /* Allow sidebar to scroll for tall menus */ 65 | height: calc(100vh - 3.5rem); 66 | overflow-y: auto; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Pages/CategoryAdministration.razor: -------------------------------------------------------------------------------- 1 | @page "/categories" 2 | @using Microsoft.AspNetCore.Identity; 3 | @using BlazorBlogs.Data; 4 | @using BlazorBlogs.Data.Models; 5 | @inject UserManager _UserManager 6 | @inject RoleManager _RoleManager 7 | @inject AuthenticationStateProvider AuthenticationStateProvider 8 | @inherits OwningComponentBase 9 | @if (CurrentUser.IsInRole(ADMINISTRATION_ROLE)) 10 | { 11 |
12 |

Category Administration

13 |
14 |
15 | 23 |
24 | @if (SelectedBlog != null) 25 | { 26 |
27 | 35 |
36 |
37 |
38 | 42 |
43 | } 44 |

@strError

45 |
46 | 47 | 49 | } 50 | @code { 51 | // AuthenticationState is available as a CascadingParameter 52 | [CascadingParameter] 53 | private Task authenticationStateTask { get; set; } 54 | 55 | string strError = ""; 56 | string ADMINISTRATION_ROLE = "Administrators"; 57 | 58 | public bool ConFirmDeletePopup = false; 59 | 60 | public System.Security.Claims.ClaimsPrincipal CurrentUser; 61 | 62 | List ColBlogEntries = new List(); 63 | Blogs SelectedBlog; 64 | 65 | private CategoryManager CategoryManagerControl; 66 | 67 | List colCategorys = new List(); 68 | IEnumerable selectedBlogCategorys = new string[] { }; 69 | 70 | protected override async Task OnInitializedAsync() 71 | { 72 | // Get the current user 73 | CurrentUser = (await authenticationStateTask).User; 74 | 75 | ColBlogEntries = await @Service.GetAllBlogsAsync(CurrentUser.Identity.Name.ToLower()); 76 | colCategorys = await @Service.GetCategorysAsync(); 77 | } 78 | 79 | void ChangeBlog(object value, string name) 80 | { 81 | // Set the selected Blog Categorys 82 | List BlogCatagories = SelectedBlog.BlogCategory.Select(x => x.CategoryId.ToString()).ToList(); 83 | selectedBlogCategorys = BlogCatagories.ToArray(); 84 | strError = ""; 85 | StateHasChanged(); 86 | } 87 | 88 | async Task SaveBlog() 89 | { 90 | try 91 | { 92 | var result = 93 | await @Service.UpdateBlogCategoriesAsync(SelectedBlog, selectedBlogCategorys); 94 | 95 | strError = "Updated!"; 96 | StateHasChanged(); 97 | } 98 | catch (Exception ex) 99 | { 100 | strError = ex.GetBaseException().Message; 101 | } 102 | } 103 | 104 | //CategoryManagerControl 105 | 106 | void OpenCategoryManagerControl() 107 | { 108 | // Open CategoryManagerControl 109 | CategoryManagerControl.SetShowManager(true); 110 | } 111 | 112 | async Task UpdateCategories() 113 | { 114 | colCategorys = await @Service.GetCategorysAsync(); 115 | } 116 | 117 | async Task ClosePopup() 118 | { 119 | // Refresh collection 120 | await UpdateCategories(); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Pages/Error.razor: -------------------------------------------------------------------------------- 1 | @page "/Error" 2 | @using System.Diagnostics 3 | 4 | Error 5 | 6 |

Error.

7 |

An error occurred while processing your request.

8 | 9 | @if (ShowRequestId) 10 | { 11 |

12 | Request ID: @RequestId 13 |

14 | } 15 | 16 |

Development Mode

17 |

18 | Swapping to Development environment will display more detailed information about the error that occurred. 19 |

20 |

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

26 | 27 | @code{ 28 | [CascadingParameter] 29 | private HttpContext HttpContext { get; set; } 30 | 31 | private string RequestId { get; set; } 32 | private bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 33 | 34 | protected override void OnInitialized() => 35 | RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier; 36 | } 37 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Pages/InstallUpgrade/InstallWizard.razor: -------------------------------------------------------------------------------- 1 | @rendermode InteractiveServer 2 | @layout InstallWizardLayout 3 | @page "/installwizard/{WizardMode}" 4 | @using System.Security.Claims; 5 | @using BlazorBlogs.Data 6 | @using Microsoft.Extensions.Hosting 7 | @inject IConfiguration _configuration 8 | @inject InstallUpdateState InstallUpdateState 9 | @inject NavigationManager NavigationManager 10 | @inject BlogsService _BlogsService 11 | @inject IHostApplicationLifetime HostApplicationLifetime 12 | 13 |

Install Upgrade Wizard

14 |
15 | 16 | @if (WizardMode == "INSTALL") 17 | { 18 | @if (WizardStage == "DatabaseConfiguration") 19 | { 20 | 21 | } 22 | 23 | @if (WizardStage == "RunScripts") 24 | { 25 | 26 | } 27 | 28 | @if (WizardStage == "RunScriptsComplete") 29 | { 30 |

Warning! Site will now restart

31 | 32 | } 33 | } 34 | 35 | @if (WizardMode == "RUNSCRIPTS") 36 | { 37 | @if (WizardStage == "RunScripts") 38 | { 39 | 40 | } 41 | 42 | @if (WizardStage == "RunScriptsComplete") 43 | { 44 | 45 | } 46 | } 47 | 48 | @if (WizardMode == "UPGRADE") 49 | { 50 | @if (WizardStage == "VerifyAdministrator") 51 | { 52 | 53 | } 54 | 55 | @if (WizardStage == "RunScripts") 56 | { 57 | 58 | } 59 | 60 | @if (WizardStage == "RunScriptsComplete") 61 | { 62 | 63 | } 64 | } 65 | 66 | @if (WizardMode == "CreateAdministrator") 67 | { 68 | @if (WizardStage == "CreateAdministrator") 69 | { 70 | 71 | } 72 | 73 | @if (WizardStage == "Continue") 74 | { 75 | 76 | } 77 | } 78 | 79 | @code { 80 | [CascadingParameter] 81 | private Task authenticationStateTask { get; set; } 82 | 83 | [Parameter] public string WizardMode { get; set; } 84 | 85 | string ADMINISTRATION_ROLE = "Administrators"; 86 | ClaimsPrincipal CurrentUser = new ClaimsPrincipal(); 87 | public string WizardStage = ""; 88 | 89 | protected override async Task OnInitializedAsync() 90 | { 91 | // Get current user 92 | CurrentUser = (await authenticationStateTask).User; 93 | 94 | if (WizardMode == "INSTALL") 95 | { 96 | WizardStage = "DatabaseConfiguration"; 97 | } 98 | 99 | if (WizardMode == "RUNSCRIPTS") 100 | { 101 | WizardStage = "RunScripts"; 102 | } 103 | 104 | if (WizardMode == "UPGRADE") 105 | { 106 | if (!CurrentUser.IsInRole(ADMINISTRATION_ROLE)) 107 | { 108 | WizardStage = "VerifyAdministrator"; 109 | } 110 | else 111 | { 112 | WizardStage = "RunScripts"; 113 | } 114 | } 115 | 116 | if (WizardMode == "CreateAdministrator") 117 | { 118 | WizardStage = "CreateAdministrator"; 119 | } 120 | } 121 | 122 | // Methods 123 | 124 | void DatabaseConfigurationComplete(bool paramValue) 125 | { 126 | if (paramValue) 127 | { 128 | WizardStage = "RunScripts"; 129 | } 130 | } 131 | 132 | void CreateAdministratorComplete(bool paramValue) 133 | { 134 | if (paramValue) 135 | { 136 | WizardStage = "Continue"; 137 | } 138 | } 139 | 140 | void RunScriptsComplete(bool paramValue) 141 | { 142 | if (paramValue) 143 | { 144 | WizardStage = "RunScriptsComplete"; 145 | } 146 | } 147 | 148 | void Continue() 149 | { 150 | if (WizardMode == "INSTALL") 151 | { 152 | @if (WizardStage == "RunScriptsComplete") 153 | { 154 | // Restart site 155 | HostApplicationLifetime.StopApplication(); 156 | 157 | NavigationManager.NavigateTo($"/", true); 158 | } 159 | } 160 | 161 | string url = NavigationManager.ToAbsoluteUri($"/").AbsoluteUri; 162 | NavigationManager.NavigateTo(url, true); 163 | } 164 | } 165 | 166 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Pages/InstallUpgrade/InstallWizardCreateAdministrator.razor: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Identity; 2 | @using BlazorBlogs.Data; 3 | @using BlazorBlogs.Data.Models; 4 | @inject IConfiguration _configuration 5 | @inject UserManager _UserManager 6 | @inject RoleManager _RoleManager 7 | @inject BlogsService _BlogsService 8 | 9 |

Create Administrator

10 |
11 |
12 | @if (@ErrorMessage != "") 13 | { 14 |

@ErrorMessage

15 | } 16 | 17 | 18 | 19 | 20 | 23 | 26 | 27 | 28 | 31 | 34 | 35 | 36 | 43 | 44 | 45 |
21 | 22 | 24 | 25 |
29 | 30 | 32 | 33 |
37 | 42 |
46 | 47 | 48 | @code { 49 | [CascadingParameter] 50 | private Task authenticationStateTask { get; set; } 51 | 52 | // CreateAdministratorChanged is an EventCallback that will 53 | // notify the parent component CreateAdministrator Is complete 54 | [Parameter] public EventCallback CreateAdministratorChanged { get; set; } 55 | 56 | string ErrorMessage = ""; 57 | string ADMINISTRATION_ROLE = "Administrators"; 58 | string strAdministratorUserName = ""; 59 | string strAdministratorPassword = ""; 60 | 61 | protected override async Task OnInitializedAsync() 62 | { 63 | // Don't allow this to be used if an Administrator already exists 64 | // Get database conection string 65 | string strDefaultConnection = _configuration["ConnectionStrings:DefaultConnection"]; 66 | var AdminExists = await _BlogsService.AdminExistsAsync(strDefaultConnection); 67 | 68 | if (AdminExists) 69 | { 70 | ErrorMessage = "Administrator Already Exists"; 71 | return; 72 | } 73 | } 74 | 75 | #region private async void CreateAdministrator() 76 | private async void CreateAdministrator() 77 | { 78 | try 79 | { 80 | // ensure there is a ADMINISTRATION_ROLE 81 | var RoleResult = await _RoleManager.FindByNameAsync(ADMINISTRATION_ROLE); 82 | if (RoleResult == null) 83 | { 84 | // Create ADMINISTRATION_ROLE Role 85 | await _RoleManager.CreateAsync(new IdentityRole(ADMINISTRATION_ROLE)); 86 | } 87 | 88 | // Create the user 89 | ApplicationUser objApplicationUser = new ApplicationUser(); 90 | objApplicationUser.UserName = strAdministratorUserName; 91 | objApplicationUser.DisplayName = strAdministratorUserName; 92 | objApplicationUser.Email = strAdministratorUserName; 93 | objApplicationUser.EmailConfirmed = true; 94 | 95 | var UserResult = await _UserManager.CreateAsync(objApplicationUser, strAdministratorPassword); 96 | 97 | if (!UserResult.Succeeded) 98 | { 99 | ErrorMessage = ""; 100 | foreach (var item in UserResult.Errors) 101 | { 102 | ErrorMessage = ErrorMessage + " " + item.Description; 103 | } 104 | StateHasChanged(); 105 | return; 106 | } 107 | 108 | var user = await _UserManager.FindByNameAsync(strAdministratorUserName); 109 | 110 | if (user != null) 111 | { 112 | // Put admin in Administrator role 113 | await _UserManager.AddToRoleAsync(user, ADMINISTRATION_ROLE); 114 | } 115 | 116 | // Notify parent component 117 | await CreateAdministratorChanged.InvokeAsync(true); 118 | } 119 | catch (Exception ex) 120 | { 121 | ErrorMessage = ex.Message; 122 | StateHasChanged(); 123 | return; 124 | } 125 | } 126 | #endregion 127 | } 128 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Pages/InstallUpgrade/InstallWizardLayout.razor: -------------------------------------------------------------------------------- 1 | @inherits LayoutComponentBase 2 | 3 | 4 | 5 |
6 | 9 | 10 |
11 | @Body 12 |
13 |
14 | @code { 15 | 16 | } -------------------------------------------------------------------------------- /BlazorBlogs/Components/Pages/InstallUpgrade/InstallWizardLayout.razor.css: -------------------------------------------------------------------------------- 1 | .page { 2 | position: relative; 3 | display: flex; 4 | flex-direction: column; 5 | } 6 | 7 | main { 8 | flex: 1; 9 | } 10 | 11 | .sidebar { 12 | background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); 13 | } 14 | 15 | .top-row { 16 | background-color: #f7f7f7; 17 | border-bottom: 1px solid #d6d5d5; 18 | justify-content: flex-end; 19 | height: 3.5rem; 20 | display: flex; 21 | align-items: center; 22 | } 23 | 24 | .top-row ::deep a, .top-row ::deep .btn-link { 25 | white-space: nowrap; 26 | margin-left: 1.5rem; 27 | text-decoration: none; 28 | } 29 | 30 | .top-row ::deep a:hover, .top-row ::deep .btn-link:hover { 31 | text-decoration: underline; 32 | } 33 | 34 | .top-row ::deep a:first-child { 35 | overflow: hidden; 36 | text-overflow: ellipsis; 37 | } 38 | 39 | @media (max-width: 640.98px) { 40 | .top-row { 41 | justify-content: space-between; 42 | } 43 | 44 | .top-row ::deep a, .top-row ::deep .btn-link { 45 | margin-left: 0; 46 | } 47 | } 48 | 49 | @media (min-width: 641px) { 50 | .page { 51 | flex-direction: row; 52 | } 53 | 54 | .sidebar { 55 | width: 250px; 56 | height: 100vh; 57 | position: sticky; 58 | top: 0; 59 | } 60 | 61 | .top-row { 62 | position: sticky; 63 | top: 0; 64 | z-index: 1; 65 | } 66 | 67 | .top-row.auth ::deep a:first-child { 68 | flex: 1; 69 | text-align: right; 70 | width: 0; 71 | } 72 | 73 | .top-row, article { 74 | padding-left: 2rem !important; 75 | padding-right: 1.5rem !important; 76 | } 77 | } 78 | 79 | #blazor-error-ui { 80 | color-scheme: light only; 81 | background: lightyellow; 82 | bottom: 0; 83 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); 84 | box-sizing: border-box; 85 | display: none; 86 | left: 0; 87 | padding: 0.6rem 1.25rem 0.7rem 1.25rem; 88 | position: fixed; 89 | width: 100%; 90 | z-index: 1000; 91 | } 92 | 93 | #blazor-error-ui .dismiss { 94 | cursor: pointer; 95 | position: absolute; 96 | right: 0.75rem; 97 | top: 0.5rem; 98 | } -------------------------------------------------------------------------------- /BlazorBlogs/Components/Pages/InstallUpgrade/InstallWizardLoginAdministrator.razor: -------------------------------------------------------------------------------- 1 | @using System.ComponentModel.DataAnnotations 2 | @using BlazorBlogs.Components.Account 3 | @using BlazorBlogs.Components.Account.Shared 4 | @using Microsoft.AspNetCore.Authentication 5 | @using Microsoft.AspNetCore.Identity 6 | @using BlazorBlogs.Data 7 | 8 | @inject SignInManager SignInManager 9 | @inject NavigationManager NavigationManager 10 | @inject IdentityRedirectManager RedirectManager 11 | 12 | Verify Administrator 13 | 14 |

Verify Administrator

15 |
16 |
17 |
18 | 19 | 20 | 21 |

Use a local account to log in.

22 |
23 | 24 |
25 | 26 | 27 | 28 |
29 |
30 | 31 | 32 | 33 |
34 |
35 | 36 |
37 |
38 |
39 |
40 |
41 | @code { 42 | private string errorMessage; 43 | 44 | [CascadingParameter] 45 | private HttpContext HttpContext { get; set; } = default!; 46 | 47 | [SupplyParameterFromForm] 48 | private InputModel Input { get; set; } = new(); 49 | 50 | [SupplyParameterFromQuery] 51 | private string ReturnUrl { get; set; } 52 | 53 | protected override async Task OnInitializedAsync() 54 | { 55 | if (HttpMethods.IsGet(HttpContext.Request.Method)) 56 | { 57 | // Clear the existing external cookie to ensure a clean login process 58 | await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme); 59 | } 60 | } 61 | 62 | public async Task LoginUser() 63 | { 64 | // This doesn't count login failures towards account lockout 65 | // To enable password failures to trigger account lockout, set lockoutOnFailure: true 66 | var result = await SignInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false); 67 | if (result.Succeeded) 68 | { 69 | RedirectManager.RedirectTo(ReturnUrl); 70 | } 71 | else if (result.RequiresTwoFactor) 72 | { 73 | RedirectManager.RedirectTo( 74 | "Account/LoginWith2fa", 75 | new() { ["returnUrl"] = ReturnUrl, ["rememberMe"] = Input.RememberMe }); 76 | } 77 | else if (result.IsLockedOut) 78 | { 79 | RedirectManager.RedirectTo("Account/Lockout"); 80 | } 81 | else 82 | { 83 | errorMessage = "Error: Invalid login attempt."; 84 | } 85 | } 86 | 87 | private sealed class InputModel 88 | { 89 | [Required] 90 | public string Email { get; set; } = ""; 91 | 92 | [Required] 93 | [DataType(DataType.Password)] 94 | public string Password { get; set; } = ""; 95 | 96 | [Display(Name = "Remember me?")] 97 | public bool RememberMe { get; set; } 98 | } 99 | } -------------------------------------------------------------------------------- /BlazorBlogs/Components/Pages/InstallUpgrade/InstallWizardNavMenu.razor: -------------------------------------------------------------------------------- 1 |  6 | 7 |
8 | 9 |
10 | 11 | @code { 12 | bool collapseNavMenu = true; 13 | 14 | string NavMenuCssClass => collapseNavMenu ? "collapse" : null; 15 | 16 | void ToggleNavMenu() 17 | { 18 | collapseNavMenu = !collapseNavMenu; 19 | } 20 | } -------------------------------------------------------------------------------- /BlazorBlogs/Components/Pages/InstallUpgrade/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using BlazorBlogs.Components.Account.Shared 2 | @attribute [ExcludeFromInteractiveRouting] 3 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Pages/Newsletter/NewsletterUnsubscribe.razor: -------------------------------------------------------------------------------- 1 | @page "/NewsletterUnsubscribe/{UserId}" 2 | 3 | @using BlazorBlogs.Data; 4 | @using BlazorBlogs.Data.Models; 5 | @using Microsoft.AspNetCore.Identity; 6 | @inject UserManager _UserManager 7 | @inject RoleManager _RoleManager 8 | @inject AuthenticationStateProvider AuthenticationStateProvider 9 | @inject IConfiguration _configuration 10 | @inject DisqusState DisqusState 11 | @inherits OwningComponentBase 12 | 13 |
14 |
15 | @if(success) 16 | { 17 |

You have been unsubscribed from the newsletter.

18 | } 19 | 20 | @code { 21 | [Parameter] public string UserId { get; set; } 22 | bool success = false; 23 | 24 | protected override async Task OnInitializedAsync() 25 | { 26 | // Get the user 27 | var user = await _UserManager.FindByIdAsync(UserId); 28 | 29 | if (user != null) 30 | { 31 | // Set NewsletterSubscriber to false 32 | user.NewsletterSubscriber = false; 33 | 34 | // Update the user 35 | await _UserManager.UpdateAsync(user); 36 | success = true; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Pages/Newsletter/NewsletterView.razor: -------------------------------------------------------------------------------- 1 | @page "/NewsletterView/{NewsletterId}" 2 | 3 | @using Microsoft.AspNetCore.Http 4 | @using BlazorBlogs.Data; 5 | @using BlazorBlogs.Data.Models; 6 | @inject IJSRuntime JSRuntime 7 | @inject BlogsService _BlogsService 8 | @inject IHttpContextAccessor httpContextAccessor 9 | @inherits OwningComponentBase 10 | @if (SelectedBlog != null) 11 | { 12 | @((MarkupString)SelectedBlog.BlogContent) 13 | } 14 | else 15 | { 16 |

Newsletter not found.

17 | } 18 | @code { 19 | [Parameter] public string NewsletterId { get; set; } 20 | BlogDTO SelectedBlog; 21 | 22 | protected override async Task OnInitializedAsync() 23 | { 24 | int intNewsletterId = Convert.ToInt32(NewsletterId); 25 | SelectedBlog = await @Service.GetNewsletterAsync(intNewsletterId); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/Pages/ViewBlogPost.razor: -------------------------------------------------------------------------------- 1 | @page "/ViewBlogPost/{BlogPostId}" 2 | 3 | @using Microsoft.AspNetCore.Http 4 | @using BlazorBlogs.Data; 5 | @using BlazorBlogs.Data.Models; 6 | @using static Toolbelt.Blazor.HeadElement.MetaElement 7 | @inject IJSRuntime JSRuntime 8 | @inject AuthenticationStateProvider AuthenticationStateProvider 9 | @inject BlogsService _BlogsService 10 | @inject IHttpContextAccessor httpContextAccessor 11 | @inject NavigationManager NavigationManager 12 | @inject IHeadElementHelper HeadElementHelper 13 | @inject DisqusState DisqusState 14 | @inherits OwningComponentBase 15 | 16 | @SelectedBlog.BlogTitle 17 | @SelectedBlog.BlogTitle 18 | 19 | 20 |

21 |   22 | @if (UserIsAdminOfBlogPost) 23 | { 24 | 25 | } 26 |

27 |
28 | @SelectedBlog.BlogDate.ToShortDateString() 29 | @SelectedBlog.BlogDisplayName 30 |
31 |

@SelectedBlog.BlogTitle

32 |
33 |
34 |
35 |
36 | @((MarkupString)SelectedBlog.BlogContent) 37 |
38 |
39 | 40 | 41 | @code { 42 | [CascadingParameter] 43 | private Task authenticationStateTask { get; set; } 44 | 45 | [Parameter] public string BlogPostId { get; set; } 46 | 47 | BlogDTO SelectedBlog = new BlogDTO() { BlogId = 0, BlogDate = DateTime.Now, BlogTitle = "", BlogDisplayName = "" }; 48 | 49 | BlogAdministration BlogAdministrationControl; 50 | System.Security.Claims.ClaimsPrincipal CurrentUser; 51 | bool UserIsAdminOfBlogPost = false; 52 | string AbsoluteUrlOfThisPage => NavigationManager.ToAbsoluteUri($"ViewBlogPost/{BlogPostId}").AbsoluteUri; 53 | 54 | protected override async Task OnInitializedAsync() 55 | { 56 | try 57 | { 58 | SelectedBlog = @Service.GetBlog(Convert.ToInt32(BlogPostId)); 59 | 60 | // Get the current user 61 | CurrentUser = (await authenticationStateTask).User; 62 | 63 | if (CurrentUser.Identity.IsAuthenticated) 64 | { 65 | if (SelectedBlog.BlogId != 0) 66 | { 67 | if (CurrentUser.Identity.Name.ToLower() == SelectedBlog.BlogUserName.ToLower()) 68 | { 69 | UserIsAdminOfBlogPost = true; 70 | } 71 | } 72 | } 73 | } 74 | catch 75 | { 76 | SelectedBlog = new BlogDTO() { BlogDate = DateTime.Now, BlogTitle = "ERROR - Page Not Found" }; 77 | return; 78 | } 79 | } 80 | 81 | protected override async Task 82 | OnAfterRenderAsync(bool firstRender) 83 | { 84 | if (SelectedBlog.BlogId > 0) 85 | { 86 | try 87 | { 88 | string url = NavigationManager.ToAbsoluteUri($"ViewBlogPost/{BlogPostId}").AbsoluteUri; 89 | 90 | if (SelectedBlog.GoogleTrackingID.Trim() != "") 91 | { 92 | try 93 | { 94 | await GoogleInterop.gaTracking( 95 | JSRuntime, 96 | SelectedBlog.GoogleTrackingID.Trim() 97 | ); 98 | } 99 | catch { } 100 | } 101 | 102 | if (Convert.ToBoolean(SelectedBlog.DisqusEnabled)) 103 | { 104 | try 105 | { 106 | await DisqusInterop.ResetDisqus( 107 | JSRuntime, 108 | BlogPostId.ToString(), 109 | url, 110 | SelectedBlog.BlogTitle); 111 | 112 | DisqusState.SetDisplayDisqus(true); 113 | } 114 | catch { } 115 | } 116 | } 117 | catch 118 | { 119 | SelectedBlog = new BlogDTO() { BlogDate = DateTime.Now, BlogTitle = "ERROR - Page Not Found" }; 120 | return; 121 | } 122 | } 123 | } 124 | 125 | void EditBlog() 126 | { 127 | BlogAdministrationControl.EditBlog(SelectedBlog); 128 | } 129 | 130 | void Back() 131 | { 132 | string url = NavigationManager.ToAbsoluteUri($"ViewBlogPost/../").AbsoluteUri; 133 | NavigationManager.NavigateTo(url); 134 | } 135 | 136 | void BlogUpdatedEvent() 137 | { 138 | try 139 | { 140 | SelectedBlog = _BlogsService.GetBlog(Convert.ToInt32(BlogPostId)); 141 | } 142 | catch 143 | { 144 | // Blog was deleted 145 | Back(); 146 | } 147 | } 148 | } -------------------------------------------------------------------------------- /BlazorBlogs/Components/Routes.razor: -------------------------------------------------------------------------------- 1 | @using BlazorBlogs.Components.Account.Shared 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /BlazorBlogs/Components/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using System.Net.Http.Json 3 | @using Microsoft.AspNetCore.Components.Authorization 4 | @using Microsoft.AspNetCore.Components.Forms 5 | @using Microsoft.AspNetCore.Components.Routing 6 | @using Microsoft.AspNetCore.Components.Web 7 | @using static Microsoft.AspNetCore.Components.Web.RenderMode 8 | @using Microsoft.AspNetCore.Components.Web.Virtualization 9 | @using Microsoft.JSInterop 10 | @using BlazorBlogs 11 | @using BlazorBlogs.Components 12 | @using Radzen 13 | @using Radzen.Blazor 14 | @using Microsoft.Extensions.Configuration 15 | @using Blazored.TextEditor 16 | @using Blazored.Toast 17 | @using Blazored.Toast.Services 18 | @using Toolbelt.Blazor.HeadElement -------------------------------------------------------------------------------- /BlazorBlogs/Controllers/FileController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.AspNetCore.Mvc; 5 | using System; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace BlazorBlogs 12 | { 13 | [Route("api/[controller]")] 14 | [ApiController] 15 | public class FileController : Controller 16 | { 17 | private readonly IWebHostEnvironment environment; 18 | public FileController(IWebHostEnvironment environment) 19 | { 20 | this.environment = environment; 21 | } 22 | 23 | [HttpGet("[action]")] 24 | public IActionResult DownloadFile(string FileName) 25 | { 26 | string path = Path.Combine( 27 | environment.WebRootPath, 28 | "files", 29 | FileName); 30 | 31 | var stream = new FileStream(path, FileMode.Open); 32 | 33 | var result = new FileStreamResult(stream, "text/plain"); 34 | result.FileDownloadName = FileName; 35 | return result; 36 | } 37 | 38 | // This cannot be called by an API call 39 | public void DeleteFile(string FileName) 40 | { 41 | string path = Path.Combine( 42 | environment.WebRootPath, 43 | "files", 44 | FileName); 45 | 46 | if (System.IO.File.Exists(path)) 47 | { 48 | // If file found, delete it 49 | System.IO.File.Delete(path); 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /BlazorBlogs/Controllers/RSSFeed.cs: -------------------------------------------------------------------------------- 1 | using BlazorBlogs.Data; 2 | using Microsoft.AspNetCore.Http; 3 | using Microsoft.AspNetCore.Mvc; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.ServiceModel.Syndication; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | using System.Xml; 11 | using System.Linq; 12 | 13 | namespace BlazorBlogs.Controllers 14 | { 15 | [Route("api/[controller]")] 16 | [ApiController] 17 | public class RSSFeed : Controller 18 | { 19 | private readonly BlazorBlogsContext _BlazorBlogsContext; 20 | private readonly GeneralSettingsService _GeneralSettingsService; 21 | private IHttpContextAccessor _httpContextAccessor; 22 | 23 | public RSSFeed(BlazorBlogsContext blazorBlogsContext, 24 | GeneralSettingsService generalSettingsService, 25 | IHttpContextAccessor httpContextAccessor) 26 | { 27 | _BlazorBlogsContext = blazorBlogsContext; 28 | _GeneralSettingsService = generalSettingsService; 29 | _httpContextAccessor = httpContextAccessor; 30 | } 31 | 32 | // From: https://mitchelsellers.com/blog/article/creating-an-rss-feed-in-asp-net-core-3-0 33 | [ResponseCache(Duration = 1200)] 34 | [HttpGet("[action]")] 35 | public async Task RssAsync() 36 | { 37 | 38 | var objGeneralSettings = await _GeneralSettingsService.GetGeneralSettingsAsync(); 39 | var feed = new SyndicationFeed(objGeneralSettings.ApplicationName, objGeneralSettings.ApplicationName, new Uri(GetBaseUrl()), "RSSUrl", DateTime.Now); 40 | 41 | feed.Copyright = new TextSyndicationContent($"{DateTime.Now.Year} {objGeneralSettings.ApplicationName}"); 42 | var items = new List(); 43 | 44 | var postings = _BlazorBlogsContext.Blogs.OrderByDescending(x => x.BlogDate); 45 | 46 | foreach (var item in postings) 47 | { 48 | string BlogURL = $"{GetBaseUrl()}/ViewBlogPost/{item.BlogId}"; 49 | var postUrl = Url.Action("Article", "Blog", new { id = BlogURL }, HttpContext.Request.Scheme); 50 | var title = item.BlogTitle; 51 | var description = SyndicationContent.CreateHtmlContent(StripTags(item.BlogSummary.Replace(" ", " "), true)); 52 | 53 | var BlogItem = new SyndicationItem(); 54 | BlogItem.Title = new TextSyndicationContent(StripTags(title, true)); 55 | BlogItem.Content = description; 56 | BlogItem.Id = BlogURL; 57 | BlogItem.PublishDate = item.BlogDate; 58 | BlogItem.LastUpdatedTime = item.BlogDate; 59 | BlogItem.Links.Add(new SyndicationLink(new Uri(BlogURL))); 60 | items.Add(BlogItem); 61 | } 62 | 63 | feed.Items = items; 64 | 65 | var settings = new XmlWriterSettings 66 | { 67 | Encoding = Encoding.UTF8, 68 | NewLineHandling = NewLineHandling.Entitize, 69 | NewLineOnAttributes = true, 70 | Indent = true 71 | }; 72 | 73 | using (var stream = new MemoryStream()) 74 | { 75 | using (var xmlWriter = XmlWriter.Create(stream, settings)) 76 | { 77 | var rssFormatter = new Rss20FeedFormatter(feed, false); 78 | rssFormatter.WriteTo(xmlWriter); 79 | xmlWriter.Flush(); 80 | } 81 | 82 | return File(stream.ToArray(), "application/rss+xml; charset=utf-8"); 83 | } 84 | } 85 | 86 | // Utility 87 | 88 | public string GetBaseUrl() 89 | { 90 | var request = _httpContextAccessor.HttpContext.Request; 91 | 92 | var host = request.Host.ToUriComponent(); 93 | 94 | var pathBase = request.PathBase.ToUriComponent(); 95 | 96 | return $"{request.Scheme}://{host}{pathBase}"; 97 | } 98 | 99 | #region StripTags 100 | public static string StripTags(string HTML, bool RetainSpace) 101 | { 102 | //Set up Replacement String 103 | string RepString; 104 | if (RetainSpace) 105 | { 106 | RepString = " "; 107 | } 108 | else 109 | { 110 | RepString = ""; 111 | } 112 | 113 | //Replace Tags by replacement String and return mofified string 114 | return System.Text.RegularExpressions.Regex.Replace(HTML, "<[^>]*>", RepString); 115 | } 116 | #endregion 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /BlazorBlogs/Data/EmailSender.cs: -------------------------------------------------------------------------------- 1 | using BlazorBlogs.Models; 2 | using Microsoft.AspNetCore.Identity; 3 | using Microsoft.AspNetCore.Identity.UI.Services; 4 | using Microsoft.Extensions.Options; 5 | using System; 6 | using System.Threading.Tasks; 7 | 8 | namespace BlazorBlogs.Data 9 | { 10 | public class EmailSender : IEmailSender 11 | { 12 | private readonly BlazorBlogsContext _context; 13 | private readonly EmailService _EmailService; 14 | private readonly GeneralSettingsService _GeneralSettingsService; 15 | 16 | public EmailSender(BlazorBlogsContext context, EmailService EmailService, GeneralSettingsService generalSettingsService) 17 | { 18 | _context = context; 19 | _EmailService = EmailService; 20 | _GeneralSettingsService = generalSettingsService; 21 | } 22 | 23 | public Task SendEmailAsync(string email, string subject, string message) 24 | { 25 | return EmailSendAsync(email, subject, message); 26 | } 27 | 28 | public async Task EmailSendAsync(string email, string subject, string message) 29 | { 30 | var objGeneralSettings = await _GeneralSettingsService.GetGeneralSettingsAsync(); 31 | 32 | string strError = await _EmailService.SendMailAsync( 33 | false, 34 | email, 35 | email, 36 | "", "", 37 | objGeneralSettings.SMTPFromEmail, 38 | $"Account Confirmation From: {objGeneralSettings.ApplicationName} {subject}", 39 | $"This is an account confirmation email from: {objGeneralSettings.ApplicationName}. {message}"); 40 | 41 | if (strError != "") 42 | { 43 | BlazorBlogs.Data.Models.Logs objLog = new Data.Models.Logs(); 44 | objLog.LogDate = DateTime.Now; 45 | objLog.LogUserName = email; 46 | objLog.LogIpaddress = "127.0.0.1"; 47 | objLog.LogAction = $"{Constants.EmailError} - Error: {strError} - To: {email} Subject: Account Confirmation From: {objGeneralSettings.ApplicationName} {subject}"; 48 | _context.Logs.Add(objLog); 49 | _context.SaveChanges(); 50 | } 51 | } 52 | 53 | Task IEmailSender.SendConfirmationLinkAsync(ApplicationUser user, string email, string confirmationLink) 54 | { 55 | return EmailSendAsync(email, "Account Confirmation", $"Please confirm your account by clicking this link: link"); 56 | } 57 | 58 | Task IEmailSender.SendPasswordResetLinkAsync(ApplicationUser user, string email, string resetLink) 59 | { 60 | return EmailSendAsync(email, "Password Reset", $"Please reset your password by clicking this link: link"); 61 | } 62 | 63 | Task IEmailSender.SendPasswordResetCodeAsync(ApplicationUser user, string email, string resetCode) 64 | { 65 | return EmailSendAsync(email, "Password Reset Code", $"Please reset your password by using this code: {resetCode}"); 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /BlazorBlogs/Data/Models/ApplicationUser.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace BlazorBlogs.Data 8 | { 9 | public class ApplicationUser : IdentityUser 10 | { 11 | public string DisplayName { get; set; } 12 | public bool NewsletterSubscriber { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /BlazorBlogs/Data/Models/ApplicationUserPaged.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace BlazorBlogs.Data.Models 4 | { 5 | public class ApplicationUserPaged 6 | { 7 | public List ApplicationUsers { get; set; } 8 | public int ApplicationUserCount { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /BlazorBlogs/Data/Models/AspNetRoles.cs: -------------------------------------------------------------------------------- 1 | // This file has been auto generated by EF Core Power Tools. 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace BlazorBlogs.Data.Models 6 | { 7 | public partial class AspNetRoles 8 | { 9 | public AspNetRoles() 10 | { 11 | AspNetUserRoles = new HashSet(); 12 | } 13 | 14 | public string Id { get; set; } 15 | public string Name { get; set; } 16 | public string NormalizedName { get; set; } 17 | public string ConcurrencyStamp { get; set; } 18 | 19 | public virtual ICollection AspNetUserRoles { get; set; } 20 | } 21 | } -------------------------------------------------------------------------------- /BlazorBlogs/Data/Models/AspNetUserRoles.cs: -------------------------------------------------------------------------------- 1 | // This file has been auto generated by EF Core Power Tools. 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace BlazorBlogs.Data.Models 6 | { 7 | public partial class AspNetUserRoles 8 | { 9 | public string UserId { get; set; } 10 | public string RoleId { get; set; } 11 | 12 | public virtual AspNetRoles Role { get; set; } 13 | public virtual AspNetUsers User { get; set; } 14 | } 15 | } -------------------------------------------------------------------------------- /BlazorBlogs/Data/Models/AspNetUsers.cs: -------------------------------------------------------------------------------- 1 | // This file has been auto generated by EF Core Power Tools. 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace BlazorBlogs.Data.Models 6 | { 7 | public partial class AspNetUsers 8 | { 9 | public AspNetUsers() 10 | { 11 | AspNetUserRoles = new HashSet(); 12 | } 13 | 14 | public string Id { get; set; } 15 | public string UserName { get; set; } 16 | public string NormalizedUserName { get; set; } 17 | public string Email { get; set; } 18 | public string NormalizedEmail { get; set; } 19 | public bool EmailConfirmed { get; set; } 20 | public string PasswordHash { get; set; } 21 | public string SecurityStamp { get; set; } 22 | public string ConcurrencyStamp { get; set; } 23 | public string PhoneNumber { get; set; } 24 | public bool PhoneNumberConfirmed { get; set; } 25 | public bool TwoFactorEnabled { get; set; } 26 | public DateTimeOffset? LockoutEnd { get; set; } 27 | public bool LockoutEnabled { get; set; } 28 | public int AccessFailedCount { get; set; } 29 | public string DisplayName { get; set; } 30 | public bool? NewsletterSubscriber { get; set; } 31 | 32 | public virtual ICollection AspNetUserRoles { get; set; } 33 | } 34 | } -------------------------------------------------------------------------------- /BlazorBlogs/Data/Models/BlogBlogs.cs: -------------------------------------------------------------------------------- 1 | // This file has been auto generated by EF Core Power Tools. 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace DotNetNukeBlogs.Data.Models 6 | { 7 | public partial class BlogBlogs 8 | { 9 | public int PortalId { get; set; } 10 | public int BlogId { get; set; } 11 | public int UserId { get; set; } 12 | public string Title { get; set; } 13 | public string Description { get; set; } 14 | public bool Public { get; set; } 15 | public bool AllowComments { get; set; } 16 | public bool AllowAnonymous { get; set; } 17 | public DateTime? LastEntry { get; set; } 18 | public DateTime Created { get; set; } 19 | public bool ShowFullName { get; set; } 20 | public string DateFormat { get; set; } 21 | public string Culture { get; set; } 22 | public int TimeZone { get; set; } 23 | public int? ParentBlogId { get; set; } 24 | public bool Syndicated { get; set; } 25 | public bool SyndicateIndependant { get; set; } 26 | public string SyndicationUrl { get; set; } 27 | public string SyndicationEmail { get; set; } 28 | public bool? EmailNotification { get; set; } 29 | public bool? AllowTrackbacks { get; set; } 30 | public bool? AutoTrackback { get; set; } 31 | public bool? MustApproveComments { get; set; } 32 | public bool? MustApproveAnonymous { get; set; } 33 | public bool? MustApproveTrackbacks { get; set; } 34 | public bool? UseCaptcha { get; set; } 35 | } 36 | } -------------------------------------------------------------------------------- /BlazorBlogs/Data/Models/BlogCategories.cs: -------------------------------------------------------------------------------- 1 | // This file has been auto generated by EF Core Power Tools. 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace DotNetNukeBlogs.Data.Models 6 | { 7 | public partial class BlogCategories 8 | { 9 | public BlogCategories() 10 | { 11 | BlogEntryCategories = new HashSet(); 12 | } 13 | 14 | public int CatId { get; set; } 15 | public string Category { get; set; } 16 | public string Slug { get; set; } 17 | public int? ParentId { get; set; } 18 | public int PortalId { get; set; } 19 | 20 | public virtual ICollection BlogEntryCategories { get; set; } 21 | } 22 | } -------------------------------------------------------------------------------- /BlazorBlogs/Data/Models/BlogCategory.cs: -------------------------------------------------------------------------------- 1 | // This file has been auto generated by EF Core Power Tools. 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace BlazorBlogs.Data.Models 6 | { 7 | public partial class BlogCategory 8 | { 9 | public int Id { get; set; } 10 | public int BlogId { get; set; } 11 | public int CategoryId { get; set; } 12 | 13 | public virtual Blogs Blog { get; set; } 14 | public virtual Categorys Category { get; set; } 15 | } 16 | } -------------------------------------------------------------------------------- /BlazorBlogs/Data/Models/BlogComments.cs: -------------------------------------------------------------------------------- 1 | // This file has been auto generated by EF Core Power Tools. 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace DotNetNukeBlogs.Data.Models 6 | { 7 | public partial class BlogComments 8 | { 9 | public int CommentId { get; set; } 10 | public int EntryId { get; set; } 11 | public int? UserId { get; set; } 12 | public string Comment { get; set; } 13 | public DateTime AddedDate { get; set; } 14 | public string Title { get; set; } 15 | public bool? Approved { get; set; } 16 | public string Author { get; set; } 17 | public string Website { get; set; } 18 | public string Email { get; set; } 19 | 20 | public virtual BlogEntries Entry { get; set; } 21 | } 22 | } -------------------------------------------------------------------------------- /BlazorBlogs/Data/Models/BlogDTO.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace BlazorBlogs.Data.Models 7 | { 8 | public class BlogDTO 9 | { 10 | public int BlogId { get; set; } 11 | public DateTime BlogDate { get; set; } 12 | public string BlogTitle { get; set; } 13 | public string BlogSummary { get; set; } 14 | public string BlogContent { get; set; } 15 | public string BlogUserName { get; set; } 16 | public string BlogDisplayName { get; set; } 17 | public string GoogleTrackingID { get; set; } 18 | public bool DisqusEnabled { get; set; } 19 | public List BlogCategory { get; set; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /BlazorBlogs/Data/Models/BlogEntries.cs: -------------------------------------------------------------------------------- 1 | // This file has been auto generated by EF Core Power Tools. 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace DotNetNukeBlogs.Data.Models 6 | { 7 | public partial class BlogEntries 8 | { 9 | public BlogEntries() 10 | { 11 | BlogComments = new HashSet(); 12 | BlogEntryCategories = new HashSet(); 13 | BlogEntryTags = new HashSet(); 14 | } 15 | 16 | public int BlogId { get; set; } 17 | public int EntryId { get; set; } 18 | public string Title { get; set; } 19 | public string Entry { get; set; } 20 | public DateTime AddedDate { get; set; } 21 | public bool Published { get; set; } 22 | public string Description { get; set; } 23 | public bool? AllowComments { get; set; } 24 | public bool DisplayCopyright { get; set; } 25 | public string Copyright { get; set; } 26 | public string PermaLink { get; set; } 27 | 28 | public virtual ICollection BlogComments { get; set; } 29 | public virtual ICollection BlogEntryCategories { get; set; } 30 | public virtual ICollection BlogEntryTags { get; set; } 31 | } 32 | } -------------------------------------------------------------------------------- /BlazorBlogs/Data/Models/BlogEntryCategories.cs: -------------------------------------------------------------------------------- 1 | // This file has been auto generated by EF Core Power Tools. 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace DotNetNukeBlogs.Data.Models 6 | { 7 | public partial class BlogEntryCategories 8 | { 9 | public int EntryCatId { get; set; } 10 | public int? EntryId { get; set; } 11 | public int? CatId { get; set; } 12 | 13 | public virtual BlogCategories Cat { get; set; } 14 | public virtual BlogEntries Entry { get; set; } 15 | } 16 | } -------------------------------------------------------------------------------- /BlazorBlogs/Data/Models/BlogEntryTags.cs: -------------------------------------------------------------------------------- 1 | // This file has been auto generated by EF Core Power Tools. 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace DotNetNukeBlogs.Data.Models 6 | { 7 | public partial class BlogEntryTags 8 | { 9 | public int EntryTagId { get; set; } 10 | public int EntryId { get; set; } 11 | public int TagId { get; set; } 12 | 13 | public virtual BlogEntries Entry { get; set; } 14 | public virtual BlogTags Tag { get; set; } 15 | } 16 | } -------------------------------------------------------------------------------- /BlazorBlogs/Data/Models/BlogSettings.cs: -------------------------------------------------------------------------------- 1 | // This file has been auto generated by EF Core Power Tools. 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace DotNetNukeBlogs.Data.Models 6 | { 7 | public partial class BlogSettings 8 | { 9 | public int PortalId { get; set; } 10 | public string Key { get; set; } 11 | public string Value { get; set; } 12 | public int TabId { get; set; } 13 | } 14 | } -------------------------------------------------------------------------------- /BlazorBlogs/Data/Models/BlogTags.cs: -------------------------------------------------------------------------------- 1 | // This file has been auto generated by EF Core Power Tools. 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace DotNetNukeBlogs.Data.Models 6 | { 7 | public partial class BlogTags 8 | { 9 | public BlogTags() 10 | { 11 | BlogEntryTags = new HashSet(); 12 | } 13 | 14 | public int TagId { get; set; } 15 | public string Tag { get; set; } 16 | public string Slug { get; set; } 17 | public bool Active { get; set; } 18 | public int PortalId { get; set; } 19 | 20 | public virtual ICollection BlogEntryTags { get; set; } 21 | } 22 | } -------------------------------------------------------------------------------- /BlazorBlogs/Data/Models/Blogs.cs: -------------------------------------------------------------------------------- 1 | // This file has been auto generated by EF Core Power Tools. 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace BlazorBlogs.Data.Models 6 | { 7 | public partial class Blogs 8 | { 9 | public Blogs() 10 | { 11 | BlogCategory = new HashSet(); 12 | Comment = new HashSet(); 13 | } 14 | 15 | public int BlogId { get; set; } 16 | public DateTime BlogDate { get; set; } 17 | public string BlogTitle { get; set; } 18 | public string BlogSummary { get; set; } 19 | public string BlogContent { get; set; } 20 | public string BlogUserName { get; set; } 21 | 22 | public virtual ICollection BlogCategory { get; set; } 23 | public virtual ICollection Comment { get; set; } 24 | } 25 | } -------------------------------------------------------------------------------- /BlazorBlogs/Data/Models/BlogsPaged.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace BlazorBlogs.Data.Models 5 | { 6 | public partial class BlogsPaged 7 | { 8 | public List Blogs { get; set; } 9 | public int BlogCount { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /BlazorBlogs/Data/Models/CategoryDTO.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace BlazorBlogs.Data.Models 7 | { 8 | public class CategoryDTO 9 | { 10 | public string CategoryId { get; set; } 11 | public string Title { get; set; } 12 | public string Description { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /BlazorBlogs/Data/Models/Categorys.cs: -------------------------------------------------------------------------------- 1 | // This file has been auto generated by EF Core Power Tools. 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace BlazorBlogs.Data.Models 6 | { 7 | public partial class Categorys 8 | { 9 | public Categorys() 10 | { 11 | BlogCategory = new HashSet(); 12 | } 13 | 14 | public int CategoryId { get; set; } 15 | public string Title { get; set; } 16 | public string Description { get; set; } 17 | 18 | public virtual ICollection BlogCategory { get; set; } 19 | } 20 | } -------------------------------------------------------------------------------- /BlazorBlogs/Data/Models/CategorysPaged.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace BlazorBlogs.Data.Models 5 | { 6 | public partial class CategorysPaged 7 | { 8 | public List Categorys { get; set; } 9 | public int CategoryCount { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /BlazorBlogs/Data/Models/Comment.cs: -------------------------------------------------------------------------------- 1 | // This file has been auto generated by EF Core Power Tools. 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace BlazorBlogs.Data.Models 6 | { 7 | public partial class Comment 8 | { 9 | public Comment() 10 | { 11 | InverseParentComment = new HashSet(); 12 | } 13 | 14 | public int CommentId { get; set; } 15 | public int BlogId { get; set; } 16 | public int? ParentCommentId { get; set; } 17 | public string CommentUserId { get; set; } 18 | public string Comment1 { get; set; } 19 | public DateTime CommentCreated { get; set; } 20 | public DateTime? CommentUpdated { get; set; } 21 | public string CommentIpaddress { get; set; } 22 | 23 | public virtual Blogs Blog { get; set; } 24 | public virtual Comment ParentComment { get; set; } 25 | public virtual ICollection InverseParentComment { get; set; } 26 | } 27 | } -------------------------------------------------------------------------------- /BlazorBlogs/Data/Models/Constants.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace BlazorBlogs.Models 5 | { 6 | public static class Constants 7 | { 8 | // General 9 | public const string EmailError = "Email-Error"; 10 | public const string EmailSent = "Email-Sent"; 11 | 12 | // Content Types 13 | public const string TXT = ".TXT"; 14 | public const string EML = ".EML"; 15 | public const string HTML = ".HTML"; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /BlazorBlogs/Data/Models/ExternalConnections.cs: -------------------------------------------------------------------------------- 1 | // This file has been auto generated by EF Core Power Tools. 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace BlazorBlogs.Data.Models 6 | { 7 | public partial class ExternalConnections 8 | { 9 | public int Id { get; set; } 10 | public string ServerName { get; set; } 11 | public string DatabaseName { get; set; } 12 | public string IntegratedSecurity { get; set; } 13 | public string DatabaseUsername { get; set; } 14 | public string DatabasePassword { get; set; } 15 | } 16 | } -------------------------------------------------------------------------------- /BlazorBlogs/Data/Models/Files.cs: -------------------------------------------------------------------------------- 1 | // This file has been auto generated by EF Core Power Tools. 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace BlazorBlogs.Data.Models 6 | { 7 | public partial class Files 8 | { 9 | public int FileId { get; set; } 10 | public string FileName { get; set; } 11 | public string FilePath { get; set; } 12 | public DateTime CreateDate { get; set; } 13 | public int DownloadCount { get; set; } 14 | } 15 | } -------------------------------------------------------------------------------- /BlazorBlogs/Data/Models/FilesDTO.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace BlazorBlogs.Data.Models 5 | { 6 | public partial class FilesPaged 7 | { 8 | public List Files { get; set; } 9 | public int FilesCount { get; set; } 10 | } 11 | 12 | public partial class FilesDTO 13 | { 14 | public string FileId { get; set; } 15 | public string FileName { get; set; } 16 | public string FilePath { get; set; } 17 | public DateTime CreateDate { get; set; } 18 | public int DownloadCount { get; set; } 19 | } 20 | } -------------------------------------------------------------------------------- /BlazorBlogs/Data/Models/GeneralSettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace BlazorBlogs.Data.Models 7 | { 8 | public class GeneralSettings 9 | { 10 | public string SMTPServer { get; set; } 11 | public string SMTPAuthendication { get; set; } 12 | public bool SMTPSecure { get; set; } 13 | public string SMTPUserName { get; set; } 14 | public string SMTPPassword { get; set; } 15 | public string SMTPFromEmail { get; set; } 16 | public bool AllowRegistration { get; set; } 17 | public bool VerifiedRegistration { get; set; } 18 | public string ApplicationName { get; set; } 19 | public string ApplicationLogo { get; set; } 20 | public string ApplicationHeader { get; set; } 21 | public string DisqusEnabled { get; set; } 22 | public string DisqusShortName { get; set; } 23 | public string VersionNumber { get; set; } 24 | public string GoogleTrackingID { get; set; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /BlazorBlogs/Data/Models/Logs.cs: -------------------------------------------------------------------------------- 1 | // This file has been auto generated by EF Core Power Tools. 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace BlazorBlogs.Data.Models 6 | { 7 | public partial class Logs 8 | { 9 | public int LogId { get; set; } 10 | public DateTime LogDate { get; set; } 11 | public string LogAction { get; set; } 12 | public string LogUserName { get; set; } 13 | public string LogIpaddress { get; set; } 14 | } 15 | } -------------------------------------------------------------------------------- /BlazorBlogs/Data/Models/LogsPaged.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace BlazorBlogs.Data.Models 5 | { 6 | public partial class LogsPaged 7 | { 8 | public List Logs { get; set; } 9 | public int LogCount { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /BlazorBlogs/Data/Models/Newsletters.cs: -------------------------------------------------------------------------------- 1 | // This file has been auto generated by EF Core Power Tools. 2 | #nullable disable 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | namespace BlazorBlogs.Data.Models; 7 | 8 | public partial class Newsletters 9 | { 10 | public int Id { get; set; } 11 | 12 | public DateTime NewsletterDate { get; set; } 13 | 14 | public string NewsletterTitle { get; set; } 15 | 16 | public string NewsletterContent { get; set; } 17 | 18 | public DateTime Created { get; set; } 19 | 20 | public DateTime? Updated { get; set; } 21 | 22 | public virtual ICollection NewslettersCampain { get; set; } = new List(); 23 | } -------------------------------------------------------------------------------- /BlazorBlogs/Data/Models/NewslettersCampain.cs: -------------------------------------------------------------------------------- 1 | // This file has been auto generated by EF Core Power Tools. 2 | #nullable disable 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | namespace BlazorBlogs.Data.Models; 7 | 8 | public partial class NewslettersCampain 9 | { 10 | public int Id { get; set; } 11 | 12 | public string NewsletterCampainName { get; set; } 13 | 14 | public int NewsletterId { get; set; } 15 | 16 | public DateTime Created { get; set; } 17 | 18 | public DateTime? Updated { get; set; } 19 | 20 | public virtual Newsletters Newsletter { get; set; } 21 | 22 | public virtual ICollection NewslettersLogs { get; set; } = new List(); 23 | } -------------------------------------------------------------------------------- /BlazorBlogs/Data/Models/NewslettersLogs.cs: -------------------------------------------------------------------------------- 1 | // This file has been auto generated by EF Core Power Tools. 2 | #nullable disable 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | namespace BlazorBlogs.Data.Models; 7 | 8 | public partial class NewslettersLogs 9 | { 10 | public int Id { get; set; } 11 | 12 | public int NewsletterCampainId { get; set; } 13 | 14 | public string LogType { get; set; } 15 | 16 | public string UserName { get; set; } 17 | 18 | public string LogDetails { get; set; } 19 | 20 | public virtual NewslettersCampain NewsletterCampain { get; set; } 21 | } -------------------------------------------------------------------------------- /BlazorBlogs/Data/Models/Settings.cs: -------------------------------------------------------------------------------- 1 | // This file has been auto generated by EF Core Power Tools. 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace BlazorBlogs.Data.Models 6 | { 7 | public partial class Settings 8 | { 9 | public int SettingId { get; set; } 10 | public string SettingName { get; set; } 11 | public string SettingValue { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /BlazorBlogs/Program.cs: -------------------------------------------------------------------------------- 1 | using BlazorBlogs.Data; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.AspNetCore.Components.Authorization; 4 | using Microsoft.AspNetCore.Identity; 5 | using Microsoft.EntityFrameworkCore; 6 | using System; 7 | using System.Runtime.Loader; 8 | using Toolbelt.Blazor.Extensions.DependencyInjection; 9 | using WilderMinds.MetaWeblog; 10 | 11 | using BlazorBlogs.Components; 12 | using BlazorBlogs.Components.Account; 13 | using Blazored.Toast; 14 | using Microsoft.AspNetCore.Identity.UI.Services; 15 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 16 | using Radzen; 17 | 18 | 19 | namespace BlazorBlogs 20 | { 21 | public class Program 22 | { 23 | public IConfiguration Configuration { get; } 24 | public static void Main(string[] args) 25 | { 26 | var builder = WebApplication.CreateBuilder(args); 27 | 28 | // Add services to the container. 29 | builder.Services.AddRazorComponents() 30 | .AddInteractiveServerComponents(); 31 | 32 | // Read the connection string from the appsettings.json file 33 | builder.Configuration.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) 34 | .AddEnvironmentVariables(); 35 | 36 | // Allows appsettings.json to be updated programatically 37 | builder.Services.ConfigureWritable(builder.Configuration.GetSection("ConnectionStrings")); 38 | builder.Services.AddSingleton(builder.Configuration); 39 | 40 | builder.Services.AddCascadingAuthenticationState(); 41 | builder.Services.AddScoped(); 42 | builder.Services.AddScoped(); 43 | builder.Services.AddScoped(); 44 | 45 | builder.Services.AddAuthentication(options => 46 | { 47 | options.DefaultScheme = IdentityConstants.ApplicationScheme; 48 | options.DefaultSignInScheme = IdentityConstants.ExternalScheme; 49 | }) 50 | .AddIdentityCookies(); 51 | 52 | var connectionString = builder.Configuration.GetConnectionString("DefaultConnection"); 53 | 54 | builder.Services.AddDbContext(options => 55 | options.UseSqlServer(connectionString)); 56 | 57 | builder.Services.AddDbContext(options => 58 | options.UseSqlServer(connectionString)); 59 | 60 | builder.Services.AddDatabaseDeveloperPageExceptionFilter(); 61 | 62 | builder.Services.AddIdentityCore(options => 63 | { 64 | options.SignIn.RequireConfirmedAccount = true; 65 | }) 66 | .AddRoles() 67 | .AddEntityFrameworkStores() 68 | .AddSignInManager() 69 | .AddRoleManager>() 70 | .AddRoleStore>() 71 | .AddDefaultTokenProviders(); 72 | 73 | builder.Services.AddScoped(); 74 | builder.Services.AddScoped(); 75 | builder.Services.AddScoped(); 76 | builder.Services.AddScoped, EmailSender>(); 77 | builder.Services.AddScoped(); 78 | builder.Services.AddScoped(); 79 | builder.Services.AddScoped(); 80 | builder.Services.AddMetaWeblog(); 81 | builder.Services.AddHttpContextAccessor(); 82 | builder.Services.AddScoped(); 83 | builder.Services.AddScoped(); 84 | builder.Services.AddBlazoredToast(); 85 | builder.Services.AddHeadElementHelper(); 86 | 87 | builder.Services.AddRadzenComponents(); 88 | 89 | builder.Services.AddControllersWithViews(); 90 | 91 | var app = builder.Build(); 92 | 93 | app.UseHeadElementServerPrerendering(); 94 | app.UseMetaWeblog("/MetaWeblog"); 95 | 96 | // Configure the HTTP request pipeline. 97 | if (app.Environment.IsDevelopment()) 98 | { 99 | app.UseMigrationsEndPoint(); 100 | } 101 | else 102 | { 103 | app.UseExceptionHandler("/Error"); 104 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. 105 | app.UseHsts(); 106 | } 107 | 108 | app.UseHttpsRedirection(); 109 | 110 | app.UseAntiforgery(); 111 | 112 | app.UseStaticFiles(); 113 | 114 | app.MapControllers(); 115 | 116 | app.MapRazorComponents() 117 | .AddInteractiveServerRenderMode(); 118 | 119 | // Add additional endpoints required by the Identity /Account Razor components. 120 | app.MapAdditionalIdentityEndpoints(); 121 | 122 | app.Run(); 123 | } 124 | } 125 | } -------------------------------------------------------------------------------- /BlazorBlogs/Properties/ServiceDependencies/blazor-blogs-test - Web Deploy/profile.arm.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "metadata": { 5 | "_dependencyType": "compute.appService.windows" 6 | }, 7 | "parameters": { 8 | "resourceGroupName": { 9 | "type": "string", 10 | "defaultValue": "Default", 11 | "metadata": { 12 | "description": "Name of the resource group for the resource. It is recommended to put resources under same resource group for better tracking." 13 | } 14 | }, 15 | "resourceGroupLocation": { 16 | "type": "string", 17 | "defaultValue": "westus", 18 | "metadata": { 19 | "description": "Location of the resource group. Resource groups could have different location than resources, however by default we use API versions from latest hybrid profile which support all locations for resource types we support." 20 | } 21 | }, 22 | "resourceName": { 23 | "type": "string", 24 | "defaultValue": "blazor-blogs-test", 25 | "metadata": { 26 | "description": "Name of the main resource to be created by this template." 27 | } 28 | }, 29 | "resourceLocation": { 30 | "type": "string", 31 | "defaultValue": "[parameters('resourceGroupLocation')]", 32 | "metadata": { 33 | "description": "Location of the resource. By default use resource group's location, unless the resource provider is not supported there." 34 | } 35 | } 36 | }, 37 | "variables": { 38 | "appServicePlan_name": "[concat('Plan', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId)))]", 39 | "appServicePlan_ResourceId": "[concat('/subscriptions/', subscription().subscriptionId, '/resourceGroups/', parameters('resourceGroupName'), '/providers/Microsoft.Web/serverFarms/', variables('appServicePlan_name'))]" 40 | }, 41 | "resources": [ 42 | { 43 | "type": "Microsoft.Resources/resourceGroups", 44 | "name": "[parameters('resourceGroupName')]", 45 | "location": "[parameters('resourceGroupLocation')]", 46 | "apiVersion": "2019-10-01" 47 | }, 48 | { 49 | "type": "Microsoft.Resources/deployments", 50 | "name": "[concat(parameters('resourceGroupName'), 'Deployment', uniqueString(concat(parameters('resourceName'), subscription().subscriptionId)))]", 51 | "resourceGroup": "[parameters('resourceGroupName')]", 52 | "apiVersion": "2019-10-01", 53 | "dependsOn": [ 54 | "[parameters('resourceGroupName')]" 55 | ], 56 | "properties": { 57 | "mode": "Incremental", 58 | "template": { 59 | "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 60 | "contentVersion": "1.0.0.0", 61 | "resources": [ 62 | { 63 | "location": "[parameters('resourceLocation')]", 64 | "name": "[parameters('resourceName')]", 65 | "type": "Microsoft.Web/sites", 66 | "apiVersion": "2015-08-01", 67 | "tags": { 68 | "[concat('hidden-related:', variables('appServicePlan_ResourceId'))]": "empty" 69 | }, 70 | "dependsOn": [ 71 | "[variables('appServicePlan_ResourceId')]" 72 | ], 73 | "kind": "app", 74 | "properties": { 75 | "name": "[parameters('resourceName')]", 76 | "kind": "app", 77 | "httpsOnly": true, 78 | "reserved": false, 79 | "serverFarmId": "[variables('appServicePlan_ResourceId')]", 80 | "siteConfig": { 81 | "metadata": [ 82 | { 83 | "name": "CURRENT_STACK", 84 | "value": "dotnetcore" 85 | } 86 | ] 87 | } 88 | }, 89 | "identity": { 90 | "type": "SystemAssigned" 91 | } 92 | }, 93 | { 94 | "location": "[parameters('resourceLocation')]", 95 | "name": "[variables('appServicePlan_name')]", 96 | "type": "Microsoft.Web/serverFarms", 97 | "apiVersion": "2015-08-01", 98 | "sku": { 99 | "name": "S1", 100 | "tier": "Standard", 101 | "family": "S", 102 | "size": "S1" 103 | }, 104 | "properties": { 105 | "name": "[variables('appServicePlan_name')]" 106 | } 107 | } 108 | ] 109 | } 110 | } 111 | } 112 | ] 113 | } -------------------------------------------------------------------------------- /BlazorBlogs/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:51022", 7 | "sslPort": 44324 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "BlazorBlogs": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /BlazorBlogs/Properties/serviceDependencies.blazor-blogs-test - Web Deploy.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "mssql1": { 4 | "ignored": "true", 5 | "type": "mssql" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /BlazorBlogs/SQLScripts/01.00.00.sql: -------------------------------------------------------------------------------- 1 | UPDATE [dbo].[Settings] 2 | SET [SettingValue] = '01.00.00' 3 | WHERE [SettingName] = 'VersionNumber' -------------------------------------------------------------------------------- /BlazorBlogs/SQLScripts/01.00.20.sql: -------------------------------------------------------------------------------- 1 | UPDATE [dbo].[Settings] 2 | SET [SettingValue] = '01.00.20' 3 | WHERE [SettingName] = 'VersionNumber' -------------------------------------------------------------------------------- /BlazorBlogs/SQLScripts/01.00.30.sql: -------------------------------------------------------------------------------- 1 | SET ANSI_NULLS ON 2 | 3 | SET QUOTED_IDENTIFIER ON 4 | 5 | IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[ExternalConnections]') AND type in (N'U')) 6 | BEGIN 7 | CREATE TABLE [dbo].[ExternalConnections]( 8 | [Id] [int] IDENTITY(1,1) NOT NULL, 9 | [ServerName] [nvarchar](1000) NOT NULL, 10 | [DatabaseName] [nvarchar](1000) NOT NULL, 11 | [IntegratedSecurity] [nvarchar](25) NOT NULL, 12 | [DatabaseUsername] [nvarchar](1000) NULL, 13 | [DatabasePassword] [nvarchar](1000) NULL, 14 | CONSTRAINT [PK_ExternalConnections] PRIMARY KEY CLUSTERED 15 | ( 16 | [Id] ASC 17 | )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 18 | ) ON [PRIMARY] 19 | END 20 | 21 | UPDATE [dbo].[Settings] 22 | SET [SettingValue] = '01.00.30' 23 | WHERE [SettingName] = 'VersionNumber' -------------------------------------------------------------------------------- /BlazorBlogs/SQLScripts/01.00.40.sql: -------------------------------------------------------------------------------- 1 | SET ANSI_NULLS ON 2 | 3 | SET QUOTED_IDENTIFIER ON 4 | 5 | UPDATE [dbo].[Settings] 6 | SET [SettingValue] = '01.00.40' 7 | WHERE [SettingName] = 'VersionNumber' -------------------------------------------------------------------------------- /BlazorBlogs/SQLScripts/01.00.50.sql: -------------------------------------------------------------------------------- 1 | SET ANSI_NULLS ON 2 | 3 | SET QUOTED_IDENTIFIER ON 4 | 5 | UPDATE [dbo].[Settings] 6 | SET [SettingValue] = '01.00.50' 7 | WHERE [SettingName] = 'VersionNumber' -------------------------------------------------------------------------------- /BlazorBlogs/SQLScripts/01.00.60.sql: -------------------------------------------------------------------------------- 1 | SET ANSI_NULLS ON 2 | 3 | SET QUOTED_IDENTIFIER ON 4 | 5 | UPDATE [dbo].[Settings] 6 | SET [SettingValue] = '01.00.60' 7 | WHERE [SettingName] = 'VersionNumber' -------------------------------------------------------------------------------- /BlazorBlogs/SQLScripts/01.00.70.sql: -------------------------------------------------------------------------------- 1 | SET ANSI_NULLS ON 2 | 3 | SET QUOTED_IDENTIFIER ON 4 | 5 | UPDATE [dbo].[Settings] 6 | SET [SettingValue] = '01.00.70' 7 | WHERE [SettingName] = 'VersionNumber' -------------------------------------------------------------------------------- /BlazorBlogs/SQLScripts/01.00.80.sql: -------------------------------------------------------------------------------- 1 | SET ANSI_NULLS ON 2 | 3 | SET QUOTED_IDENTIFIER ON 4 | 5 | IF (0 = (SELECT count(*) FROM [dbo].[Settings] where [SettingName] = 'GoogleTrackingID')) 6 | begin 7 | INSERT [dbo].[Settings] ([SettingName], [SettingValue]) VALUES (N'GoogleTrackingID', N'') 8 | end; 9 | 10 | UPDATE [dbo].[Settings] 11 | SET [SettingValue] = '01.00.80' 12 | WHERE [SettingName] = 'VersionNumber' -------------------------------------------------------------------------------- /BlazorBlogs/SQLScripts/01.00.90.sql: -------------------------------------------------------------------------------- 1 | SET ANSI_NULLS ON 2 | 3 | SET QUOTED_IDENTIFIER ON 4 | 5 | UPDATE [dbo].[Settings] 6 | SET [SettingValue] = '01.00.90' 7 | WHERE [SettingName] = 'VersionNumber' -------------------------------------------------------------------------------- /BlazorBlogs/SQLScripts/01.01.00.sql: -------------------------------------------------------------------------------- 1 | SET ANSI_NULLS ON 2 | 3 | SET QUOTED_IDENTIFIER ON 4 | 5 | UPDATE [dbo].[Settings] 6 | SET [SettingValue] = '01.01.00' 7 | WHERE [SettingName] = 'VersionNumber' -------------------------------------------------------------------------------- /BlazorBlogs/SQLScripts/01.01.10.sql: -------------------------------------------------------------------------------- 1 | SET ANSI_NULLS ON 2 | 3 | SET QUOTED_IDENTIFIER ON 4 | 5 | UPDATE [dbo].[Settings] 6 | SET [SettingValue] = '01.01.10' 7 | WHERE [SettingName] = 'VersionNumber' -------------------------------------------------------------------------------- /BlazorBlogs/SQLScripts/02.00.00.sql: -------------------------------------------------------------------------------- 1 | SET ANSI_NULLS ON 2 | 3 | SET QUOTED_IDENTIFIER ON 4 | 5 | UPDATE [dbo].[Settings] 6 | SET [SettingValue] = '02.00.00' 7 | WHERE [SettingName] = 'VersionNumber' -------------------------------------------------------------------------------- /BlazorBlogs/SQLScripts/03.00.00.sql: -------------------------------------------------------------------------------- 1 | SET ANSI_NULLS ON 2 | 3 | SET QUOTED_IDENTIFIER ON 4 | 5 | UPDATE [dbo].[Settings] 6 | SET [SettingValue] = '03.00.00' 7 | WHERE [SettingName] = 'VersionNumber' -------------------------------------------------------------------------------- /BlazorBlogs/SQLScripts/04.00.00.sql: -------------------------------------------------------------------------------- 1 | /****** Object: Table [dbo].[Newsletters] Script Date: 9/23/2023 1:06:07 PM ******/ 2 | SET ANSI_NULLS ON 3 | 4 | SET QUOTED_IDENTIFIER ON 5 | 6 | IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Newsletters]') AND type in (N'U')) 7 | BEGIN 8 | CREATE TABLE [dbo].[Newsletters]( 9 | [Id] [int] IDENTITY(1,1) NOT NULL, 10 | [NewsletterDate] [datetime] NOT NULL, 11 | [NewsletterTitle] [nvarchar](500) NOT NULL, 12 | [NewsletterContent] [nvarchar](max) NOT NULL, 13 | [Created] [datetime] NOT NULL, 14 | [Updated] [datetime] NULL, 15 | CONSTRAINT [PK_Newsletters] PRIMARY KEY CLUSTERED 16 | ( 17 | [Id] ASC 18 | )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 19 | ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] 20 | END 21 | 22 | /****** Object: Table [dbo].[NewslettersCampain] Script Date: 9/23/2023 1:06:07 PM ******/ 23 | SET ANSI_NULLS ON 24 | 25 | SET QUOTED_IDENTIFIER ON 26 | 27 | IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[NewslettersCampain]') AND type in (N'U')) 28 | BEGIN 29 | CREATE TABLE [dbo].[NewslettersCampain]( 30 | [Id] [int] IDENTITY(1,1) NOT NULL, 31 | [NewsletterCampainName] [nvarchar](50) NOT NULL, 32 | [NewsletterId] [int] NOT NULL, 33 | [Created] [datetime] NOT NULL, 34 | [Updated] [datetime] NULL, 35 | CONSTRAINT [PK_NewslettersCampain] PRIMARY KEY CLUSTERED 36 | ( 37 | [Id] ASC 38 | )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 39 | ) ON [PRIMARY] 40 | END 41 | 42 | /****** Object: Table [dbo].[NewslettersLogs] Script Date: 9/23/2023 1:06:07 PM ******/ 43 | SET ANSI_NULLS ON 44 | 45 | SET QUOTED_IDENTIFIER ON 46 | 47 | IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[NewslettersLogs]') AND type in (N'U')) 48 | BEGIN 49 | CREATE TABLE [dbo].[NewslettersLogs]( 50 | [Id] [int] IDENTITY(1,1) NOT NULL, 51 | [NewsletterCampainId] [int] NOT NULL, 52 | [LogType] [nvarchar](50) NULL, 53 | [UserName] [nvarchar](500) NULL, 54 | [LogDetails] [nvarchar](4000) NULL, 55 | CONSTRAINT [PK_NewslettersLogs] PRIMARY KEY CLUSTERED 56 | ( 57 | [Id] ASC 58 | )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 59 | ) ON [PRIMARY] 60 | END 61 | 62 | IF NOT EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'[dbo].[FK_NewslettersCampain_Newsletters]') AND parent_object_id = OBJECT_ID(N'[dbo].[NewslettersCampain]')) 63 | ALTER TABLE [dbo].[NewslettersCampain] WITH CHECK ADD CONSTRAINT [FK_NewslettersCampain_Newsletters] FOREIGN KEY([NewsletterId]) 64 | REFERENCES [dbo].[Newsletters] ([Id]) 65 | 66 | IF EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'[dbo].[FK_NewslettersCampain_Newsletters]') AND parent_object_id = OBJECT_ID(N'[dbo].[NewslettersCampain]')) 67 | ALTER TABLE [dbo].[NewslettersCampain] CHECK CONSTRAINT [FK_NewslettersCampain_Newsletters] 68 | 69 | IF NOT EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'[dbo].[FK_NewslettersLogs_NewslettersCampain]') AND parent_object_id = OBJECT_ID(N'[dbo].[NewslettersLogs]')) 70 | ALTER TABLE [dbo].[NewslettersLogs] WITH CHECK ADD CONSTRAINT [FK_NewslettersLogs_NewslettersCampain] FOREIGN KEY([NewsletterCampainId]) 71 | REFERENCES [dbo].[NewslettersCampain] ([Id]) 72 | 73 | IF EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'[dbo].[FK_NewslettersLogs_NewslettersCampain]') AND parent_object_id = OBJECT_ID(N'[dbo].[NewslettersLogs]')) 74 | ALTER TABLE [dbo].[NewslettersLogs] CHECK CONSTRAINT [FK_NewslettersLogs_NewslettersCampain] 75 | 76 | SET ANSI_NULLS ON 77 | 78 | SET QUOTED_IDENTIFIER ON 79 | 80 | UPDATE [dbo].[Settings] 81 | SET [SettingValue] = '04.00.00' 82 | WHERE [SettingName] = 'VersionNumber' -------------------------------------------------------------------------------- /BlazorBlogs/SQLScripts/09.00.00.sql: -------------------------------------------------------------------------------- 1 | SET ANSI_NULLS ON 2 | 3 | SET QUOTED_IDENTIFIER ON 4 | 5 | UPDATE [dbo].[Settings] 6 | SET [SettingValue] = '09.00.00' 7 | WHERE [SettingName] = 'VersionNumber' -------------------------------------------------------------------------------- /BlazorBlogs/Uploads/Placeholder.txt: -------------------------------------------------------------------------------- 1 | To ensure the directory is created -------------------------------------------------------------------------------- /BlazorBlogs/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /BlazorBlogs/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "DefaultConnection": "data source=(local);TrustServerCertificate=True;initial catalog=BlazorBlogs;integrated security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False" 4 | }, 5 | "Logging": { 6 | "LogLevel": { 7 | "Default": "Information", 8 | "Microsoft": "Warning", 9 | "Microsoft.Hosting.Lifetime": "Information" 10 | } 11 | }, 12 | "AllowedHosts": "*" 13 | } -------------------------------------------------------------------------------- /BlazorBlogs/wwwroot/app.css: -------------------------------------------------------------------------------- 1 | @import url('css/open-iconic/font/css/open-iconic-bootstrap.min.css'); 2 | 3 | html, body { 4 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 5 | } 6 | 7 | a, .btn-link { 8 | color: #006bb7; 9 | } 10 | 11 | .btn-primary { 12 | color: #fff; 13 | background-color: #1b6ec2; 14 | border-color: #1861ac; 15 | } 16 | 17 | .btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus { 18 | box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb; 19 | } 20 | 21 | .content { 22 | padding-top: 1.1rem; 23 | } 24 | 25 | h1:focus { 26 | outline: none; 27 | } 28 | 29 | .valid.modified:not([type=checkbox]) { 30 | outline: 1px solid #26b050; 31 | } 32 | 33 | .invalid { 34 | outline: 1px solid #e50000; 35 | } 36 | 37 | .validation-message { 38 | color: #e50000; 39 | } 40 | 41 | .blazor-error-boundary { 42 | background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; 43 | padding: 1rem 1rem 1rem 3.7rem; 44 | color: white; 45 | } 46 | 47 | .blazor-error-boundary::after { 48 | content: "An error has occurred." 49 | } 50 | 51 | .darker-border-checkbox.form-check-input { 52 | border-color: #929292; 53 | } 54 | 55 | .form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder { 56 | color: var(--bs-secondary-color); 57 | text-align: end; 58 | } 59 | 60 | .form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder { 61 | text-align: start; 62 | } -------------------------------------------------------------------------------- /BlazorBlogs/wwwroot/css/open-iconic/FONT-LICENSE: -------------------------------------------------------------------------------- 1 | SIL OPEN FONT LICENSE Version 1.1 2 | 3 | Copyright (c) 2014 Waybury 4 | 5 | PREAMBLE 6 | The goals of the Open Font License (OFL) are to stimulate worldwide 7 | development of collaborative font projects, to support the font creation 8 | efforts of academic and linguistic communities, and to provide a free and 9 | open framework in which fonts may be shared and improved in partnership 10 | with others. 11 | 12 | The OFL allows the licensed fonts to be used, studied, modified and 13 | redistributed freely as long as they are not sold by themselves. The 14 | fonts, including any derivative works, can be bundled, embedded, 15 | redistributed and/or sold with any software provided that any reserved 16 | names are not used by derivative works. The fonts and derivatives, 17 | however, cannot be released under any other type of license. The 18 | requirement for fonts to remain under this license does not apply 19 | to any document created using the fonts or their derivatives. 20 | 21 | DEFINITIONS 22 | "Font Software" refers to the set of files released by the Copyright 23 | Holder(s) under this license and clearly marked as such. This may 24 | include source files, build scripts and documentation. 25 | 26 | "Reserved Font Name" refers to any names specified as such after the 27 | copyright statement(s). 28 | 29 | "Original Version" refers to the collection of Font Software components as 30 | distributed by the Copyright Holder(s). 31 | 32 | "Modified Version" refers to any derivative made by adding to, deleting, 33 | or substituting -- in part or in whole -- any of the components of the 34 | Original Version, by changing formats or by porting the Font Software to a 35 | new environment. 36 | 37 | "Author" refers to any designer, engineer, programmer, technical 38 | writer or other person who contributed to the Font Software. 39 | 40 | PERMISSION & CONDITIONS 41 | Permission is hereby granted, free of charge, to any person obtaining 42 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 43 | redistribute, and sell modified and unmodified copies of the Font 44 | Software, subject to the following conditions: 45 | 46 | 1) Neither the Font Software nor any of its individual components, 47 | in Original or Modified Versions, may be sold by itself. 48 | 49 | 2) Original or Modified Versions of the Font Software may be bundled, 50 | redistributed and/or sold with any software, provided that each copy 51 | contains the above copyright notice and this license. These can be 52 | included either as stand-alone text files, human-readable headers or 53 | in the appropriate machine-readable metadata fields within text or 54 | binary files as long as those fields can be easily viewed by the user. 55 | 56 | 3) No Modified Version of the Font Software may use the Reserved Font 57 | Name(s) unless explicit written permission is granted by the corresponding 58 | Copyright Holder. This restriction only applies to the primary font name as 59 | presented to the users. 60 | 61 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 62 | Software shall not be used to promote, endorse or advertise any 63 | Modified Version, except to acknowledge the contribution(s) of the 64 | Copyright Holder(s) and the Author(s) or with their explicit written 65 | permission. 66 | 67 | 5) The Font Software, modified or unmodified, in part or in whole, 68 | must be distributed entirely under this license, and must not be 69 | distributed under any other license. The requirement for fonts to 70 | remain under this license does not apply to any document created 71 | using the Font Software. 72 | 73 | TERMINATION 74 | This license becomes null and void if any of the above conditions are 75 | not met. 76 | 77 | DISCLAIMER 78 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 79 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 80 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 81 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 82 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 83 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 84 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 85 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 86 | OTHER DEALINGS IN THE FONT SOFTWARE. 87 | -------------------------------------------------------------------------------- /BlazorBlogs/wwwroot/css/open-iconic/ICON-LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Waybury 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 13 | all 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 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /BlazorBlogs/wwwroot/css/open-iconic/README.md: -------------------------------------------------------------------------------- 1 | [Open Iconic v1.1.1](https://github.com/iconic/open-iconic) 2 | =========== 3 | 4 | ### Open Iconic is the open source sibling of [Iconic](https://github.com/iconic/open-iconic). It is a hyper-legible collection of 223 icons with a tiny footprint—ready to use with Bootstrap and Foundation. [View the collection](https://github.com/iconic/open-iconic) 5 | 6 | 7 | 8 | ## What's in Open Iconic? 9 | 10 | * 223 icons designed to be legible down to 8 pixels 11 | * Super-light SVG files - 61.8 for the entire set 12 | * SVG sprite—the modern replacement for icon fonts 13 | * Webfont (EOT, OTF, SVG, TTF, WOFF), PNG and WebP formats 14 | * Webfont stylesheets (including versions for Bootstrap and Foundation) in CSS, LESS, SCSS and Stylus formats 15 | * PNG and WebP raster images in 8px, 16px, 24px, 32px, 48px and 64px. 16 | 17 | 18 | ## Getting Started 19 | 20 | #### For code samples and everything else you need to get started with Open Iconic, check out our [Icons](https://github.com/iconic/open-iconic) and [Reference](https://github.com/iconic/open-iconic) sections. 21 | 22 | ### General Usage 23 | 24 | #### Using Open Iconic's SVGs 25 | 26 | We like SVGs and we think they're the way to display icons on the web. Since Open Iconic are just basic SVGs, we suggest you display them like you would any other image (don't forget the `alt` attribute). 27 | 28 | ``` 29 | icon name 30 | ``` 31 | 32 | #### Using Open Iconic's SVG Sprite 33 | 34 | Open Iconic also comes in a SVG sprite which allows you to display all the icons in the set with a single request. It's like an icon font, without being a hack. 35 | 36 | Adding an icon from an SVG sprite is a little different than what you're used to, but it's still a piece of cake. *Tip: To make your icons easily style able, we suggest adding a general class to the* `` *tag and a unique class name for each different icon in the* `` *tag.* 37 | 38 | ``` 39 | 40 | 41 | 42 | ``` 43 | 44 | Sizing icons only needs basic CSS. All the icons are in a square format, so just set the `` tag with equal width and height dimensions. 45 | 46 | ``` 47 | .icon { 48 | width: 16px; 49 | height: 16px; 50 | } 51 | ``` 52 | 53 | Coloring icons is even easier. All you need to do is set the `fill` rule on the `` tag. 54 | 55 | ``` 56 | .icon-account-login { 57 | fill: #f00; 58 | } 59 | ``` 60 | 61 | To learn more about SVG Sprites, read [Chris Coyier's guide](http://css-tricks.com/svg-sprites-use-better-icon-fonts/). 62 | 63 | #### Using Open Iconic's Icon Font... 64 | 65 | 66 | ##### …with Bootstrap 67 | 68 | You can find our Bootstrap stylesheets in `font/css/open-iconic-bootstrap.{css, less, scss, styl}` 69 | 70 | 71 | ``` 72 | 73 | ``` 74 | 75 | 76 | ``` 77 | 78 | ``` 79 | 80 | ##### …with Foundation 81 | 82 | You can find our Foundation stylesheets in `font/css/open-iconic-foundation.{css, less, scss, styl}` 83 | 84 | ``` 85 | 86 | ``` 87 | 88 | 89 | ``` 90 | 91 | ``` 92 | 93 | ##### …on its own 94 | 95 | You can find our default stylesheets in `font/css/open-iconic.{css, less, scss, styl}` 96 | 97 | ``` 98 | 99 | ``` 100 | 101 | ``` 102 | 103 | ``` 104 | 105 | 106 | ## License 107 | 108 | ### Icons 109 | 110 | All code (including SVG markup) is under the [MIT License](http://opensource.org/licenses/MIT). 111 | 112 | ### Fonts 113 | 114 | All fonts are under the [SIL Licensed](http://scripts.sil.org/cms/scripts/page.php?item_id=OFL_web). 115 | -------------------------------------------------------------------------------- /BlazorBlogs/wwwroot/css/open-iconic/font/fonts/open-iconic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ADefWebserver/Blazor-Blogs/c5d58fcfc27366114eb5ed7b14af561f3bac9c75/BlazorBlogs/wwwroot/css/open-iconic/font/fonts/open-iconic.eot -------------------------------------------------------------------------------- /BlazorBlogs/wwwroot/css/open-iconic/font/fonts/open-iconic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ADefWebserver/Blazor-Blogs/c5d58fcfc27366114eb5ed7b14af561f3bac9c75/BlazorBlogs/wwwroot/css/open-iconic/font/fonts/open-iconic.otf -------------------------------------------------------------------------------- /BlazorBlogs/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ADefWebserver/Blazor-Blogs/c5d58fcfc27366114eb5ed7b14af561f3bac9c75/BlazorBlogs/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf -------------------------------------------------------------------------------- /BlazorBlogs/wwwroot/css/open-iconic/font/fonts/open-iconic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ADefWebserver/Blazor-Blogs/c5d58fcfc27366114eb5ed7b14af561f3bac9c75/BlazorBlogs/wwwroot/css/open-iconic/font/fonts/open-iconic.woff -------------------------------------------------------------------------------- /BlazorBlogs/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ADefWebserver/Blazor-Blogs/c5d58fcfc27366114eb5ed7b14af561f3bac9c75/BlazorBlogs/wwwroot/favicon.ico -------------------------------------------------------------------------------- /BlazorBlogs/wwwroot/imageIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ADefWebserver/Blazor-Blogs/c5d58fcfc27366114eb5ed7b14af561f3bac9c75/BlazorBlogs/wwwroot/imageIcon.png -------------------------------------------------------------------------------- /BlazorBlogs/wwwroot/javascript/DisqusFunctions.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | window.DisqusFunctions = { 3 | createDisqus: function (disqusThreadElement, disqusSrc) { 4 | var dsq = document.createElement('script'); 5 | dsq.type = 'text/javascript'; 6 | dsq.async = true; 7 | dsq.src = disqusSrc; 8 | dsq.disqus_container_id = 'disqus_thread'; 9 | disqusThreadElement.appendChild(dsq); 10 | }, 11 | resetDisqus: function (newIdentifier, newUrl, newTitle) { 12 | try { 13 | DISQUS.reset({ 14 | reload: true, 15 | config: function () { 16 | this.page.identifier = newIdentifier; 17 | this.page.url = newUrl; 18 | this.page.title = newTitle; 19 | } 20 | }); 21 | } 22 | catch (err) { 23 | // do nothing 24 | } 25 | } 26 | }; 27 | })(); -------------------------------------------------------------------------------- /BlazorBlogs/wwwroot/uploads/BlazorHelpWebsite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ADefWebserver/Blazor-Blogs/c5d58fcfc27366114eb5ed7b14af561f3bac9c75/BlazorBlogs/wwwroot/uploads/BlazorHelpWebsite.png -------------------------------------------------------------------------------- /BlazorBlogs/wwwroot/uploads/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ADefWebserver/Blazor-Blogs/c5d58fcfc27366114eb5ed7b14af561f3bac9c75/BlazorBlogs/wwwroot/uploads/logo.png -------------------------------------------------------------------------------- /BlazorBlogs/wwwroot/wlwmanifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Metaweblog 5 | Yes 6 | Yes 7 | Yes 8 | No 9 | No 10 | No 11 | No 12 | No 13 | No 14 | No 15 | No 16 | No 17 | No 18 | Yes 19 | Yes 20 | No 21 | Yes 22 | No 23 | No 24 | Yes 25 | No 26 | No 27 | 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Michael Washington 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 | # Blazor-Blogs 2 | #### Simple blogging application written in Microsoft Server Side Blazor 3 | 4 | [![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FADefWebserver%2FBlazor-Blogs%2Fmain%2Fazuredeploy.json) 5 | 6 | See: [Installing Blazor-Blogs on Microsoft Azure (Deploy To Azure Button Method)](https://blazorblogs.azurewebsites.net/ViewBlogPost/1008 "Installing Blazor-Blogs on Microsoft Azure") 7 | 8 | ## Live example site: https://blazorblogs.azurewebsites.net 9 | #### Also see: [Installing Blazor-Blogs on Microsoft Azure (Manual Method)](https://blazorblogs.azurewebsites.net/ViewBlogPost/1007 "Installing Blazor-Blogs on Microsoft Azure") 10 | 11 | 12 | ![Screenshot](Animation.gif) 13 | 14 | ![Screenshot](Screenshot001.png) 15 | 16 | ![Screenshot](Screenshot006.png) 17 | 18 | ![Screenshot](Screenshot003.png) 19 | 20 | ![Screenshot](Screenshot004.png) 21 | 22 | ![Screenshot](Screenshot005.png) 23 | 24 | ![image](https://github.com/user-attachments/assets/de5a539a-ccde-4430-8ba4-55437b4bbf7e) 25 | 26 | ### Features 27 | 28 | * Supports unlimited Bloggers 29 | * Rich Text Editor with image uploads 30 | * [Open Live Writer and Windows Live Writer support](https://blazorblogs.azurewebsites.net/ViewBlogPost/1005) 31 | * [Disqus integration for Blog post comments](https://blazorblogs.azurewebsites.net/ViewBlogPost/1004) 32 | * User Administration and role management 33 | * User Registration with Email Verification and Password Resets 34 | * File Download Page 35 | * Custom Privacy Statement and Terms Of Use 36 | * Dynamic Header and Logo 37 | * Unlimited Tags for Blog Posts 38 | * RSS Feeds 39 | 40 | ### Installing 41 | 42 | 1) Create a Database 43 | 2) Deploy the code to Azure or IIS 44 | 3) Run the application by navigating to the root page in the web browser 45 | 4) The Install Wizard will show and guide you though installing the application 46 | 47 | ### Helpful Articles 48 | 49 | * [Installing Blazor-Blogs on Microsoft Azure (Deploy To Azure Button Method)](https://blazorblogs.azurewebsites.net/ViewBlogPost/1008 "Installing Blazor-Blogs on Microsoft Azure") 50 | * [Installing Blazor-Blogs on Microsoft Azure (Manual Method)](https://blazorblogs.azurewebsites.net/ViewBlogPost/1007 "Installing Blazor-Blogs on Microsoft Azure") 51 | * [Upgrading Blazor Blogs](https://blazorblogs.azurewebsites.net/ViewBlogPost/1013) 52 | * [Uploading Images With The Blazor Rich Text Editor](https://blazorhelpwebsite.com/ViewBlogPost/7 "Uploading Images With The Blazor Rich Text Editor") 53 | * [Creating Reusable Custom Blazor Controls](https://blazorhelpwebsite.com/ViewBlogPost/11 "Creating Reusable Custom Blazor Controls") 54 | * [Creating A Rich Text Editor In Blazor Using Quill](https://blazorhelpwebsite.com/ViewBlogPost/12 "Creating A Rich Text Editor In Blazor Using Quill") 55 | * [Importing Blogs From DotNetNuke Blogs](https://blazorblogs.azurewebsites.net/ViewBlogPost/1012 "Importing Blogs From DotNetNuke Blogs") 56 | 57 | Uses [Blazored.TextEditor - Rich Text Editor for Blazor applications](https://github.com/Blazored/TextEditor "Blazored.TextEditor - Rich Text Editor for Blazor applications") 58 | -------------------------------------------------------------------------------- /Screenshot001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ADefWebserver/Blazor-Blogs/c5d58fcfc27366114eb5ed7b14af561f3bac9c75/Screenshot001.png -------------------------------------------------------------------------------- /Screenshot002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ADefWebserver/Blazor-Blogs/c5d58fcfc27366114eb5ed7b14af561f3bac9c75/Screenshot002.png -------------------------------------------------------------------------------- /Screenshot003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ADefWebserver/Blazor-Blogs/c5d58fcfc27366114eb5ed7b14af561f3bac9c75/Screenshot003.png -------------------------------------------------------------------------------- /Screenshot004.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ADefWebserver/Blazor-Blogs/c5d58fcfc27366114eb5ed7b14af561f3bac9c75/Screenshot004.png -------------------------------------------------------------------------------- /Screenshot005.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ADefWebserver/Blazor-Blogs/c5d58fcfc27366114eb5ed7b14af561f3bac9c75/Screenshot005.png -------------------------------------------------------------------------------- /Screenshot006.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ADefWebserver/Blazor-Blogs/c5d58fcfc27366114eb5ed7b14af561f3bac9c75/Screenshot006.png --------------------------------------------------------------------------------