diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..a89ff0b
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,51 @@
+name: CI
+
+on:
+ push:
+ branches: [main, dev]
+ pull_request:
+ branches: [main, dev]
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ build-and-test:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup .NET SDK
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: "9.0.x"
+
+ - name: Cache NuGet packages
+ uses: actions/cache@v4
+ with:
+ path: ~/.nuget/packages
+ key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
+ restore-keys: |
+ ${{ runner.os }}-nuget-
+
+ - name: Restore
+ run: dotnet restore BlazorBlog.sln
+
+ - name: Build (Release)
+ run: dotnet build BlazorBlog.sln -c Release --no-restore
+
+ - name: Test
+ run: dotnet test BlazorBlog.sln -c Release --no-build --logger "trx;LogFileName=test_results.trx" --results-directory "TestResults"
+
+ - name: Upload test results
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: test-results
+ path: TestResults/**/*.trx
diff --git a/BlazorBlog/BlazorBlog.csproj b/BlazorBlog/BlazorBlog.csproj
index 7d11d4b..ee96350 100644
--- a/BlazorBlog/BlazorBlog.csproj
+++ b/BlazorBlog/BlazorBlog.csproj
@@ -12,7 +12,6 @@
all
-
diff --git a/BlazorBlog/Components/Pages/Admin/ManageCategories.razor.cs b/BlazorBlog/Components/Pages/Admin/ManageCategories.razor.cs
index 7608d82..631077c 100644
--- a/BlazorBlog/Components/Pages/Admin/ManageCategories.razor.cs
+++ b/BlazorBlog/Components/Pages/Admin/ManageCategories.razor.cs
@@ -120,7 +120,7 @@ private async Task SaveCategoryAsync()
}
catch (InvalidOperationException ex)
{
- var nameField = new FieldIdentifier(_operatingCategory, nameof(Category.Name));
+ var nameField = new FieldIdentifier(_operatingCategory!, nameof(Category.Name));
_messageStore.Add(nameField, ex.Message);
_editContext.NotifyValidationStateChanged();
diff --git a/BlazorBlog/Components/Pages/Admin/SaveBlogPost.razor.cs b/BlazorBlog/Components/Pages/Admin/SaveBlogPost.razor.cs
index fa295c4..15a7c19 100644
--- a/BlazorBlog/Components/Pages/Admin/SaveBlogPost.razor.cs
+++ b/BlazorBlog/Components/Pages/Admin/SaveBlogPost.razor.cs
@@ -8,38 +8,40 @@ public partial class SaveBlogPost
{
private const int MaxFileLenght = 10 * 1024 * 1024;
- private bool _isLoading;
- private string? _loadingText;
+ private bool _isLoading = false;
+ private string? _loadingText = null;
private BlogPostVm _blogPostVm = new BlogPostVm();
private EditContext _editContext = default!;
private ValidationMessageStore? _messageStore;
private BlazoredTextEditor? _quillHtml;
private Category[] _categories = [];
private string? _content = default!;
- private string? _errorMessage = null;
+ private string? _errorMessage = null;
private IBrowserFile? _fileToUpload;
private string? _imageUrl;
private string PageTitle => Id is > 0 ? "Edit Blog Post" : "New Blog Post";
- private bool _isSaving;
- public bool IsSaving => _isSaving;
- public string TagsCsv { get; set; } = string.Empty;
+ private bool _isSaving;
+ public bool IsSaving => _isSaving;
+ public string TagsCsv { get; set; } = string.Empty;
private readonly CancellationTokenSource _cts = new();
- [Inject] AuthenticationStateProvider AuthenticationStateProvider { get; set; } = default!;
- [Inject] IWebHostEnvironment WebHostEnvironment { get; set; } = default!;
- [Inject] IBlogPostAdminService BlogPostService { get; set; } = default!;
- [Inject] ICategoryService CategoryService { get; set; } = default!;
- [Inject] NavigationManager NavigationManager { get; set; } = default!;
- [Inject] IToastService ToastService { get; set; } = default!;
- [Inject] IHtmlSanitizer HtmlSanitizer { get; set; } = default!;
- [Inject] IValidator Validator { get; set; } = default!;
- [Inject] ITagService TagService { get; set; } = default!;
+ [Inject] AuthenticationStateProvider AuthenticationStateProvider { get; set; } = default!;
+ [Inject] IWebHostEnvironment WebHostEnvironment { get; set; } = default!;
+ [Inject] IBlogPostAdminService BlogPostService { get; set; } = default!;
+ [Inject] ICategoryService CategoryService { get; set; } = default!;
+ [Inject] NavigationManager NavigationManager { get; set; } = default!;
+ [Inject] IToastService ToastService { get; set; } = default!;
+ [Inject] IHtmlSanitizer HtmlSanitizer { get; set; } = default!;
+ [Inject] IValidator Validator { get; set; } = default!;
+ [Inject] ITagService TagService { get; set; } = default!;
[Parameter] public int? Id { get; set; }
protected override async Task OnInitializedAsync()
{
+ _isLoading = true;
+ _loadingText = "Loading blog post...";
_editContext = new EditContext(_blogPostVm);
_messageStore = new ValidationMessageStore(_editContext);
@@ -67,6 +69,8 @@ protected override async Task OnInitializedAsync()
}
catch { /* optional */ }
}
+ _isLoading = false;
+ _loadingText = null;
}
private async Task PreviewImageAsync(IBrowserFile file)
@@ -130,6 +134,8 @@ private async Task SubmitAsync()
}
_isSaving = true;
+ _isLoading = true;
+ _loadingText = "Saving blog post...";
StateHasChanged();
await SaveBlogPostAsync();
}
@@ -190,6 +196,11 @@ private async Task SaveBlogPostAsync()
ToastService.ShowToast(ToastLevel.Error, "Something went wrong while saving the blog post.", heading: "Error");
_isSaving = false;
}
+ finally
+ {
+ _isLoading = false;
+ _loadingText = null;
+ }
}
private async Task SaveFileAsync(IBrowserFile file)