从 Excel 电子表格复制到 Blazor 应用程序

S.M*_*des 2 excel clipboard copy blazor

我有一个托管的 Blazor WebAssembly 应用程序。

我需要一个策略或示例,了解如何从 Excel 电子表格复制值并将其粘贴到应用程序中,最终目标是通过现有 API 将它们添加到我的数据库中。

所以这里的问题是:我应该将值粘贴到哪些组件中,以及我应该如何处理整个过程:

excel > 剪贴板 > 组件 > 保存在数据库中

Jus*_*nno 11

实际上比我最初想象的要困难。我创建了一个repo。结果是这样的。

将数据从 Excel 复制到 Blazor

您可以选择 Excel 中的任何元素,复制它们,聚焦 Blazor 页面的内容并将其粘贴。作为一个简单的视图,它显示在表格中。

让我们看一下解决方案。

索引剃刀

@page "/"
<div class="form-group">
    <label for="parser">Parser type</label>
    <select class="form-control" id="parser" @bind="_parserType">
        <option value="text">Text</option>
        <option value="html">HTML</option>
    </select>
</div>

<PasteAwareComponent OnContentPasted="FillTable">
    @if (_excelContent.Any() == false)
    {
        <p>No Content</p>
    }
    else
    {
        <table class="table table-striped">
            @foreach (var row in _excelContent)
            {
                <tr>
                    @foreach (var cell in row)
                    {
                        <td>@cell</td>
                    }
                </tr>
            }
        </table>
    }
</PasteAwareComponent>

<button type="button" class="btn btn-primary" @onclick="@( () => _excelContent = new List<String[]>() )">Clear</button>

@code
{
    private IList<String[]> _excelContent = new List<String[]>();

    ...more content, explained later...
}

Run Code Online (Sandbox Code Playgroud)

如果将 Excel 中的选定内容复制到剪贴板中,则不会复制单个文本,而是复制同一内容的多个表示形式。在我的实验中,它分为三种不同的类型。

不是单个元素,而是从 Excel 复制多个表示

我构建了两个不同的解析器:ExcelHtmlContentParserExcelTextContentParser。关于 Excel 中单元格内容的多种不同可能性,我的实现仅仅完成了,应该被视为一种启发。要查看两个解析器的运行情况,您可以通过更改选择框中的值在它们之间进行选择。

处理PasteAwareComponent与 Javascript 的交互。您可以在此组件中放置任何内容。如果该组件(或任何子组件)具有焦点,则粘贴事件将得到正确处理。

<span @ref="_reference">
    @ChildContent
</span>

@code {
    private ElementReference _reference;

    [Parameter]
    public RenderFragment ChildContent { get; set; }

    [Parameter]
    public EventCallback<IEnumerable<IDictionary<String, String>>> OnContentPasted { get; set; }

    [JSInvokable("Pasted")]
    public async void raisePasteEvent(IEnumerable<IDictionary<String, String>> items)
    {
        await OnContentPasted.InvokeAsync(items);
    }

}
Run Code Online (Sandbox Code Playgroud)

该组件处理与 javascript 的互操作。一旦粘贴事件发生,就会EventCallback<IEnumerable<IDictionary<String, String>>> OnContentPasted被触发。

剪贴板内可能有多个元素。因此,我们需要处理一个集合IEnumerable<>。如上图所示,同一个剪贴板项目可以有多种表示形式。每个表示都有一个 mime 类型,如“text/plain”或“text/html”以及值。这由 表示,IDictionary<String, String>其中键是 mime 类型,值是内容。

在详细讨论 javascript 互操作之前,我们先回到组件Index

<PasteAwareComponent OnContentPasted="FillTable">
...
</PasteAwareComponent>

@code {
private async Task FillTable(IEnumerable<IDictionary<String, String>> content)
    {
        if (content == null || content.Count() != 1)
        {
            return;
        }

        var clipboardContent = content.ElementAt(0);
        IExcelContentParser parser = null;
        switch (_parserType)
        {
            case "text":
                parser = new ExcelTextContentParser();
                break;
            case "html":
                parser = new ExcelHtmlContentParser();
                break;
            default:
                break;
        }

        foreach (var item in clipboardContent)
        {
            if (parser.CanParse(item.Key) == false)
            {
                continue;
            }

            _excelContent = await parser.GetRows(item.Value);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

索引组件在方法中使用此事件回调 FillTable。该方法检查剪贴板中是否有一个元素。根据选择,选择解析器。如果所选解析器可以根据提供的 mime 类型解析每个表示,则在下一步中检查它。_excelContent 如果找到正确的解析器,解析器就会发挥其魔力,并更新字段的内容。因为是EventCallback StateHasChanged内部调用,并且视图更新。

文本解析器 在文本表示中,Excel 使用\r\n表示行尾,并使用 a\t表示每个单元格,甚至是空单元格。解析器逻辑非常简单。

public class ExcelTextContentParser : IExcelContentParser
{
    public String ValidMimeType { get; } = "text/plain";

    public Task<IList<String[]>> GetRows(String input) =>
        Task.FromResult<IList<String[]>>(input.Split("\r\n", StringSplitOptions.RemoveEmptyEntries).Select(x =>
            x.Split("\t").Select(y => y ?? String.Empty).ToArray()
        ).ToList());
}
Run Code Online (Sandbox Code Playgroud)

我还没有测试如果内容更复杂的话这种行为会如何变化。我认为 HTML 表示更稳定。因此,第二个解析器。

HTML 解析器

HTML 表示形式是一个表格。与<tr><td>. 我使用AngleSharp库作为 HTML 解析器。

public class ExcelHtmlContentParser : IExcelContentParser
{
    public String ValidMimeType { get; } = "text/html";

    public async Task<IList<String[]>> GetRows(String input)
    {
        var context = BrowsingContext.New(Configuration.Default);
        var document = await context.OpenAsync(reg => reg.Content(input));

        var element = document.QuerySelector<IHtmlTableElement>("table");
        var result = element.Rows.Select(x => x.Cells.Select(y => y.TextContent).ToArray()).ToList();
        return result;
    }
}
Run Code Online (Sandbox Code Playgroud)

我们将剪贴板内容作为 HTML 文档加载,获取表格并迭代所有行,并选择每一列。

** js 互操作 ***

@inject IJSRuntime runtime
@implements IDisposable

<span @ref="_reference">
    @ChildContent
</span>

@code {

    private ElementReference _reference;

    private DotNetObjectReference<PasteAwareComponent> _objectReference;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        await base.OnAfterRenderAsync(firstRender);
        if (firstRender == true)
        {
            _objectReference = DotNetObjectReference.Create(this);
            await runtime.InvokeVoidAsync("BlazorClipboadInterop.ListeningForPasteEvents", new Object[] { _reference, _objectReference });
        }
    }

    public void Dispose()
    {
        GC.SuppressFinalize(this);
        if (_objectReference != null)
        {
            _objectReference.Dispose();
        }
    }
}

Run Code Online (Sandbox Code Playgroud)

PasteAwareComponent组件覆盖OnAfterRenderAsync生命周期,以调用 js 互操作方法。一定是OnAfterRenderAsync因为之前 HTML 引用不存在,我们需要引用来添加粘贴事件侦听器。当粘贴事件发生时,JavaScript 必须调用这个对象,因此我们需要创建一个DotNetObjectReference实例。我们实现了IDisposable接口并正确处理了引用以防止内存泄漏。

最后一部分是 javascript 部分本身。我创建了一个名为 的文件clipboard-interop.js并将其放置在 wwwroot/js 文件夹中。

@page "/"
<div class="form-group">
    <label for="parser">Parser type</label>
    <select class="form-control" id="parser" @bind="_parserType">
        <option value="text">Text</option>
        <option value="html">HTML</option>
    </select>
</div>

<PasteAwareComponent OnContentPasted="FillTable">
    @if (_excelContent.Any() == false)
    {
        <p>No Content</p>
    }
    else
    {
        <table class="table table-striped">
            @foreach (var row in _excelContent)
            {
                <tr>
                    @foreach (var cell in row)
                    {
                        <td>@cell</td>
                    }
                </tr>
            }
        </table>
    }
</PasteAwareComponent>

<button type="button" class="btn btn-primary" @onclick="@( () => _excelContent = new List<String[]>() )">Clear</button>

@code
{
    private IList<String[]> _excelContent = new List<String[]>();

    ...more content, explained later...
}

Run Code Online (Sandbox Code Playgroud)

我们使用 HTML 引用来注册“粘贴”事件的事件侦听器。在处理方法中,我们创建传递给 C# 方法的对象。

<span @ref="_reference">
    @ChildContent
</span>

@code {
    private ElementReference _reference;

    [Parameter]
    public RenderFragment ChildContent { get; set; }

    [Parameter]
    public EventCallback<IEnumerable<IDictionary<String, String>>> OnContentPasted { get; set; }

    [JSInvokable("Pasted")]
    public async void raisePasteEvent(IEnumerable<IDictionary<String, String>> items)
    {
        await OnContentPasted.InvokeAsync(items);
    }

}
Run Code Online (Sandbox Code Playgroud)

当我们使用js互操作时,我们应该使用易于序列化的对象。对于真正的 blob(例如图像),它将是基于 64 编码的字符串,否则只是内容。

该解决方案使用了这些navigator.clipboard功能。用户需要允许它。因此我们看到了对话框。

请求访问剪贴板