From 9ed70676889bb5360f75fdc3ba4bfd27f32e0e26 Mon Sep 17 00:00:00 2001 From: Zhelyazko Zhelyazkov Date: Sun, 17 Aug 2025 23:02:36 +0300 Subject: [PATCH 1/4] Fix nullability issue in FieldIdentifier instantiation Added null-forgiving operator to `_operatingCategory` in `FieldIdentifier` to prevent potential null reference exceptions during runtime. --- BlazorBlog/Components/Pages/Admin/ManageCategories.razor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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(); From 67a32e16f13b77b23f76ed959a78c192091f395d Mon Sep 17 00:00:00 2001 From: Zhelyazko Zhelyazkov Date: Sun, 17 Aug 2025 23:03:12 +0300 Subject: [PATCH 2/4] Enhance loading state management in SaveBlogPost Added `_isLoading` and `_loadingText` fields to provide user feedback during blog post loading and saving processes. Initialized `_isSaving` for consistent state management. These changes improve user experience by displaying appropriate loading messages during asynchronous operations. --- .../Pages/Admin/SaveBlogPost.razor.cs | 41 ++++++++++++------- 1 file changed, 26 insertions(+), 15 deletions(-) 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) From 745dd0874d7a1c34ffcb9035c222e23fe7ce7dee Mon Sep 17 00:00:00 2001 From: Zhelyazko Zhelyazkov Date: Sun, 17 Aug 2025 23:03:26 +0300 Subject: [PATCH 3/4] Remove AngleSharp package reference from project file This commit removes the package reference for `AngleSharp` version `1.3.0` from the `BlazorBlog.csproj` file, streamlining the project's dependencies. --- BlazorBlog/BlazorBlog.csproj | 1 - 1 file changed, 1 deletion(-) 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 - From c6cb19d64616f204e1d2f0f231b2dfec46fd2152 Mon Sep 17 00:00:00 2001 From: Zhelyazko Zhelyazkov Date: Sun, 17 Aug 2025 23:03:36 +0300 Subject: [PATCH 4/4] Add CI workflow for build and test automation Updated `ci.yml` to define a CI workflow with triggers for `push` and `pull_request` events on `main` and `dev` branches. Configured permissions, concurrency settings, and added a `build-and-test` job that runs on `ubuntu-latest`. The job includes steps for checking out code, setting up .NET SDK 9.0.x, caching NuGet packages, restoring the solution, building in Release mode, running tests, and uploading test results as artifacts. --- .github/workflows/ci.yml | 51 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 .github/workflows/ci.yml 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