Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">

<PropertyGroup>
<Version>9.0.0</Version>
</PropertyGroup>

<PropertyGroup>
<PackageTags>Bootstrap Blazor WebAssembly wasm UI Components Office Viewer</PackageTags>
<Description>Bootstrap UI components extensions of Microsoft Office Documentation Viewer</Description>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="BootstrapBlazor" Version="$(BBVersion)" />
</ItemGroup>

<ItemGroup>
<Using Include="BootstrapBlazor.Components" />
<Using Include="Microsoft.JSInterop" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@namespace BootstrapBlazor.Components
@inherits BootstrapModuleComponentBase
@attribute [JSModuleAutoLoader("./_content/BootstrapBlazor.OfficeDocumentViewer/OfficeDocumentViewer.razor.js", JSObjectReference = true, AutoInvokeDispose = false)]

<div @attributes="AdditionalAttributes" id="@Id" class="@ClassString" style="@StyleString"></div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/

using Microsoft.AspNetCore.Components;

namespace BootstrapBlazor.Components;

public partial class OfficeDocumentViewer
{
/// <summary>
/// Gets or sets the url for the PDF file to be displayed.
/// </summary>
[Parameter]
public string? Url { get; set; }

/// <summary>
/// Gets or sets the viewer height. Default is null.
/// </summary>
[Parameter]
public string? Height { get; set; }

/// <summary>
/// Gets or sets the document loaded event callback.
/// </summary>
[Parameter]
public Func<Task>? OnLoaded { get; set; }

[Inject, NotNull]
private NavigationManager? NavigationManager { get; set; }

private string? ClassString => CssBuilder.Default("bb-office-viewer-container")
.AddClassFromAttributes(AdditionalAttributes)
.Build();

private string? StyleString => CssBuilder.Default()
.AddClass($"--bb-office-viewer-height: {Height};", !string.IsNullOrEmpty(Height))
.Build();

private string? _url;

/// <summary>
/// <inheritdoc/>
/// </summary>
/// <param name="firstRender"></param>
/// <returns></returns>
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);

if (firstRender)
{
_url = Url;
return;
}

var rerender = false;
if (_url != Url)
{
_url = Url;
rerender = true;
}

if (rerender)
{
await InvokeVoidAsync("load", Id, GetAbsoluteUri(_url));
}
}

/// <summary>
/// <inheritdoc/>
/// </summary>
/// <returns></returns>
protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, new
{
LoadedCallaback = nameof(TriggerOnLoaded),
Url = GetAbsoluteUri(Url)
});

private string GetAbsoluteUri(string? url)
{
url ??= string.Empty;
if (string.IsNullOrEmpty(url))
{
return url;
}
var uri = NavigationManager.ToAbsoluteUri(url);
return uri.AbsoluteUri;
}

/// <summary>
/// Trigger OnLoaded callback when the PDF document is loaded.
/// </summary>
/// <returns></returns>
[JSInvokable]
public async Task TriggerOnLoaded()
{
if (OnLoaded != null)
{
await OnLoaded();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { addLink } from "../BootstrapBlazor/modules/utility.js"
import Data from "../BootstrapBlazor/modules/data.js"

export async function init(id, invoke, options) {
await addLink("./_content/BootstrapBlazor.OfficeDocumentViewer/office-viewer.css");

const el = document.getElementById(id);
const officeViewer = { el, invoke, options };
Data.set(id, officeViewer);

await load(id, options.url);
}

export async function load(id, url) {
const officeViewer = Data.get(id);
const { el, invoke, options } = officeViewer;

el.innerHTML = '';

if (url) {
const { frame } = officeViewer;
const viewer = frame || createFrame(el);
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);

};
}
viewer.src = `https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(url)}`;
}
}

const createFrame = el => {
const frame = document.createElement('iframe');
frame.classList.add('bb-office-viewer');
el.appendChild(frame);
return frame;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@using BootstrapBlazor.Components;
@using Microsoft.AspNetCore.Components.Web
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.bb-office-viewer-container {
width: 100%;
height: var(--bb-office-viewer-height, 500px);
}

.bb-office-viewer {
width: 100%;
height: 100%;
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Version>9.0.0-beta01</Version>
<Version>9.0.0</Version>
</PropertyGroup>

<PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Version>9.0.0-beta01</Version>
<Version>9.0.0</Version>
</PropertyGroup>

<PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Version>9.0.0</Version>
</PropertyGroup>

<PropertyGroup>
<PackageTags>Bootstrap Blazor WebAssembly wasm UI Components Topology FlowChart</PackageTags>
<Description>Bootstrap UI components extensions of FlowChart</Description>
</PropertyGroup>

<ItemGroup>
<SupportedPlatform Remove="browser" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="TouchSocket" Version="4.0.0-Alpha.10" />
</ItemGroup>

<ItemGroup>
<!--<PackageReference Include="BootstrapBlazor" Version="9.8.0-beta07" />-->
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\..\BootstrapBlazor\src\BootstrapBlazor\BootstrapBlazor.csproj" />
</ItemGroup>

<ItemGroup>
<Using Include="Microsoft.JSInterop" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/

using BootstrapBlazor.Components;

namespace Microsoft.Extensions.DependencyInjection;

/// <summary>
/// BootstrapBlazor 服务扩展类
/// </summary>
public static class BootstrapBlazorTouchSocketServiceExtensions
{
/// <summary>
/// 添加 TouchSocket 服务
/// </summary>
/// <param name="services"></param>
public static IServiceCollection AddBootstrapBlazorTouchSocketProviderService(this IServiceCollection services)
{
services.AddTransient<ISocketClientProvider, DefaultTcpSocketProvider>();

return services;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/

using System.Buffers;
using System.Diagnostics;
using System.IO.Pipelines;
using System.Net;
using TouchSocket.Core;
using TouchSocket.Sockets;

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.

{
/// <summary>
/// <inheritdoc/>
/// </summary>
public bool IsConnected => base.Online;

/// <summary>
/// <inheritdoc/>
/// </summary>
public IPEndPoint LocalEndPoint { get; set; } = new IPEndPoint(IPAddress.Any, 0);

/// <summary>
/// <inheritdoc/>
/// </summary>
public async ValueTask CloseAsync()
{
await base.CloseAsync(string.Empty);
}

/// <summary>
/// <inheritdoc/>
/// </summary>
public async ValueTask<bool> ConnectAsync(IPEndPoint endPoint, CancellationToken token = default)
{
await SetupAsync(new TouchSocketConfig()
.SetBindIPHost(new IPHost(LocalEndPoint.Address, LocalEndPoint.Port))
.SetRemoteIPHost(new IPHost(endPoint.Address, endPoint.Port)));

try
{
await TcpConnectAsync(int.MaxValue, token);
Debug.Assert(MainSocket != null, "MainSocket cannot be null after connection.");
Debug.Assert(base.Online, "Online should be true after successful connection.");
if (MainSocket.LocalEndPoint is IPEndPoint localEndPoint)
{
LocalEndPoint = localEndPoint;
}
return true;
}
catch (Exception ex)
{
this.Logger?.Exception(this, ex);
return false;
}
}

/// <summary>
/// <inheritdoc/>
/// </summary>
public async ValueTask<int> ReceiveAsync(Memory<byte> buffer, CancellationToken token = default)
{
token.ThrowIfCancellationRequested();
this.ThrowIfTcpClientNotConnected();
this.ThrowIfDisposed();

var result = await base.Transport.Input.ReadAsync(token);
if (result.IsCompleted)
{
return 0;
}
var length = (int)Math.Min(result.Buffer.Length, buffer.Length);

var sequence = result.Buffer.Slice(0, length);

sequence.CopyTo(buffer.Span);
base.Transport.Input.AdvanceTo(sequence.End);
return length;
}

/// <summary>
/// <inheritdoc/>
/// </summary>
public async ValueTask<bool> SendAsync(ReadOnlyMemory<byte> data, CancellationToken token = default)
{
token.ThrowIfCancellationRequested();
base.ThrowIfTcpClientNotConnected();
base.ThrowIfDisposed();
var pipeWriter = base.Transport.Output;
var locker = base.Transport.SemaphoreSlimForWriter;
await locker.WaitAsync(token);
try
{
pipeWriter.Write(data.Span);
var result = await pipeWriter.FlushAsync(token);
if (result.IsCanceled || result.IsCompleted)
{
return false;
}
return true;
}
catch (Exception ex)
{
this.Logger?.Exception(this, ex);
return false;
}
finally
{
locker.Release();
}
}

protected override sealed async Task ReceiveLoopAsync(ITransport transport)
{
//重写接收循环方法
//此处不做任何数据读取
//让数据直接到ReceiveAsync使用管道直接读取数据
await Task.Delay(-1, transport.ClosedToken);
}
}
Loading