Skip to content

feat(PdfReader): add load progress function#7322

Merged
ArgoZhang merged 6 commits intomainfrom
feat-pdf
Dec 13, 2025
Merged

feat(PdfReader): add load progress function#7322
ArgoZhang merged 6 commits intomainfrom
feat-pdf

Conversation

@ArgoZhang
Copy link
Copy Markdown
Member

@ArgoZhang ArgoZhang commented Dec 13, 2025

Link issues

fixes #7321

Summary By Copilot

Regression?

  • Yes
  • No

Risk

  • High
  • Medium
  • Low

Verification

  • Manual (required)
  • Automated

Packaging changes reviewed?

  • Yes
  • No
  • N/A

☑️ Self Check before Merge

⚠️ Please check all items below before review. ⚠️

  • Doc is updated/provided or not needed
  • Demo is updated/provided or not needed
  • Merge the latest code from the main branch

Summary by Sourcery

Add stream-based loading support to the PdfReader sample and expose UI controls to switch between URL and stream sources.

New Features:

  • Support loading PDF content in the PdfReader sample via an asynchronous stream provider callback instead of only URL-based loading.
  • Provide sample UI buttons to toggle between different PDF files loaded either by URL or by stream.

Enhancements:

  • Refine default PDF URL configuration in the PdfReader sample to work with both direct URL and stream-based loading paths.

Copilot AI review requested due to automatic review settings December 13, 2025 08:31
@bb-auto bb-auto Bot added the enhancement New feature or request label Dec 13, 2025
@bb-auto bb-auto Bot added this to the v10.1.0 milestone Dec 13, 2025
@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Dec 13, 2025

Reviewer's Guide

Adds stream-based loading support and progress demonstration to the PdfReader sample by introducing an OnGetStreamAsync callback, toggling between URL and stream sources, and wiring new sample buttons for different PDFs.

Sequence diagram for PdfReader stream-based loading with OnGetStreamAsync

sequenceDiagram
    actor User
    participant PdfReaders
    participant PdfReader
    participant WebHostEnvironment
    participant FileSystem

    User->>PdfReaders: click Sample-Stream button
    PdfReaders->>PdfReaders: GetSampleStream()
    PdfReaders->>PdfReaders: _url = ""
    PdfReaders->>PdfReaders: _streamFileName = "sample.pdf"

    PdfReader->>PdfReaders: OnGetStreamAsync()
    activate PdfReaders
    PdfReaders->>PdfReaders: check _streamFileName
    alt _streamFileName is empty
        PdfReaders-->>PdfReader: return Stream.Null
    else _streamFileName is set
        PdfReaders->>WebHostEnvironment: get WebRootPath
        WebHostEnvironment-->>PdfReaders: WebRootPath
        PdfReaders->>FileSystem: File.OpenRead(WebRootPath + "/samples/" + _streamFileName)
        FileSystem-->>PdfReaders: Stream
        PdfReaders-->>PdfReader: return Stream
    end
    deactivate PdfReaders

    PdfReader->>PdfReader: render PDF pages from returned stream
Loading

Updated class diagram for PdfReaders sample component

classDiagram
    class PdfReaders {
        - bool _showTwoPagesOneView
        - bool _enableThumbnails
        - bool _showDownload
        - bool _showToolbar
        - string _url
        - string _streamFileName
        + Task~Stream~ OnGetStreamAsync()
        + void GetTestStream()
        + void GetSampleStream()
    }
Loading

Flow diagram for selecting PDF source in PdfReaders sample

flowchart LR
    Start([Start])
    B{User clicks button}
    BU[Sample-Url]
    BV[Sample2-Url]
    BS[Sample-Stream]
    BT[Sample2-Stream]
    C[Set _url to ./samples/sample.pdf]
    D[Set _url to ./samples/sample2.pdf]
    E[Set _url to empty]
    F[Set _streamFileName to sample.pdf]
    G[Set _streamFileName to sample2.pdf]
    H{Is _url empty?}
    I[PdfReader loads PDF from Url]
    J[PdfReader calls OnGetStreamAsync]
    K{Is _streamFileName empty?}
    L[Return Stream.Null]
    M[Open file at WebRootPath/samples/_streamFileName]
    N[Return file Stream to PdfReader]
    End([End])

    Start --> B
    B --> BU
    B --> BV
    B --> BS
    B --> BT

    BU --> C --> H
    BV --> D --> H

    BS --> E --> F --> H
    BT --> E --> G --> H

    H -->|No| I --> End
    H -->|Yes| J --> K
    K -->|Yes| L --> End
    K -->|No| M --> N --> End
Loading

File-Level Changes

Change Details Files
Replace download callback usage with a new stream-loading callback for PdfReader and adjust sample state to support URL vs. stream loading.
  • Remove the OnDownloadAsync handler and introduce OnGetStreamAsync returning a Stream for the PdfReader component.
  • Track the currently selected stream file name via a new _streamFileName field and return Stream.Null when none is set.
  • Add helper methods GetTestStream and GetSampleStream to switch the reader into stream mode by clearing the Url and selecting the desired sample PDF file.
  • Update the PdfReader usage to bind OnGetStreamAsync instead of OnDownloadAsync while preserving other configuration properties.
src/BootstrapBlazor.Server/Components/Samples/PdfReaders.razor.cs
src/BootstrapBlazor.Server/Components/Samples/PdfReaders.razor
Extend the PdfReaders sample UI with buttons to choose between URL-based and stream-based PDF loading for two sample files.
  • Add buttons to set the PdfReader Url directly to sample.pdf and sample2.pdf for URL-based loading scenarios.
  • Add buttons to invoke GetSampleStream and GetTestStream to demonstrate loading the same PDFs via OnGetStreamAsync stream callback instead of direct URLs.
src/BootstrapBlazor.Server/Components/Samples/PdfReaders.razor

Assessment against linked issues

Issue Objective Addressed Explanation
#7321 Add a load progress function to the PdfReader component (including any necessary API and/or sample usage) so that PDF loading progress can be tracked. The PR only introduces stream-based loading for PdfReader in the sample (OnGetStreamAsync, GetSampleStream/GetTestStream, and related buttons) and switches from OnDownloadAsync to OnGetStreamAsync. There is no implementation of a load progress function, no progress callback/handler, and no UI or API related to reporting loading progress.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@ArgoZhang ArgoZhang merged commit 2e5c675 into main Dec 13, 2025
6 of 8 checks passed
@ArgoZhang ArgoZhang deleted the feat-pdf branch December 13, 2025 08:32
Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes and found some issues that need to be addressed.

  • The Razor button OnClick lambdas use double quotes inside a double-quoted attribute (e.g., OnClick="@(() => _url = "./samples/sample.pdf")"), which will break parsing; use single quotes around the path or interpolate differently to avoid nested double quotes.
  • OnGetStreamAsync is marked async but only calls Task.Yield() and then uses synchronous File.OpenRead; consider removing async/Task.Yield() or switching to truly asynchronous file IO (e.g., FileStream with useAsync: true) for clarity and consistency.
  • The file names and sample paths (sample.pdf, sample2.pdf, ./samples/...) are duplicated across methods and the markup; consider centralizing these into constants or a small configuration to reduce the risk of typos and ease future changes.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The Razor button `OnClick` lambdas use double quotes inside a double-quoted attribute (e.g., `OnClick="@(() => _url = "./samples/sample.pdf")"`), which will break parsing; use single quotes around the path or interpolate differently to avoid nested double quotes.
- `OnGetStreamAsync` is marked async but only calls `Task.Yield()` and then uses synchronous `File.OpenRead`; consider removing `async`/`Task.Yield()` or switching to truly asynchronous file IO (e.g., `FileStream` with `useAsync: true`) for clarity and consistency.
- The file names and sample paths (`sample.pdf`, `sample2.pdf`, `./samples/...`) are duplicated across methods and the markup; consider centralizing these into constants or a small configuration to reduce the risk of typos and ease future changes.

## Individual Comments

### Comment 1
<location> `src/BootstrapBlazor.Server/Components/Samples/PdfReaders.razor:42-43` </location>
<code_context>
             </BootstrapInputGroup>
         </div>
+        <div class="col-12">
+            <Button Color="Color.Primary" OnClick="@(() => _url = "./samples/sample.pdf")" class="me-2">Sample-Url</Button>
+            <Button Color="Color.Primary" OnClick="@(() => _url = "./samples/sample2.pdf")" class="me-2">Sample2-Url</Button>
+            <Button Color="Color.Danger" OnClick="GetSampleStream" class="me-2">Sample-Stream</Button>
+            <Button Color="Color.Danger" OnClick="GetTestStream">Sample2-Stream</Button>
</code_context>

<issue_to_address>
**issue (bug_risk):** The inline lambdas for `OnClick` have invalid string quoting and will not compile.

The nested double quotes in the `OnClick` lambdas break Razor/C# parsing. Use a different quoting strategy, e.g. escape the inner quotes (`@(() => _url = "./samples/sample.pdf")`) or switch to single quotes inside the string: `@(() => _url = './samples/sample.pdf')`.
</issue_to_address>

### Comment 2
<location> `src/BootstrapBlazor.Server/Components/Samples/PdfReaders.razor.cs:21-25` </location>
<code_context>
     private bool _enableThumbnails = true;
     private bool _showDownload = true;
     private bool _showToolbar = true;
-    private string _url = "./samples/sample.pdf";
+    private string _url = "sample.pdf";
+    private string _streamFileName = "";

</code_context>

<issue_to_address>
**suggestion (bug_risk):** The initial URL value uses a different path pattern than the button click handlers, which may be confusing or inconsistent.

The initial `_url` now points to `"sample.pdf"` while the buttons use `"./samples/sample.pdf"` and `"./samples/sample2.pdf"`. If `PdfReader` resolves these differently, the first load could fail or behave differently from the buttons. Consider using a consistent path pattern (e.g., all with `./samples/...` or all without) based on where the files actually reside.

```suggestion
    private bool _enableThumbnails = true;
    private bool _showDownload = true;
    private bool _showToolbar = true;
    private string _url = "./samples/sample.pdf";
    private string _streamFileName = "";
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +42 to +43
<Button Color="Color.Primary" OnClick="@(() => _url = "./samples/sample.pdf")" class="me-2">Sample-Url</Button>
<Button Color="Color.Primary" OnClick="@(() => _url = "./samples/sample2.pdf")" class="me-2">Sample2-Url</Button>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): The inline lambdas for OnClick have invalid string quoting and will not compile.

The nested double quotes in the OnClick lambdas break Razor/C# parsing. Use a different quoting strategy, e.g. escape the inner quotes (@(() => _url = "./samples/sample.pdf")) or switch to single quotes inside the string: @(() => _url = './samples/sample.pdf').

Comment on lines 21 to 25
private bool _enableThumbnails = true;
private bool _showDownload = true;
private bool _showToolbar = true;
private string _url = "./samples/sample.pdf";
private string _url = "sample.pdf";
private string _streamFileName = "";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): The initial URL value uses a different path pattern than the button click handlers, which may be confusing or inconsistent.

The initial _url now points to "sample.pdf" while the buttons use "./samples/sample.pdf" and "./samples/sample2.pdf". If PdfReader resolves these differently, the first load could fail or behave differently from the buttons. Consider using a consistent path pattern (e.g., all with ./samples/... or all without) based on where the files actually reside.

Suggested change
private bool _enableThumbnails = true;
private bool _showDownload = true;
private bool _showToolbar = true;
private string _url = "./samples/sample.pdf";
private string _url = "sample.pdf";
private string _streamFileName = "";
private bool _enableThumbnails = true;
private bool _showDownload = true;
private bool _showToolbar = true;
private string _url = "./samples/sample.pdf";
private string _streamFileName = "";

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds stream-based loading functionality to the PdfReader component demo, replacing the previous download-based approach. The changes demonstrate how to load PDF files from streams rather than just URLs, upgrading to version 10.0.15 of the BootstrapBlazor.PdfReader package.

Key Changes:

  • Replaced OnDownloadAsync callback with OnGetStreamAsync to support stream-based PDF loading
  • Added UI buttons to demonstrate both URL-based and stream-based PDF loading modes
  • Updated BootstrapBlazor.PdfReader package from version 10.0.12 to 10.0.15

Reviewed changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated 7 comments.

File Description
src/BootstrapBlazor.Server/Components/Samples/PdfReaders.razor.cs Implements stream loading functionality with OnGetStreamAsync method and helper methods to switch between URL and stream modes
src/BootstrapBlazor.Server/Components/Samples/PdfReaders.razor Adds buttons to toggle between different PDF samples using URL or stream loading approaches
src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj Updates PdfReader package version to 10.0.15 to support new streaming capabilities

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

{
var file = Path.Combine(WebHostEnvironment.WebRootPath, "samples", "sample.pdf");
await DownloadService.DownloadFromFileAsync($"sample_{DateTime.Now:yyyyMMddHHmmss}.pdf", file);
await Task.Yield();
Copy link

Copilot AI Dec 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The call to await Task.Yield() appears unnecessary in this context. Task.Yield() is typically used to force asynchronous yielding in tight loops or to prevent blocking the synchronization context. Since the rest of the method is synchronous (File.OpenRead is synchronous), this adds no value and makes the code less clear. Consider removing it or using Task.FromResult if the method needs to remain async for interface compliance.

Suggested change
await Task.Yield();

Copilot uses AI. Check for mistakes.
Comment on lines +39 to +43
private void GetTestStream()
{
_url = "";
_streamFileName = "sample2.pdf";
}
Copy link

Copilot AI Dec 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method name GetTestStream is ambiguous. It's unclear what "Test" refers to in this context - it appears to load "sample2.pdf" which doesn't obviously relate to "test". Consider renaming to something more descriptive like GetSample2Stream or LoadSecondSampleStream to better reflect its purpose.

Copilot uses AI. Check for mistakes.
</div>
<div class="col-12">
<Button Color="Color.Primary" OnClick="@(() => _url = "./samples/sample.pdf")" class="me-2">Sample-Url</Button>
<Button Color="Color.Primary" OnClick="@(() => _url = "./samples/sample2.pdf")" class="me-2">Sample2-Url</Button>
Copy link

Copilot AI Dec 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The button label "Sample2-Url" is inconsistent with the naming used elsewhere. Consider using more descriptive labels that indicate the actual difference between the samples, or maintain consistency by using "Sample 2" (with a space) instead of "Sample2" to match the pattern if "Sample-Url" represents "Sample 1".

Suggested change
<Button Color="Color.Primary" OnClick="@(() => _url = "./samples/sample2.pdf")" class="me-2">Sample2-Url</Button>
<Button Color="Color.Primary" OnClick="@(() => _url = "./samples/sample2.pdf")" class="me-2">Sample 2-Url</Button>

Copilot uses AI. Check for mistakes.
Comment on lines +39 to +48
private void GetTestStream()
{
_url = "";
_streamFileName = "sample2.pdf";
}

private void GetSampleStream()
{
_url = "";
_streamFileName = "sample.pdf";
Copy link

Copilot AI Dec 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The GetTestStream and GetSampleStream methods have duplicated logic - both clear the URL and set a stream filename. Consider consolidating this into a single parameterized method like LoadStreamFile(string fileName) to reduce code duplication and improve maintainability.

Suggested change
private void GetTestStream()
{
_url = "";
_streamFileName = "sample2.pdf";
}
private void GetSampleStream()
{
_url = "";
_streamFileName = "sample.pdf";
private void LoadStreamFile(string fileName)
{
_url = "";
_streamFileName = fileName;
}
private void GetTestStream()
{
LoadStreamFile("sample2.pdf");
}
private void GetSampleStream()
{
LoadStreamFile("sample.pdf");

Copilot uses AI. Check for mistakes.
Comment on lines +35 to +36
var stream = File.OpenRead(Path.Combine(WebHostEnvironment.WebRootPath, "samples", _streamFileName));
return stream;
Copy link

Copilot AI Dec 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file stream opened by File.OpenRead is not being properly disposed of. When OnGetStreamAsync is called, it opens a file stream and returns it to the caller. However, there's no guarantee that the caller will dispose of the stream. This can lead to file handle leaks and resource exhaustion over time. Consider implementing IDisposable pattern in the component or ensuring the stream is properly disposed after use.

Suggested change
var stream = File.OpenRead(Path.Combine(WebHostEnvironment.WebRootPath, "samples", _streamFileName));
return stream;
var filePath = Path.Combine(WebHostEnvironment.WebRootPath, "samples", _streamFileName);
var memoryStream = new MemoryStream();
using (var fileStream = File.OpenRead(filePath))
{
await fileStream.CopyToAsync(memoryStream);
}
memoryStream.Position = 0;
return memoryStream;

Copilot uses AI. Check for mistakes.
return Stream.Null;
}

var stream = File.OpenRead(Path.Combine(WebHostEnvironment.WebRootPath, "samples", _streamFileName));
Copy link

Copilot AI Dec 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method constructs a file path using _streamFileName without validating or sanitizing the input. While this is a demo/sample file, it sets a potentially dangerous precedent. An attacker could potentially manipulate _streamFileName to include path traversal sequences (e.g., "../../../sensitive.pdf") to access files outside the intended directory. Consider validating the filename to ensure it doesn't contain directory traversal characters or use Path.GetFileName to extract only the filename component.

Suggested change
var stream = File.OpenRead(Path.Combine(WebHostEnvironment.WebRootPath, "samples", _streamFileName));
var stream = File.OpenRead(Path.Combine(WebHostEnvironment.WebRootPath, "samples", Path.GetFileName(_streamFileName)));

Copilot uses AI. Check for mistakes.
Comment on lines +35 to +36
var stream = File.OpenRead(Path.Combine(WebHostEnvironment.WebRootPath, "samples", _streamFileName));
return stream;
Copy link

Copilot AI Dec 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The File.OpenRead call does not handle potential exceptions such as FileNotFoundException, UnauthorizedAccessException, or IOException. If the file doesn't exist or cannot be accessed, this will result in an unhandled exception that could crash the component or display unhelpful errors to users. Consider wrapping this in a try-catch block and returning an appropriate error response or Stream.Null with proper error logging.

Suggested change
var stream = File.OpenRead(Path.Combine(WebHostEnvironment.WebRootPath, "samples", _streamFileName));
return stream;
try
{
var stream = File.OpenRead(Path.Combine(WebHostEnvironment.WebRootPath, "samples", _streamFileName));
return stream;
}
catch (FileNotFoundException ex)
{
Console.Error.WriteLine($"File not found: {ex.Message}");
return Stream.Null;
}
catch (UnauthorizedAccessException ex)
{
Console.Error.WriteLine($"Access denied: {ex.Message}");
return Stream.Null;
}
catch (IOException ex)
{
Console.Error.WriteLine($"IO error: {ex.Message}");
return Stream.Null;
}

Copilot uses AI. Check for mistakes.
@codecov
Copy link
Copy Markdown

codecov Bot commented Dec 13, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.98%. Comparing base (904fd36) to head (ed946ce).
⚠️ Report is 3 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #7322   +/-   ##
=======================================
  Coverage   99.98%   99.98%           
=======================================
  Files         746      746           
  Lines       32561    32561           
  Branches     4500     4500           
=======================================
  Hits        32556    32556           
  Misses          1        1           
  Partials        4        4           
Flag Coverage Δ
BB 99.98% <ø> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(PdfReader): add load progress function

2 participants