Skip to content

feat(TcpSocket): add TcpSocket lib#512

Merged
ArgoZhang merged 20 commits intomasterfrom
feat-TouchSocket
Aug 5, 2025
Merged

feat(TcpSocket): add TcpSocket lib#512
ArgoZhang merged 20 commits intomasterfrom
feat-TouchSocket

Conversation

@ArgoZhang
Copy link
Copy Markdown
Member

@ArgoZhang ArgoZhang commented Aug 5, 2025

Link issues

fixes #511

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 a new TCP socket extension and an Office document viewer component

New Features:

  • Introduce a BootstrapBlazor.TcpSocket library with ITcpSocketFactory and DefaultTcpSocketProvider implementation using TouchSocket integration
  • Add comprehensive unit tests covering socket creation, connection, send/receive operations, timeouts, cancellations, errors, and custom data package handlers
  • Implement an OfficeDocumentViewer component with Razor UI, C# code-behind, JavaScript interop, and CSS styling

Enhancements:

  • Provide an AddBootstrapBlazorTouchSocketProviderService extension method for registering the default socket provider

Build:

  • Add new project files for BootstrapBlazor.TcpSocket, BootstrapBlazor.TouchSocket, and UnitTestTouchSocket

ArgoZhang and others added 19 commits June 18, 2025 14:37
添加 TouchSocket 包引用,删除 DefaultTcpSocketClient 类,使用 TouchSocketTcpClient 类实现 ITcpSocketClient 接口,提供连接、发送和接收数据的功能。修改 DefaultTcpSocketFactory 类构造函数,移除对 ILogger<DefaultTcpSocketClient> 的依赖,改为使用 TouchSocketTcpClient。新增 TouchSocketTcpClient 类实现 TCP 连接逻辑。
# Conflicts:
#	BootstrapBlazor.Extensions.sln
在 DefaultTcpSocketClient 类中,修改 SendAsync 方法的调用方式,去掉 WaitAsync,直接使用 SendAsync(data, sendToken),使代码更简洁并提高异步操作效率
在 `BootstrapBlazor.TouchSocket.csproj` 文件中,将 `TouchSocket` 的版本从 `3.1.11.8` 更新为 `4.0.0-Alpha.10`。
在 `DefaultTcpSocketProvider.cs` 文件中,类的访问修饰符从 `sealed` 改为 `internal`,并添加了对 `System.Diagnostics` 的引用。
修改 `IsConnected` 属性的实现,确保调用基类的 `Online` 属性。
添加新的 `CloseAsync` 方法,重写连接逻辑以包含异常处理,并在连接成功后进行调试断言。
更新 `ReceiveAsync` 方法的数据读取逻辑,使用基类的 `Transport.Input` 进行数据读取,并确保正确处理数据缓冲区。
重写 `SendAsync` 方法,添加对取消令牌的处理,并确保在发送数据时正确管理锁。
重写 `ReceiveLoopAsync` 方法,注释说明新的接收循环逻辑,确保数据通过管道直接读取,而不进行额外的数据读取操作
# Conflicts:
#	BootstrapBlazor.Extensions.sln
@bb-auto bb-auto Bot added the enhancement New feature or request label Aug 5, 2025
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Aug 5, 2025

Reviewer's Guide

The PR introduces a new TcpSocket extension library based on TouchSocket with a custom provider, DI registration, comprehensive unit tests, and project file updates; it also adds a new OfficeDocumentViewer component with Razor, JS interop, and styling.

Class diagram for DefaultTcpSocketProvider and ISocketClientProvider

classDiagram
    class ISocketClientProvider {
        <<interface>>
        +bool IsConnected
        +IPEndPoint LocalEndPoint
        +ValueTask CloseAsync()
        +ValueTask<bool> ConnectAsync(IPEndPoint, CancellationToken)
        +ValueTask<int> ReceiveAsync(Memory<byte>, CancellationToken)
        +ValueTask<bool> SendAsync(ReadOnlyMemory<byte>, CancellationToken)
    }
    class TcpClientBase {
        +bool Online
        +Task CloseAsync(string)
        +Task TcpConnectAsync(int, CancellationToken)
        +Transport Transport
        +Logger Logger
        +void ThrowIfTcpClientNotConnected()
        +void ThrowIfDisposed()
    }
    class DefaultTcpSocketProvider {
        +bool IsConnected
        +IPEndPoint LocalEndPoint
        +ValueTask CloseAsync()
        +ValueTask<bool> ConnectAsync(IPEndPoint, CancellationToken)
        +ValueTask<int> ReceiveAsync(Memory<byte>, CancellationToken)
        +ValueTask<bool> SendAsync(ReadOnlyMemory<byte>, CancellationToken)
        -Task ReceiveLoopAsync(ITransport)
    }
    ISocketClientProvider <|.. DefaultTcpSocketProvider
    TcpClientBase <|-- DefaultTcpSocketProvider
Loading

Class diagram for OfficeDocumentViewer component

classDiagram
    class OfficeDocumentViewer {
        +string? Url
        +string? Height
        +Func<Task>? OnLoaded
        -NavigationManager NavigationManager
        -string? ClassString
        -string? StyleString
        -string? _url
        +Task OnAfterRenderAsync(bool)
        +Task InvokeInitAsync()
        -string GetAbsoluteUri(string?)
        +Task TriggerOnLoaded()
    }
    class BootstrapModuleComponentBase {
    }
    OfficeDocumentViewer --|> BootstrapModuleComponentBase
Loading

File-Level Changes

Change Details Files
Implement custom TCP socket provider leveraging TouchSocket pipelines
  • Extend TcpClientBase and implement ISocketClientProvider
  • Override ConnectAsync to bind local endpoint and handle errors
  • Implement SendAsync using PipeWriter with semaphore locking and error logging
  • Implement ReceiveAsync reading from PipeReader and handling cancellations
  • Override ReceiveLoopAsync to defer data reading to ReceiveAsync
  • Add CloseAsync for graceful shutdown
src/extensions/BootstrapBlazor.TouchSocket/Services/DefaultTcpSocketProvider.cs
Register TcpSocket provider in DI
  • Add IServiceCollection extension for TouchSocket provider
  • Register ISocketClientProvider with Transient lifetime
src/extensions/BootstrapBlazor.TouchSocket/Extensions/ServiceCollectionExtensions.cs
Add comprehensive unit tests for TcpSocket factory and client behaviors
  • Test factory GetOrCreate, Remove, Dispose logic
  • Cover ConnectAsync, SendAsync, ReceiveAsync scenarios with timeouts, cancellations, and errors
  • Verify data package handlers: split, sticky, fixed-length, delimiter, and auto-close cases
  • Use mock logger and server stubs to simulate network conditions
test/UnitTestTouchSocket/TcpSocketFactoryTest.cs
Update and add project files for new TcpSocket and test modules
  • Add BootstrapBlazor.TcpSocket and UnitTestTouchSocket csproj files
  • Update BootstrapBlazor.Socket and BootstrapBlazor.TouchSocket csproj entries
  • Include project references for packaging and testing
src/extensions/BootstrapBlazor.Socket/BootstrapBlazor.Socket.csproj
src/extensions/BootstrapBlazor.TcpSocket/BootstrapBlazor.TcpSocket.csproj
src/extensions/BootstrapBlazor.TouchSocket/BootstrapBlazor.TouchSocket.csproj
test/UnitTestTouchSocket/UnitTestTouchSocket.csproj
Introduce OfficeDocumentViewer component with Razor and JS interop
  • Define component parameters, lifecycle, and JS invokable callback in .razor.cs
  • Add Razor markup with JSModuleAutoLoader attribute and styling hooks
  • Implement JS module for initialization and dynamic loading of Office viewer iframe
  • Provide CSS styling and imports for the component
src/components/BootstrapBlazor.OfficeDocumentViewer/OfficeDocumentViewer.razor.cs
src/components/BootstrapBlazor.OfficeDocumentViewer/OfficeDocumentViewer.razor
src/components/BootstrapBlazor.OfficeDocumentViewer/OfficeDocumentViewer.razor.js
src/components/BootstrapBlazor.OfficeDocumentViewer/wwwroot/office-viewer.css
src/components/BootstrapBlazor.OfficeDocumentViewer/_Imports.razor
src/components/BootstrapBlazor.OfficeDocumentViewer/BootstrapBlazor.OfficeDocumentViewer.csproj

Assessment against linked issues

Issue Objective Addressed Explanation
#511 Add a TcpSocket library to the project.

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

@bb-auto bb-auto Bot added this to the v9.2.0 milestone Aug 5, 2025
Copy link
Copy Markdown

@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 @ArgoZhang - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments

### Comment 1
<location> `src/components/BootstrapBlazor.OfficeDocumentViewer/OfficeDocumentViewer.razor.js:23` </location>
<code_context>
+    if (url) {
+        const { frame } = officeViewer;
+        const viewer = frame || createFrame(el);
+        if (options.loadedCallaback) {
+            viewer.onload = () => {
+                invoke.invokeMethodAsync(options.loadedCallaback);
</code_context>

<issue_to_address>
Typo in property access: 'loadedCallaback' should be 'loadedCallback'.

This typo could cause runtime errors due to referencing a non-existent property.
</issue_to_address>

<suggested_fix>
<<<<<<< SEARCH
        if (options.loadedCallaback) {
            viewer.onload = () => {
                invoke.invokeMethodAsync(options.loadedCallaback);
=======
        if (options.loadedCallback) {
            viewer.onload = () => {
                invoke.invokeMethodAsync(options.loadedCallback);
>>>>>>> REPLACE

</suggested_fix>

### Comment 2
<location> `src/extensions/BootstrapBlazor.TouchSocket/Services/DefaultTcpSocketProvider.cs:14` </location>
<code_context>
+
+namespace BootstrapBlazor.Components;
+
+internal sealed class DefaultTcpSocketProvider : TcpClientBase, ISocketClientProvider
+{
+    /// <summary>
</code_context>

<issue_to_address>
Consider replacing the custom pipeline and semaphore logic with a simple NetworkStream to handle reading and writing.

```suggestion
The manual PipeReader/PipeWriter plumbing, custom ReceiveLoopAsync override, and semaphore handling can be replaced by a simple NetworkStream over `MainSocket`. This preserves all behavior while dramatically cutting complexity.

1. Add a private `NetworkStream` field:
```csharp
private NetworkStream? _networkStream;
```

2. Initialize it in `ConnectAsync` (right after `TcpConnectAsync`):
```csharp
await TcpConnectAsync(int.MaxValue, token);
_networkStream = new NetworkStream(MainSocket!, ownsSocket: true);
```

3. Simplify `ReceiveAsync` to use `NetworkStream.ReadAsync`:
```csharp
public async ValueTask<int> ReceiveAsync(Memory<byte> buffer, CancellationToken token = default)
{
    token.ThrowIfCancellationRequested();
    ThrowIfTcpClientNotConnected(); // unchanged
    ThrowIfDisposed();             // unchanged

    try
    {
        int bytesRead = await _networkStream!.ReadAsync(buffer, token);
        return bytesRead;
    }
    catch (OperationCanceledException)
    {
        throw;
    }
    catch (Exception ex)
    {
        Logger?.Exception(this, ex);
        return 0;
    }
}
```

4. Simplify `SendAsync` to use `NetworkStream.WriteAsync` (remove semaphore):
```csharp
public async ValueTask<bool> SendAsync(ReadOnlyMemory<byte> data, CancellationToken token = default)
{
    token.ThrowIfCancellationRequested();
    ThrowIfTcpClientNotConnected();
    ThrowIfDisposed();

    try
    {
        await _networkStream!.WriteAsync(data, token);
        await _networkStream.FlushAsync(token);
        return true;
    }
    catch (Exception ex)
    {
        Logger?.Exception(this, ex);
        return false;
    }
}
```

5. Remove the entire `ReceiveLoopAsync` override—it's no longer needed since reads happen on demand.

These changes maintain the same public API and semantics but drop most of the custom pipeline plumbing.
</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 +23 to +25
if (options.loadedCallaback) {
viewer.onload = () => {
invoke.invokeMethodAsync(options.loadedCallaback);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (typo): Typo in property access: 'loadedCallaback' should be 'loadedCallback'.

This typo could cause runtime errors due to referencing a non-existent property.

Suggested change
if (options.loadedCallaback) {
viewer.onload = () => {
invoke.invokeMethodAsync(options.loadedCallaback);
if (options.loadedCallback) {
viewer.onload = () => {
invoke.invokeMethodAsync(options.loadedCallback);


namespace BootstrapBlazor.Components;

internal sealed class DefaultTcpSocketProvider : TcpClientBase, ISocketClientProvider
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (complexity): Consider replacing the custom pipeline and semaphore logic with a simple NetworkStream to handle reading and writing.

Suggested change
internal sealed class DefaultTcpSocketProvider : TcpClientBase, ISocketClientProvider
The manual PipeReader/PipeWriter plumbing, custom ReceiveLoopAsync override, and semaphore handling can be replaced by a simple NetworkStream over `MainSocket`. This preserves all behavior while dramatically cutting complexity.
1. Add a private `NetworkStream` field:
```csharp
private NetworkStream? _networkStream;
  1. Initialize it in ConnectAsync (right after TcpConnectAsync):
await TcpConnectAsync(int.MaxValue, token);
_networkStream = new NetworkStream(MainSocket!, ownsSocket: true);
  1. Simplify ReceiveAsync to use NetworkStream.ReadAsync:
public async ValueTask<int> ReceiveAsync(Memory<byte> buffer, CancellationToken token = default)
{
    token.ThrowIfCancellationRequested();
    ThrowIfTcpClientNotConnected(); // unchanged
    ThrowIfDisposed();             // unchanged

    try
    {
        int bytesRead = await _networkStream!.ReadAsync(buffer, token);
        return bytesRead;
    }
    catch (OperationCanceledException)
    {
        throw;
    }
    catch (Exception ex)
    {
        Logger?.Exception(this, ex);
        return 0;
    }
}
  1. Simplify SendAsync to use NetworkStream.WriteAsync (remove semaphore):
public async ValueTask<bool> SendAsync(ReadOnlyMemory<byte> data, CancellationToken token = default)
{
    token.ThrowIfCancellationRequested();
    ThrowIfTcpClientNotConnected();
    ThrowIfDisposed();

    try
    {
        await _networkStream!.WriteAsync(data, token);
        await _networkStream.FlushAsync(token);
        return true;
    }
    catch (Exception ex)
    {
        Logger?.Exception(this, ex);
        return false;
    }
}
  1. Remove the entire ReceiveLoopAsync override—it's no longer needed since reads happen on demand.

These changes maintain the same public API and semantics but drop most of the custom pipeline plumbing.

@ArgoZhang ArgoZhang merged commit 5729731 into master Aug 5, 2025
1 check passed
@ArgoZhang ArgoZhang deleted the feat-TouchSocket branch August 5, 2025 07:03
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(TcpSocket): add TcpSocket lib

2 participants