Skip to content
This repository was archived by the owner on May 5, 2021. It is now read-only.

Commit 69c82ba

Browse files
Initial state
0 parents  commit 69c82ba

35 files changed

+1322
-0
lines changed

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.vs/
2+
bin/
3+
obj/
4+
*.user
5+
samples/Sample.WebAssembly/wwwroot/_content/

BlazorInputFile.sln

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 16
4+
VisualStudioVersion = 16.0.29230.61
5+
MinimumVisualStudioVersion = 15.0.26124.0
6+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorInputFile", "BlazorInputFile\BlazorInputFile.csproj", "{C3C06DC6-FC66-4266-B9B9-16F2F233B87D}"
7+
EndProject
8+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{EEEC40BE-3F2B-4B6E-9040-71FF3DC274AA}"
9+
EndProject
10+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.Server", "samples\Sample.Server\Sample.Server.csproj", "{BE1CED5B-9590-4170-BB54-822555E920CF}"
11+
EndProject
12+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample.WebAssembly", "samples\Sample.WebAssembly\Sample.WebAssembly.csproj", "{28B157AE-2F2D-4438-8493-3FE9148E442B}"
13+
EndProject
14+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample.Core", "samples\Sample.Core\Sample.Core.csproj", "{5D825DE4-4A0D-439F-A3BB-F16B33461F16}"
15+
EndProject
16+
Global
17+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
18+
Debug|Any CPU = Debug|Any CPU
19+
Debug|x64 = Debug|x64
20+
Debug|x86 = Debug|x86
21+
Release|Any CPU = Release|Any CPU
22+
Release|x64 = Release|x64
23+
Release|x86 = Release|x86
24+
EndGlobalSection
25+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
26+
{C3C06DC6-FC66-4266-B9B9-16F2F233B87D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27+
{C3C06DC6-FC66-4266-B9B9-16F2F233B87D}.Debug|Any CPU.Build.0 = Debug|Any CPU
28+
{C3C06DC6-FC66-4266-B9B9-16F2F233B87D}.Debug|x64.ActiveCfg = Debug|Any CPU
29+
{C3C06DC6-FC66-4266-B9B9-16F2F233B87D}.Debug|x64.Build.0 = Debug|Any CPU
30+
{C3C06DC6-FC66-4266-B9B9-16F2F233B87D}.Debug|x86.ActiveCfg = Debug|Any CPU
31+
{C3C06DC6-FC66-4266-B9B9-16F2F233B87D}.Debug|x86.Build.0 = Debug|Any CPU
32+
{C3C06DC6-FC66-4266-B9B9-16F2F233B87D}.Release|Any CPU.ActiveCfg = Release|Any CPU
33+
{C3C06DC6-FC66-4266-B9B9-16F2F233B87D}.Release|Any CPU.Build.0 = Release|Any CPU
34+
{C3C06DC6-FC66-4266-B9B9-16F2F233B87D}.Release|x64.ActiveCfg = Release|Any CPU
35+
{C3C06DC6-FC66-4266-B9B9-16F2F233B87D}.Release|x64.Build.0 = Release|Any CPU
36+
{C3C06DC6-FC66-4266-B9B9-16F2F233B87D}.Release|x86.ActiveCfg = Release|Any CPU
37+
{C3C06DC6-FC66-4266-B9B9-16F2F233B87D}.Release|x86.Build.0 = Release|Any CPU
38+
{BE1CED5B-9590-4170-BB54-822555E920CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
39+
{BE1CED5B-9590-4170-BB54-822555E920CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
40+
{BE1CED5B-9590-4170-BB54-822555E920CF}.Debug|x64.ActiveCfg = Debug|Any CPU
41+
{BE1CED5B-9590-4170-BB54-822555E920CF}.Debug|x64.Build.0 = Debug|Any CPU
42+
{BE1CED5B-9590-4170-BB54-822555E920CF}.Debug|x86.ActiveCfg = Debug|Any CPU
43+
{BE1CED5B-9590-4170-BB54-822555E920CF}.Debug|x86.Build.0 = Debug|Any CPU
44+
{BE1CED5B-9590-4170-BB54-822555E920CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
45+
{BE1CED5B-9590-4170-BB54-822555E920CF}.Release|Any CPU.Build.0 = Release|Any CPU
46+
{BE1CED5B-9590-4170-BB54-822555E920CF}.Release|x64.ActiveCfg = Release|Any CPU
47+
{BE1CED5B-9590-4170-BB54-822555E920CF}.Release|x64.Build.0 = Release|Any CPU
48+
{BE1CED5B-9590-4170-BB54-822555E920CF}.Release|x86.ActiveCfg = Release|Any CPU
49+
{BE1CED5B-9590-4170-BB54-822555E920CF}.Release|x86.Build.0 = Release|Any CPU
50+
{28B157AE-2F2D-4438-8493-3FE9148E442B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
51+
{28B157AE-2F2D-4438-8493-3FE9148E442B}.Debug|Any CPU.Build.0 = Debug|Any CPU
52+
{28B157AE-2F2D-4438-8493-3FE9148E442B}.Debug|x64.ActiveCfg = Debug|Any CPU
53+
{28B157AE-2F2D-4438-8493-3FE9148E442B}.Debug|x64.Build.0 = Debug|Any CPU
54+
{28B157AE-2F2D-4438-8493-3FE9148E442B}.Debug|x86.ActiveCfg = Debug|Any CPU
55+
{28B157AE-2F2D-4438-8493-3FE9148E442B}.Debug|x86.Build.0 = Debug|Any CPU
56+
{28B157AE-2F2D-4438-8493-3FE9148E442B}.Release|Any CPU.ActiveCfg = Release|Any CPU
57+
{28B157AE-2F2D-4438-8493-3FE9148E442B}.Release|Any CPU.Build.0 = Release|Any CPU
58+
{28B157AE-2F2D-4438-8493-3FE9148E442B}.Release|x64.ActiveCfg = Release|Any CPU
59+
{28B157AE-2F2D-4438-8493-3FE9148E442B}.Release|x64.Build.0 = Release|Any CPU
60+
{28B157AE-2F2D-4438-8493-3FE9148E442B}.Release|x86.ActiveCfg = Release|Any CPU
61+
{28B157AE-2F2D-4438-8493-3FE9148E442B}.Release|x86.Build.0 = Release|Any CPU
62+
{5D825DE4-4A0D-439F-A3BB-F16B33461F16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
63+
{5D825DE4-4A0D-439F-A3BB-F16B33461F16}.Debug|Any CPU.Build.0 = Debug|Any CPU
64+
{5D825DE4-4A0D-439F-A3BB-F16B33461F16}.Debug|x64.ActiveCfg = Debug|Any CPU
65+
{5D825DE4-4A0D-439F-A3BB-F16B33461F16}.Debug|x64.Build.0 = Debug|Any CPU
66+
{5D825DE4-4A0D-439F-A3BB-F16B33461F16}.Debug|x86.ActiveCfg = Debug|Any CPU
67+
{5D825DE4-4A0D-439F-A3BB-F16B33461F16}.Debug|x86.Build.0 = Debug|Any CPU
68+
{5D825DE4-4A0D-439F-A3BB-F16B33461F16}.Release|Any CPU.ActiveCfg = Release|Any CPU
69+
{5D825DE4-4A0D-439F-A3BB-F16B33461F16}.Release|Any CPU.Build.0 = Release|Any CPU
70+
{5D825DE4-4A0D-439F-A3BB-F16B33461F16}.Release|x64.ActiveCfg = Release|Any CPU
71+
{5D825DE4-4A0D-439F-A3BB-F16B33461F16}.Release|x64.Build.0 = Release|Any CPU
72+
{5D825DE4-4A0D-439F-A3BB-F16B33461F16}.Release|x86.ActiveCfg = Release|Any CPU
73+
{5D825DE4-4A0D-439F-A3BB-F16B33461F16}.Release|x86.Build.0 = Release|Any CPU
74+
EndGlobalSection
75+
GlobalSection(SolutionProperties) = preSolution
76+
HideSolutionNode = FALSE
77+
EndGlobalSection
78+
GlobalSection(NestedProjects) = preSolution
79+
{BE1CED5B-9590-4170-BB54-822555E920CF} = {EEEC40BE-3F2B-4B6E-9040-71FF3DC274AA}
80+
{28B157AE-2F2D-4438-8493-3FE9148E442B} = {EEEC40BE-3F2B-4B6E-9040-71FF3DC274AA}
81+
{5D825DE4-4A0D-439F-A3BB-F16B33461F16} = {EEEC40BE-3F2B-4B6E-9040-71FF3DC274AA}
82+
EndGlobalSection
83+
GlobalSection(ExtensibilityGlobals) = postSolution
84+
SolutionGuid = {843608D9-10E0-41C6-9955-83621659FFE5}
85+
EndGlobalSection
86+
EndGlobal
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Razor">
2+
3+
<PropertyGroup>
4+
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
5+
<RazorLangVersion>3.0</RazorLangVersion>
6+
<LangVersion>preview</LangVersion>
7+
<PackageVersion>0.1.0-preview</PackageVersion>
8+
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<PackageReference Include="Microsoft.AspNetCore.Components" Version="$(ServerPackagesVersion)" />
13+
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="$(ServerPackagesVersion)" />
14+
</ItemGroup>
15+
16+
</Project>
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using System;
2+
using System.IO;
3+
4+
namespace BlazorInputFile
5+
{
6+
// This is public only because it's used in a JSInterop method signature,
7+
// but otherwise is intended as internal
8+
public class FileListEntryImpl : IFileListEntry
9+
{
10+
internal InputFile Owner { get; set; }
11+
12+
private Stream _stream;
13+
14+
public event EventHandler OnDataRead;
15+
16+
public int Id { get; set; }
17+
18+
public DateTime LastModified { get; set; }
19+
20+
public string Name { get; set; }
21+
22+
public long Size { get; set; }
23+
24+
public string Type { get; set; }
25+
26+
public Stream Data
27+
{
28+
get
29+
{
30+
_stream ??= Owner.OpenFileStream(this);
31+
return _stream;
32+
}
33+
}
34+
35+
internal void RaiseOnDataRead()
36+
{
37+
OnDataRead?.Invoke(this, null);
38+
}
39+
}
40+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
using Microsoft.AspNetCore.Components;
2+
using Microsoft.JSInterop;
3+
using System;
4+
using System.IO;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
8+
namespace BlazorInputFile
9+
{
10+
// TODO: When ReadAsync is called, don't just fetch the segment of data that's being requested.
11+
// That will be very slow, as you may be doing a separate round-trip for each 1KB or so of data.
12+
// Instead, have a larger buffer whose size == SignalR.MaxMessageSize and populate that. Then
13+
// many of the ReadAsync calls can return immediately with already-loaded data.
14+
//
15+
// This is still not as fast as allowing the client to send as much data as it wants, and using
16+
// TCP to apply backpressure. In the future we could achieve something closer to that by having
17+
// an even larger buffer, and telling the client to send N messages in parallel. The ReadAsync
18+
// calls would return whenever their portion of the buffer was populated. This is much more
19+
// complicated to implement.
20+
21+
public abstract class FileListEntryStream : Stream
22+
{
23+
protected readonly IJSRuntime _jsRuntime;
24+
protected readonly ElementReference _inputFileElement;
25+
protected readonly FileListEntryImpl _file;
26+
private long _position;
27+
28+
public FileListEntryStream(IJSRuntime jsRuntime, ElementReference inputFileElement, FileListEntryImpl file)
29+
{
30+
_jsRuntime = jsRuntime;
31+
_inputFileElement = inputFileElement;
32+
_file = file;
33+
}
34+
35+
public override bool CanRead => true;
36+
37+
public override bool CanSeek => false;
38+
39+
public override bool CanWrite => false;
40+
41+
public override long Length => _file.Size;
42+
43+
public override long Position
44+
{
45+
get => _position;
46+
set => throw new NotSupportedException();
47+
}
48+
49+
public override void Flush()
50+
=> throw new NotSupportedException();
51+
52+
public override int Read(byte[] buffer, int offset, int count)
53+
=> throw new NotSupportedException("Synchronous reads are not supported");
54+
55+
public override long Seek(long offset, SeekOrigin origin)
56+
=> throw new NotSupportedException();
57+
58+
public override void SetLength(long value)
59+
=> throw new NotSupportedException();
60+
61+
public override void Write(byte[] buffer, int offset, int count)
62+
=> throw new NotSupportedException();
63+
64+
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
65+
{
66+
var maxBytesToRead = (int)Math.Min(count, Length - Position);
67+
if (maxBytesToRead == 0)
68+
{
69+
return 0;
70+
}
71+
72+
var actualBytesRead = await CopyFileDataIntoBuffer(_position, buffer, offset, maxBytesToRead, cancellationToken);
73+
_position += actualBytesRead;
74+
_file.RaiseOnDataRead();
75+
return actualBytesRead;
76+
}
77+
78+
protected abstract Task<int> CopyFileDataIntoBuffer(long sourceOffset, byte[] destination, int destinationOffset, int maxBytes, CancellationToken cancellationToken);
79+
}
80+
}

BlazorInputFile/IFileListEntry.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System;
2+
using System.IO;
3+
4+
namespace BlazorInputFile
5+
{
6+
public interface IFileListEntry
7+
{
8+
DateTime LastModified { get; }
9+
10+
string Name { get; }
11+
12+
long Size { get; }
13+
14+
string Type { get; }
15+
16+
Stream Data { get; }
17+
18+
event EventHandler OnDataRead;
19+
}
20+
}

BlazorInputFile/InputFile.razor

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
@implements IDisposable
2+
@inject IJSRuntime JSRuntime
3+
4+
<input type="file" @ref="inputFileElement" @attributes="UnmatchedParameters" />
5+
6+
@code {
7+
[Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object> UnmatchedParameters { get; set; }
8+
[Parameter] public EventCallback<IFileListEntry[]> OnChange { get; set; }
9+
[Parameter] public int MaxMessageSize { get; set; } = 20 * 1024; // TODO: Use SignalR default
10+
[Parameter] public int MaxBufferSize { get; set; } = 1024 * 1024;
11+
12+
ElementReference inputFileElement;
13+
IDisposable thisReference;
14+
15+
[JSInvokable]
16+
public Task NotifyChange(FileListEntryImpl[] files)
17+
{
18+
foreach (var file in files)
19+
{
20+
// So that method invocations on the file can be dispatched back here
21+
file.Owner = (InputFile)(object)this;
22+
}
23+
24+
return OnChange.InvokeAsync(files);
25+
}
26+
27+
protected override async Task OnAfterRenderAsync(bool firstRender)
28+
{
29+
if (firstRender)
30+
{
31+
thisReference = DotNetObjectReference.Create(this);
32+
await JSRuntime.InvokeAsync<object>("BlazorInputFile.init", inputFileElement, thisReference);
33+
}
34+
}
35+
36+
internal Stream OpenFileStream(FileListEntryImpl file)
37+
{
38+
return SharedMemoryFileListEntryStream.IsSupported(JSRuntime)
39+
? (Stream)new SharedMemoryFileListEntryStream(JSRuntime, inputFileElement, file)
40+
: new RemoteFileListEntryStream(JSRuntime, inputFileElement, file, MaxMessageSize, MaxBufferSize);
41+
}
42+
43+
void IDisposable.Dispose()
44+
{
45+
thisReference?.Dispose();
46+
}
47+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Threading;
4+
5+
namespace BlazorInputFile
6+
{
7+
internal class PreFetchingSequence<T>
8+
{
9+
private readonly Func<long, CancellationToken, T> _fetchCallback;
10+
private readonly int _maxBufferCapacity;
11+
private readonly long _totalFetchableItems;
12+
private readonly Queue<T> _buffer;
13+
private long _maxFetchedIndex;
14+
15+
public PreFetchingSequence(Func<long, CancellationToken, T> fetchCallback, long totalFetchableItems, int maxBufferCapacity)
16+
{
17+
_fetchCallback = fetchCallback;
18+
_buffer = new Queue<T>();
19+
_maxBufferCapacity = maxBufferCapacity;
20+
_totalFetchableItems = totalFetchableItems;
21+
}
22+
23+
public T ReadNext(CancellationToken cancellationToken)
24+
{
25+
EnqueueFetches(cancellationToken);
26+
if (_buffer.Count == 0)
27+
{
28+
throw new InvalidOperationException("There are no more entries to read");
29+
}
30+
31+
var next = _buffer.Dequeue();
32+
EnqueueFetches(cancellationToken);
33+
return next;
34+
}
35+
36+
public bool TryPeekNext(out T result)
37+
{
38+
if (_buffer.Count > 0)
39+
{
40+
result = _buffer.Peek();
41+
return true;
42+
}
43+
else
44+
{
45+
result = default;
46+
return false;
47+
}
48+
}
49+
50+
private void EnqueueFetches(CancellationToken cancellationToken)
51+
{
52+
while (_buffer.Count < _maxBufferCapacity && _maxFetchedIndex < _totalFetchableItems)
53+
{
54+
_buffer.Enqueue(_fetchCallback(_maxFetchedIndex++, cancellationToken));
55+
}
56+
}
57+
}
58+
}

0 commit comments

Comments
 (0)