InputFile follow-up (#25248)
* Changes from API review * Feedback from security review * Update IBrowserFile.cs * Updated documentation. * Moved InputFile to M.A.Components.Forms * More changes * Move ProtectedBrowserStorage to it's own package * Mark Components.Web.Extensions as non-shipping until we can move it over * Allow InputFile.OpenReadStreamAsync to specify an expected file size. * Fix E2E tests * CR: Throw if user supplies too many files. * Another build fix * CR: Zero files is not a scenario * Update E2E tests * Update JS binaries after rebase * Update test Co-authored-by: Pranav K <prkrishn@hotmail.com> Co-authored-by: Steve Sanderson <SteveSandersonMS@users.noreply.github.com>
This commit is contained in:
parent
d3ebaf0782
commit
c2f97933fe
|
|
@ -1395,8 +1395,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Web.Extensions", "Web.Exten
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.Web.Extensions", "src\Components\Web.Extensions\src\Microsoft.AspNetCore.Components.Web.Extensions.csproj", "{8294A74F-7DAA-4B69-BC56-7634D93C9693}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.Web.Extensions", "src\Components\Web.Extensions\src\Microsoft.AspNetCore.Components.Web.Extensions.csproj", "{8294A74F-7DAA-4B69-BC56-7634D93C9693}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.Web.Extensions.Tests", "src\Components\Web.Extensions\test\Microsoft.AspNetCore.Components.Web.Extensions.Tests.csproj", "{157605CB-5170-4C1A-980F-4BAE42DB60DE}"
|
|
||||||
EndProject
|
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Sdk", "Sdk", "{FED4267E-E5E4-49C5-98DB-8B3F203596EE}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Sdk", "Sdk", "{FED4267E-E5E4-49C5-98DB-8B3F203596EE}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.NET.Sdk.BlazorWebAssembly", "src\Components\WebAssembly\Sdk\src\Microsoft.NET.Sdk.BlazorWebAssembly.csproj", "{6B2734BF-C61D-4889-ABBF-456A4075D59B}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.NET.Sdk.BlazorWebAssembly", "src\Components\WebAssembly\Sdk\src\Microsoft.NET.Sdk.BlazorWebAssembly.csproj", "{6B2734BF-C61D-4889-ABBF-456A4075D59B}"
|
||||||
|
|
@ -1489,6 +1487,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Diagno
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.JSInterop.Tests", "src\JSInterop\Microsoft.JSInterop\test\Microsoft.JSInterop.Tests.csproj", "{DAAB6B35-CBD2-4573-B633-CDD42F583A0E}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.JSInterop.Tests", "src\JSInterop\Microsoft.JSInterop\test\Microsoft.JSInterop.Tests.csproj", "{DAAB6B35-CBD2-4573-B633-CDD42F583A0E}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ProtectedBrowserStorage", "ProtectedBrowserStorage", "{1B06FD32-3A1D-46A4-B2AF-99159FAD8127}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Components.ProtectedBrowserStorage", "src\Components\ProtectedBrowserStorage\src\Microsoft.AspNetCore.Components.ProtectedBrowserStorage.csproj", "{9059AC97-7547-4CC1-A076-680CBCCC1F33}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Components.ProtectedBrowserStorage.Tests", "src\Components\ProtectedBrowserStorage\test\Microsoft.AspNetCore.Components.ProtectedBrowserStorage.Tests.csproj", "{943FD3EC-D330-4277-B3F3-3DFABB57D3B5}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
|
@ -6671,18 +6675,6 @@ Global
|
||||||
{8294A74F-7DAA-4B69-BC56-7634D93C9693}.Release|x64.Build.0 = Release|Any CPU
|
{8294A74F-7DAA-4B69-BC56-7634D93C9693}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{8294A74F-7DAA-4B69-BC56-7634D93C9693}.Release|x86.ActiveCfg = Release|Any CPU
|
{8294A74F-7DAA-4B69-BC56-7634D93C9693}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{8294A74F-7DAA-4B69-BC56-7634D93C9693}.Release|x86.Build.0 = Release|Any CPU
|
{8294A74F-7DAA-4B69-BC56-7634D93C9693}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{157605CB-5170-4C1A-980F-4BAE42DB60DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{157605CB-5170-4C1A-980F-4BAE42DB60DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{157605CB-5170-4C1A-980F-4BAE42DB60DE}.Debug|x64.ActiveCfg = Debug|Any CPU
|
|
||||||
{157605CB-5170-4C1A-980F-4BAE42DB60DE}.Debug|x64.Build.0 = Debug|Any CPU
|
|
||||||
{157605CB-5170-4C1A-980F-4BAE42DB60DE}.Debug|x86.ActiveCfg = Debug|Any CPU
|
|
||||||
{157605CB-5170-4C1A-980F-4BAE42DB60DE}.Debug|x86.Build.0 = Debug|Any CPU
|
|
||||||
{157605CB-5170-4C1A-980F-4BAE42DB60DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{157605CB-5170-4C1A-980F-4BAE42DB60DE}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{157605CB-5170-4C1A-980F-4BAE42DB60DE}.Release|x64.ActiveCfg = Release|Any CPU
|
|
||||||
{157605CB-5170-4C1A-980F-4BAE42DB60DE}.Release|x64.Build.0 = Release|Any CPU
|
|
||||||
{157605CB-5170-4C1A-980F-4BAE42DB60DE}.Release|x86.ActiveCfg = Release|Any CPU
|
|
||||||
{157605CB-5170-4C1A-980F-4BAE42DB60DE}.Release|x86.Build.0 = Release|Any CPU
|
|
||||||
{6B2734BF-C61D-4889-ABBF-456A4075D59B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{6B2734BF-C61D-4889-ABBF-456A4075D59B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{6B2734BF-C61D-4889-ABBF-456A4075D59B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{6B2734BF-C61D-4889-ABBF-456A4075D59B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{6B2734BF-C61D-4889-ABBF-456A4075D59B}.Debug|x64.ActiveCfg = Debug|Any CPU
|
{6B2734BF-C61D-4889-ABBF-456A4075D59B}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
|
@ -7113,6 +7105,30 @@ Global
|
||||||
{DAAB6B35-CBD2-4573-B633-CDD42F583A0E}.Release|x64.Build.0 = Release|Any CPU
|
{DAAB6B35-CBD2-4573-B633-CDD42F583A0E}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{DAAB6B35-CBD2-4573-B633-CDD42F583A0E}.Release|x86.ActiveCfg = Release|Any CPU
|
{DAAB6B35-CBD2-4573-B633-CDD42F583A0E}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{DAAB6B35-CBD2-4573-B633-CDD42F583A0E}.Release|x86.Build.0 = Release|Any CPU
|
{DAAB6B35-CBD2-4573-B633-CDD42F583A0E}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{9059AC97-7547-4CC1-A076-680CBCCC1F33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{9059AC97-7547-4CC1-A076-680CBCCC1F33}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{9059AC97-7547-4CC1-A076-680CBCCC1F33}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{9059AC97-7547-4CC1-A076-680CBCCC1F33}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{9059AC97-7547-4CC1-A076-680CBCCC1F33}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{9059AC97-7547-4CC1-A076-680CBCCC1F33}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{9059AC97-7547-4CC1-A076-680CBCCC1F33}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{9059AC97-7547-4CC1-A076-680CBCCC1F33}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{9059AC97-7547-4CC1-A076-680CBCCC1F33}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{9059AC97-7547-4CC1-A076-680CBCCC1F33}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{9059AC97-7547-4CC1-A076-680CBCCC1F33}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{9059AC97-7547-4CC1-A076-680CBCCC1F33}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{943FD3EC-D330-4277-B3F3-3DFABB57D3B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{943FD3EC-D330-4277-B3F3-3DFABB57D3B5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{943FD3EC-D330-4277-B3F3-3DFABB57D3B5}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{943FD3EC-D330-4277-B3F3-3DFABB57D3B5}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{943FD3EC-D330-4277-B3F3-3DFABB57D3B5}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{943FD3EC-D330-4277-B3F3-3DFABB57D3B5}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{943FD3EC-D330-4277-B3F3-3DFABB57D3B5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{943FD3EC-D330-4277-B3F3-3DFABB57D3B5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{943FD3EC-D330-4277-B3F3-3DFABB57D3B5}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{943FD3EC-D330-4277-B3F3-3DFABB57D3B5}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{943FD3EC-D330-4277-B3F3-3DFABB57D3B5}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{943FD3EC-D330-4277-B3F3-3DFABB57D3B5}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
@ -7814,7 +7830,6 @@ Global
|
||||||
{1542DC58-1836-4191-A9C5-51D1716D2543} = {05A169C7-4F20-4516-B10A-B13C5649D346}
|
{1542DC58-1836-4191-A9C5-51D1716D2543} = {05A169C7-4F20-4516-B10A-B13C5649D346}
|
||||||
{F71FE795-9923-461B-9809-BB1821A276D0} = {60D51C98-2CC0-40DF-B338-44154EFEE2FF}
|
{F71FE795-9923-461B-9809-BB1821A276D0} = {60D51C98-2CC0-40DF-B338-44154EFEE2FF}
|
||||||
{8294A74F-7DAA-4B69-BC56-7634D93C9693} = {F71FE795-9923-461B-9809-BB1821A276D0}
|
{8294A74F-7DAA-4B69-BC56-7634D93C9693} = {F71FE795-9923-461B-9809-BB1821A276D0}
|
||||||
{157605CB-5170-4C1A-980F-4BAE42DB60DE} = {F71FE795-9923-461B-9809-BB1821A276D0}
|
|
||||||
{FED4267E-E5E4-49C5-98DB-8B3F203596EE} = {562D5067-8CD8-4F19-BCBB-873204932C61}
|
{FED4267E-E5E4-49C5-98DB-8B3F203596EE} = {562D5067-8CD8-4F19-BCBB-873204932C61}
|
||||||
{6B2734BF-C61D-4889-ABBF-456A4075D59B} = {FED4267E-E5E4-49C5-98DB-8B3F203596EE}
|
{6B2734BF-C61D-4889-ABBF-456A4075D59B} = {FED4267E-E5E4-49C5-98DB-8B3F203596EE}
|
||||||
{83371889-9A3E-4D16-AE77-EB4F83BC6374} = {FED4267E-E5E4-49C5-98DB-8B3F203596EE}
|
{83371889-9A3E-4D16-AE77-EB4F83BC6374} = {FED4267E-E5E4-49C5-98DB-8B3F203596EE}
|
||||||
|
|
@ -7861,6 +7876,9 @@ Global
|
||||||
{55CACC1F-FE96-47C8-8073-91F4CAA55C75} = {2A91479A-4ABE-4BB7-9A5E-CA3B9CCFC69E}
|
{55CACC1F-FE96-47C8-8073-91F4CAA55C75} = {2A91479A-4ABE-4BB7-9A5E-CA3B9CCFC69E}
|
||||||
{7509AA1E-3093-4BEE-984F-E11579E98A11} = {7CB09412-C9B0-47E8-A8C3-311AA4CFDE04}
|
{7509AA1E-3093-4BEE-984F-E11579E98A11} = {7CB09412-C9B0-47E8-A8C3-311AA4CFDE04}
|
||||||
{DAAB6B35-CBD2-4573-B633-CDD42F583A0E} = {16898702-3E33-41C1-B8D8-4CE3F1D46BD9}
|
{DAAB6B35-CBD2-4573-B633-CDD42F583A0E} = {16898702-3E33-41C1-B8D8-4CE3F1D46BD9}
|
||||||
|
{1B06FD32-3A1D-46A4-B2AF-99159FAD8127} = {60D51C98-2CC0-40DF-B338-44154EFEE2FF}
|
||||||
|
{9059AC97-7547-4CC1-A076-680CBCCC1F33} = {1B06FD32-3A1D-46A4-B2AF-99159FAD8127}
|
||||||
|
{943FD3EC-D330-4277-B3F3-3DFABB57D3B5} = {1B06FD32-3A1D-46A4-B2AF-99159FAD8127}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {3E8720B3-DBDD-498C-B383-2CC32A054E8F}
|
SolutionGuid = {3E8720B3-DBDD-498C-B383-2CC32A054E8F}
|
||||||
|
|
|
||||||
|
|
@ -140,6 +140,7 @@
|
||||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Components" ProjectPath="$(RepoRoot)src\Components\Components\src\Microsoft.AspNetCore.Components.csproj" />
|
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Components" ProjectPath="$(RepoRoot)src\Components\Components\src\Microsoft.AspNetCore.Components.csproj" />
|
||||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Components.Forms" ProjectPath="$(RepoRoot)src\Components\Forms\src\Microsoft.AspNetCore.Components.Forms.csproj" />
|
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Components.Forms" ProjectPath="$(RepoRoot)src\Components\Forms\src\Microsoft.AspNetCore.Components.Forms.csproj" />
|
||||||
<ProjectReferenceProvider Include="Ignitor" ProjectPath="$(RepoRoot)src\Components\Ignitor\src\Ignitor.csproj" />
|
<ProjectReferenceProvider Include="Ignitor" ProjectPath="$(RepoRoot)src\Components\Ignitor\src\Ignitor.csproj" />
|
||||||
|
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Components.ProtectedBrowserStorage" ProjectPath="$(RepoRoot)src\Components\ProtectedBrowserStorage\src\Microsoft.AspNetCore.Components.ProtectedBrowserStorage.csproj" />
|
||||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Components.Server" ProjectPath="$(RepoRoot)src\Components\Server\src\Microsoft.AspNetCore.Components.Server.csproj" />
|
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Components.Server" ProjectPath="$(RepoRoot)src\Components\Server\src\Microsoft.AspNetCore.Components.Server.csproj" />
|
||||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Components.Web.Extensions" ProjectPath="$(RepoRoot)src\Components\Web.Extensions\src\Microsoft.AspNetCore.Components.Web.Extensions.csproj" />
|
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Components.Web.Extensions" ProjectPath="$(RepoRoot)src\Components\Web.Extensions\src\Microsoft.AspNetCore.Components.Web.Extensions.csproj" />
|
||||||
<ProjectReferenceProvider Include="Microsoft.Authentication.WebAssembly.Msal" ProjectPath="$(RepoRoot)src\Components\WebAssembly\Authentication.Msal\src\Microsoft.Authentication.WebAssembly.Msal.csproj" />
|
<ProjectReferenceProvider Include="Microsoft.Authentication.WebAssembly.Msal" ProjectPath="$(RepoRoot)src\Components\WebAssembly\Authentication.Msal\src\Microsoft.Authentication.WebAssembly.Msal.csproj" />
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,6 @@
|
||||||
"src\\Components\\benchmarkapps\\Wasm.Performance\\Driver\\Wasm.Performance.Driver.csproj",
|
"src\\Components\\benchmarkapps\\Wasm.Performance\\Driver\\Wasm.Performance.Driver.csproj",
|
||||||
"src\\Components\\benchmarkapps\\Wasm.Performance\\TestApp\\Wasm.Performance.TestApp.csproj",
|
"src\\Components\\benchmarkapps\\Wasm.Performance\\TestApp\\Wasm.Performance.TestApp.csproj",
|
||||||
"src\\Components\\Web.Extensions\\src\\Microsoft.AspNetCore.Components.Web.Extensions.csproj",
|
"src\\Components\\Web.Extensions\\src\\Microsoft.AspNetCore.Components.Web.Extensions.csproj",
|
||||||
"src\\Components\\Web.Extensions\\test\\Microsoft.AspNetCore.Components.Web.Extensions.Tests.csproj",
|
|
||||||
"src\\Components\\WebAssembly\\Server\\test\\Microsoft.AspNetCore.Components.WebAssembly.Server.Tests.csproj",
|
"src\\Components\\WebAssembly\\Server\\test\\Microsoft.AspNetCore.Components.WebAssembly.Server.Tests.csproj",
|
||||||
"src\\Components\\WebAssembly\\Authentication.Msal\\src\\Microsoft.Authentication.WebAssembly.Msal.csproj",
|
"src\\Components\\WebAssembly\\Authentication.Msal\\src\\Microsoft.Authentication.WebAssembly.Msal.csproj",
|
||||||
"src\\Components\\WebAssembly\\JSInterop\\src\\Microsoft.JSInterop.WebAssembly.csproj",
|
"src\\Components\\WebAssembly\\JSInterop\\src\\Microsoft.JSInterop.WebAssembly.csproj",
|
||||||
|
|
@ -112,7 +111,9 @@
|
||||||
"src\\Components\\WebAssembly\\Sdk\\src\\Microsoft.NET.Sdk.BlazorWebAssembly.csproj",
|
"src\\Components\\WebAssembly\\Sdk\\src\\Microsoft.NET.Sdk.BlazorWebAssembly.csproj",
|
||||||
"src\\Components\\WebAssembly\\Sdk\\test\\Microsoft.NET.Sdk.BlazorWebAssembly.Tests.csproj",
|
"src\\Components\\WebAssembly\\Sdk\\test\\Microsoft.NET.Sdk.BlazorWebAssembly.Tests.csproj",
|
||||||
"src\\Components\\WebAssembly\\Sdk\\integrationtests\\Microsoft.NET.Sdk.BlazorWebAssembly.IntegrationTests.csproj",
|
"src\\Components\\WebAssembly\\Sdk\\integrationtests\\Microsoft.NET.Sdk.BlazorWebAssembly.IntegrationTests.csproj",
|
||||||
"src\\JSInterop\\Microsoft.JSInterop\\src\\Microsoft.JSInterop.csproj"
|
"src\\JSInterop\\Microsoft.JSInterop\\src\\Microsoft.JSInterop.csproj",
|
||||||
|
"src\\Components\\ProtectedBrowserStorage\\src\\Microsoft.AspNetCore.Components.ProtectedBrowserStorage.csproj",
|
||||||
|
"src\\Components\\ProtectedBrowserStorage\\test\\Microsoft.AspNetCore.Components.ProtectedBrowserStorage.Tests.csproj"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,6 @@ using System.Runtime.CompilerServices;
|
||||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Components.Server.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Components.Server.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Components.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Components.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Components.Web.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Components.Web.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Components.Web.Extensions.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Components.ProtectedBrowserStorage.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||||
|
|
||||||
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
|
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@
|
||||||
"src\\Components\\Server\\src\\Microsoft.AspNetCore.Components.Server.csproj",
|
"src\\Components\\Server\\src\\Microsoft.AspNetCore.Components.Server.csproj",
|
||||||
"src\\Components\\Server\\test\\Microsoft.AspNetCore.Components.Server.Tests.csproj",
|
"src\\Components\\Server\\test\\Microsoft.AspNetCore.Components.Server.Tests.csproj",
|
||||||
"src\\Components\\Web.Extensions\\src\\Microsoft.AspNetCore.Components.Web.Extensions.csproj",
|
"src\\Components\\Web.Extensions\\src\\Microsoft.AspNetCore.Components.Web.Extensions.csproj",
|
||||||
"src\\Components\\Web.Extensions\\test\\Microsoft.AspNetCore.Components.Web.Extensions.Tests.csproj",
|
|
||||||
"src\\Components\\WebAssembly\\Authentication.Msal\\src\\Microsoft.Authentication.WebAssembly.Msal.csproj",
|
"src\\Components\\WebAssembly\\Authentication.Msal\\src\\Microsoft.Authentication.WebAssembly.Msal.csproj",
|
||||||
"src\\Components\\WebAssembly\\DevServer\\src\\Microsoft.AspNetCore.Components.WebAssembly.DevServer.csproj",
|
"src\\Components\\WebAssembly\\DevServer\\src\\Microsoft.AspNetCore.Components.WebAssembly.DevServer.csproj",
|
||||||
"src\\Components\\WebAssembly\\JSInterop\\src\\Microsoft.JSInterop.WebAssembly.csproj",
|
"src\\Components\\WebAssembly\\JSInterop\\src\\Microsoft.JSInterop.WebAssembly.csproj",
|
||||||
|
|
@ -46,7 +45,9 @@
|
||||||
"src\\Components\\test\\testassets\\BasicTestApp\\BasicTestApp.csproj",
|
"src\\Components\\test\\testassets\\BasicTestApp\\BasicTestApp.csproj",
|
||||||
"src\\Components\\test\\testassets\\ComponentsApp.Server\\ComponentsApp.Server.csproj",
|
"src\\Components\\test\\testassets\\ComponentsApp.Server\\ComponentsApp.Server.csproj",
|
||||||
"src\\Components\\test\\testassets\\TestContentPackage\\TestContentPackage.csproj",
|
"src\\Components\\test\\testassets\\TestContentPackage\\TestContentPackage.csproj",
|
||||||
"src\\Components\\test\\testassets\\TestServer\\Components.TestServer.csproj"
|
"src\\Components\\test\\testassets\\TestServer\\Components.TestServer.csproj",
|
||||||
|
"src\\Components\\ProtectedBrowserStorage\\src\\Microsoft.AspNetCore.Components.ProtectedBrowserStorage.csproj",
|
||||||
|
"src\\Components\\ProtectedBrowserStorage\\test\\Microsoft.AspNetCore.Components.ProtectedBrowserStorage.Tests.csproj"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
|
||||||
|
<Description>Provides functionality for storing protected data using the browser's localStorage and sessionStorage APIs.</Description>
|
||||||
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="Microsoft.AspNetCore.Components" />
|
||||||
|
<Reference Include="Microsoft.AspNetCore.DataProtection" />
|
||||||
|
<Reference Include="Microsoft.JSInterop" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="..\..\Shared\src\JsonSerializerOptionsProvider.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.Versioning;
|
||||||
|
|
||||||
|
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Components.ProtectedBrowserStorage.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||||
|
|
@ -10,7 +10,7 @@ using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.DataProtection;
|
using Microsoft.AspNetCore.DataProtection;
|
||||||
using Microsoft.JSInterop;
|
using Microsoft.JSInterop;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.Web.Extensions
|
namespace Microsoft.AspNetCore.Components.ProtectedBrowserStorage
|
||||||
{
|
{
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.Web.Extensions
|
namespace Microsoft.AspNetCore.Components.ProtectedBrowserStorage
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Contains the result of a protected browser storage operation.
|
/// Contains the result of a protected browser storage operation.
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Components.Web.Extensions;
|
using Microsoft.AspNetCore.Components.ProtectedBrowserStorage;
|
||||||
|
|
||||||
namespace Microsoft.Extensions.DependencyInjection
|
namespace Microsoft.Extensions.DependencyInjection
|
||||||
{
|
{
|
||||||
|
|
@ -5,7 +5,7 @@ using System.Runtime.Versioning;
|
||||||
using Microsoft.AspNetCore.DataProtection;
|
using Microsoft.AspNetCore.DataProtection;
|
||||||
using Microsoft.JSInterop;
|
using Microsoft.JSInterop;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.Web.Extensions
|
namespace Microsoft.AspNetCore.Components.ProtectedBrowserStorage
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides mechanisms for storing and retrieving data in the browser's
|
/// Provides mechanisms for storing and retrieving data in the browser's
|
||||||
|
|
@ -5,7 +5,7 @@ using System.Runtime.Versioning;
|
||||||
using Microsoft.AspNetCore.DataProtection;
|
using Microsoft.AspNetCore.DataProtection;
|
||||||
using Microsoft.JSInterop;
|
using Microsoft.JSInterop;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.Web.Extensions
|
namespace Microsoft.AspNetCore.Components.ProtectedBrowserStorage
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides mechanisms for storing and retrieving data in the browser's
|
/// Provides mechanisms for storing and retrieving data in the browser's
|
||||||
|
|
@ -2,12 +2,11 @@
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
|
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
|
||||||
<RootNamespace>Microsoft.AspNetCore.Components</RootNamespace>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="Microsoft.AspNetCore.Components" />
|
<Reference Include="Microsoft.AspNetCore.Components" />
|
||||||
<Reference Include="Microsoft.AspNetCore.Components.Web.Extensions" />
|
<Reference Include="Microsoft.AspNetCore.Components.ProtectedBrowserStorage" />
|
||||||
<Reference Include="Microsoft.AspNetCore.WebUtilities" />
|
<Reference Include="Microsoft.AspNetCore.WebUtilities" />
|
||||||
<Reference Include="Microsoft.Extensions.DependencyInjection" />
|
<Reference Include="Microsoft.Extensions.DependencyInjection" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
@ -14,7 +14,7 @@ using Microsoft.AspNetCore.WebUtilities;
|
||||||
using Microsoft.JSInterop;
|
using Microsoft.JSInterop;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.Web.Extensions
|
namespace Microsoft.AspNetCore.Components.ProtectedBrowserStorage
|
||||||
{
|
{
|
||||||
public class ProtectedBrowserStorageTest
|
public class ProtectedBrowserStorageTest
|
||||||
{
|
{
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.Web.Extensions
|
|
||||||
{
|
|
||||||
internal class BrowserFile : IBrowserFile
|
|
||||||
{
|
|
||||||
internal InputFile Owner { get; set; } = default!;
|
|
||||||
|
|
||||||
public int Id { get; set; }
|
|
||||||
|
|
||||||
public string Name { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
public DateTime LastModified { get; set; }
|
|
||||||
|
|
||||||
public long Size { get; set; }
|
|
||||||
|
|
||||||
public string Type { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
public string? RelativePath { get; set; }
|
|
||||||
|
|
||||||
public Stream OpenReadStream(CancellationToken cancellationToken = default)
|
|
||||||
=> Owner.OpenReadStream(this, cancellationToken);
|
|
||||||
|
|
||||||
public Task<IBrowserFile> ToImageFileAsync(string format, int maxWidth, int maxHeight)
|
|
||||||
=> Owner.ConvertToImageFileAsync(this, format, maxWidth, maxHeight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.Web.Extensions
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents the data of a file selected from an <see cref="InputFile"/> component.
|
|
||||||
/// </summary>
|
|
||||||
public interface IBrowserFile
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the name of the file.
|
|
||||||
/// </summary>
|
|
||||||
string Name { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the last modified date.
|
|
||||||
/// </summary>
|
|
||||||
DateTime LastModified { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the size of the file in bytes.
|
|
||||||
/// </summary>
|
|
||||||
long Size { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the MIME type of the file.
|
|
||||||
/// </summary>
|
|
||||||
string Type { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Opens the stream for reading the uploaded file.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="cancellationToken">A cancellation token to signal the cancellation of streaming file data.</param>
|
|
||||||
Stream OpenReadStream(CancellationToken cancellationToken = default);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Converts the current image file to a new one of the specified file type and maximum file dimensions.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// The image will be scaled to fit the specified dimensions while preserving the original aspect ratio.
|
|
||||||
/// </remarks>
|
|
||||||
/// <param name="format">The new image format.</param>
|
|
||||||
/// <param name="maxWith">The maximum image width.</param>
|
|
||||||
/// <param name="maxHeight">The maximum image height</param>
|
|
||||||
/// <returns>A <see cref="Task"/> representing the completion of the operation.</returns>
|
|
||||||
Task<IBrowserFile> ToImageFileAsync(string format, int maxWith, int maxHeight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.Web.Extensions
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Supplies information about an <see cref="InputFile.OnChange"/> event being raised.
|
|
||||||
/// </summary>
|
|
||||||
public class InputFileChangeEventArgs : EventArgs
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The updated file entries list.
|
|
||||||
/// </summary>
|
|
||||||
public IReadOnlyList<IBrowserFile> Files { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructs a new <see cref="InputFileChangeEventArgs"/> instance.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="files">The updated file entries list.</param>
|
|
||||||
public InputFileChangeEventArgs(IReadOnlyList<IBrowserFile> files)
|
|
||||||
{
|
|
||||||
Files = files;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -6,11 +6,11 @@
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<RootNamespace>Microsoft.AspNetCore.Components</RootNamespace>
|
<RootNamespace>Microsoft.AspNetCore.Components</RootNamespace>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
<IsShipping>false</IsShipping>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="Microsoft.AspNetCore.Components" />
|
<Reference Include="Microsoft.AspNetCore.Components" />
|
||||||
<Reference Include="Microsoft.AspNetCore.DataProtection" />
|
|
||||||
<Reference Include="Microsoft.JSInterop" />
|
<Reference Include="Microsoft.JSInterop" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,142 +0,0 @@
|
||||||
(function () {
|
|
||||||
|
|
||||||
// Exported functions
|
|
||||||
|
|
||||||
function init(callbackWrapper, elem) {
|
|
||||||
elem._blazorInputFileNextFileId = 0;
|
|
||||||
|
|
||||||
elem.addEventListener('click', function () {
|
|
||||||
// Permits replacing an existing file with a new one of the same file name.
|
|
||||||
elem.value = '';
|
|
||||||
});
|
|
||||||
|
|
||||||
elem.addEventListener('change', function () {
|
|
||||||
// Reduce to purely serializable data, plus an index by ID.
|
|
||||||
elem._blazorFilesById = {};
|
|
||||||
|
|
||||||
const fileList = Array.prototype.map.call(elem.files, function (file) {
|
|
||||||
const result = {
|
|
||||||
id: ++elem._blazorInputFileNextFileId,
|
|
||||||
lastModified: new Date(file.lastModified).toISOString(),
|
|
||||||
name: file.name,
|
|
||||||
size: file.size,
|
|
||||||
type: file.type,
|
|
||||||
};
|
|
||||||
|
|
||||||
elem._blazorFilesById[result.id] = result;
|
|
||||||
|
|
||||||
// Attach the blob data itself as a non-enumerable property so it doesn't appear in the JSON.
|
|
||||||
Object.defineProperty(result, 'blob', { value: file });
|
|
||||||
|
|
||||||
return result;
|
|
||||||
});
|
|
||||||
|
|
||||||
callbackWrapper.invokeMethodAsync('NotifyChange', fileList);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function toImageFile(elem, fileId, format, maxWidth, maxHeight) {
|
|
||||||
var originalFile = getFileById(elem, fileId);
|
|
||||||
|
|
||||||
return new Promise(function (resolve) {
|
|
||||||
var originalFileImage = new Image();
|
|
||||||
originalFileImage.onload = function () { resolve(originalFileImage); };
|
|
||||||
originalFileImage.src = URL.createObjectURL(originalFile.blob);
|
|
||||||
}).then(function (loadedImage) {
|
|
||||||
return new Promise(function (resolve) {
|
|
||||||
var desiredWidthRatio = Math.min(1, maxWidth / loadedImage.width);
|
|
||||||
var desiredHeightRatio = Math.min(1, maxHeight / loadedImage.height);
|
|
||||||
var chosenSizeRatio = Math.min(desiredWidthRatio, desiredHeightRatio);
|
|
||||||
|
|
||||||
var canvas = document.createElement('canvas');
|
|
||||||
canvas.width = Math.round(loadedImage.width * chosenSizeRatio);
|
|
||||||
canvas.height = Math.round(loadedImage.height * chosenSizeRatio);
|
|
||||||
canvas.getContext('2d').drawImage(loadedImage, 0, 0, canvas.width, canvas.height);
|
|
||||||
canvas.toBlob(resolve, format);
|
|
||||||
});
|
|
||||||
}).then(function (resizedImageBlob) {
|
|
||||||
var result = {
|
|
||||||
id: ++elem._blazorInputFileNextFileId,
|
|
||||||
lastModified: originalFile.lastModified,
|
|
||||||
name: originalFile.name, // Note: we're not changing the file extension.
|
|
||||||
size: resizedImageBlob.size,
|
|
||||||
type: format,
|
|
||||||
relativePath: originalFile.relativePath
|
|
||||||
};
|
|
||||||
|
|
||||||
elem._blazorFilesById[result.id] = result;
|
|
||||||
|
|
||||||
// Attach the blob data itself as a non-enumerable property so it doesn't appear in the JSON.
|
|
||||||
Object.defineProperty(result, 'blob', { value: resizedImageBlob });
|
|
||||||
|
|
||||||
return result;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensureArrayBufferReadyForSharedMemoryInterop(elem, fileId) {
|
|
||||||
return getArrayBufferFromFileAsync(elem, fileId).then(function (arrayBuffer) {
|
|
||||||
getFileById(elem, fileId).arrayBuffer = arrayBuffer;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function readFileData(elem, fileId, startOffset, count) {
|
|
||||||
return getArrayBufferFromFileAsync(elem, fileId).then(function (arrayBuffer) {
|
|
||||||
return btoa(String.fromCharCode.apply(null, new Uint8Array(arrayBuffer, startOffset, count)));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function readFileDataSharedMemory(readRequest) {
|
|
||||||
const inputFileElementReferenceId = Blazor.platform.readStringField(readRequest, 0);
|
|
||||||
const inputFileElement = document.querySelector(`[_bl_${inputFileElementReferenceId}]`);
|
|
||||||
const fileId = Blazor.platform.readInt32Field(readRequest, 4);
|
|
||||||
const sourceOffset = Blazor.platform.readUint64Field(readRequest, 8);
|
|
||||||
const destination = Blazor.platform.readInt32Field(readRequest, 16);
|
|
||||||
const destinationOffset = Blazor.platform.readInt32Field(readRequest, 20);
|
|
||||||
const maxBytes = Blazor.platform.readInt32Field(readRequest, 24);
|
|
||||||
|
|
||||||
const sourceArrayBuffer = getFileById(inputFileElement, fileId).arrayBuffer;
|
|
||||||
const bytesToRead = Math.min(maxBytes, sourceArrayBuffer.byteLength - sourceOffset);
|
|
||||||
const sourceUint8Array = new Uint8Array(sourceArrayBuffer, sourceOffset, bytesToRead);
|
|
||||||
|
|
||||||
const destinationUint8Array = Blazor.platform.toUint8Array(destination);
|
|
||||||
destinationUint8Array.set(sourceUint8Array, destinationOffset);
|
|
||||||
|
|
||||||
return bytesToRead;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Local helpers
|
|
||||||
|
|
||||||
function getFileById(elem, fileId) {
|
|
||||||
const file = elem._blazorFilesById[fileId];
|
|
||||||
|
|
||||||
if (!file) {
|
|
||||||
throw new Error(`There is no file with ID ${fileId}. The file list may have changed.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getArrayBufferFromFileAsync(elem, fileId) {
|
|
||||||
const file = getFileById(elem, fileId);
|
|
||||||
|
|
||||||
// On the first read, convert the FileReader into a Promise<ArrayBuffer>.
|
|
||||||
if (!file.readPromise) {
|
|
||||||
file.readPromise = new Promise(function (resolve, reject) {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = function () { resolve(reader.result); };
|
|
||||||
reader.onerror = function (err) { reject(err); };
|
|
||||||
reader.readAsArrayBuffer(file.blob);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return file.readPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
window._blazorInputFile = {
|
|
||||||
init,
|
|
||||||
toImageFile,
|
|
||||||
ensureArrayBufferReadyForSharedMemoryInterop,
|
|
||||||
readFileData,
|
|
||||||
readFileDataSharedMemory,
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -2,6 +2,7 @@ import { navigateTo, internalFunctions as navigationManagerInternalFunctions } f
|
||||||
import { attachRootComponentToElement } from './Rendering/Renderer';
|
import { attachRootComponentToElement } from './Rendering/Renderer';
|
||||||
import { domFunctions } from './DomWrapper';
|
import { domFunctions } from './DomWrapper';
|
||||||
import { Virtualize } from './Virtualize';
|
import { Virtualize } from './Virtualize';
|
||||||
|
import { InputFile } from './InputFile';
|
||||||
|
|
||||||
// Make the following APIs available in global scope for invocation from JS
|
// Make the following APIs available in global scope for invocation from JS
|
||||||
window['Blazor'] = {
|
window['Blazor'] = {
|
||||||
|
|
@ -11,5 +12,6 @@ window['Blazor'] = {
|
||||||
navigationManager: navigationManagerInternalFunctions,
|
navigationManager: navigationManagerInternalFunctions,
|
||||||
domWrapper: domFunctions,
|
domWrapper: domFunctions,
|
||||||
Virtualize,
|
Virtualize,
|
||||||
|
InputFile,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,159 @@
|
||||||
|
import { monoPlatform } from './Platform/Mono/MonoPlatform';
|
||||||
|
import { System_Array } from './Platform/Platform';
|
||||||
|
|
||||||
|
export const InputFile = {
|
||||||
|
init,
|
||||||
|
toImageFile,
|
||||||
|
ensureArrayBufferReadyForSharedMemoryInterop,
|
||||||
|
readFileData,
|
||||||
|
readFileDataSharedMemory,
|
||||||
|
};
|
||||||
|
|
||||||
|
interface BrowserFile {
|
||||||
|
id: number;
|
||||||
|
lastModified: string;
|
||||||
|
name: string;
|
||||||
|
size: number;
|
||||||
|
type: string;
|
||||||
|
readPromise: Promise<ArrayBuffer> | undefined;
|
||||||
|
arrayBuffer: ArrayBuffer | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface InputElement extends HTMLInputElement {
|
||||||
|
_blazorInputFileNextFileId: number;
|
||||||
|
_blazorFilesById: { [id: number]: BrowserFile };
|
||||||
|
}
|
||||||
|
|
||||||
|
function init(callbackWrapper: any, elem: InputElement): void {
|
||||||
|
elem._blazorInputFileNextFileId = 0;
|
||||||
|
|
||||||
|
elem.addEventListener('click', function(): void {
|
||||||
|
// Permits replacing an existing file with a new one of the same file name.
|
||||||
|
elem.value = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
elem.addEventListener('change', function(): void {
|
||||||
|
// Reduce to purely serializable data, plus an index by ID.
|
||||||
|
elem._blazorFilesById = {};
|
||||||
|
|
||||||
|
const fileList = Array.prototype.map.call(elem.files, function(file): BrowserFile {
|
||||||
|
const result = {
|
||||||
|
id: ++elem._blazorInputFileNextFileId,
|
||||||
|
lastModified: new Date(file.lastModified).toISOString(),
|
||||||
|
name: file.name,
|
||||||
|
size: file.size,
|
||||||
|
type: file.type,
|
||||||
|
readPromise: undefined,
|
||||||
|
arrayBuffer: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
elem._blazorFilesById[result.id] = result;
|
||||||
|
|
||||||
|
// Attach the blob data itself as a non-enumerable property so it doesn't appear in the JSON.
|
||||||
|
Object.defineProperty(result, 'blob', { value: file });
|
||||||
|
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
callbackWrapper.invokeMethodAsync('NotifyChange', fileList);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function toImageFile(elem: InputElement, fileId: number, format: string, maxWidth: number, maxHeight: number): Promise<BrowserFile> {
|
||||||
|
const originalFile = getFileById(elem, fileId);
|
||||||
|
|
||||||
|
const loadedImage = await new Promise(function(resolve: (loadedImage: HTMLImageElement) => void): void {
|
||||||
|
const originalFileImage = new Image();
|
||||||
|
originalFileImage.onload = function(): void {
|
||||||
|
resolve(originalFileImage);
|
||||||
|
};
|
||||||
|
originalFileImage.src = URL.createObjectURL(originalFile['blob']);
|
||||||
|
});
|
||||||
|
|
||||||
|
const resizedImageBlob = await new Promise(function(resolve: BlobCallback): void {
|
||||||
|
const desiredWidthRatio = Math.min(1, maxWidth / loadedImage.width);
|
||||||
|
const desiredHeightRatio = Math.min(1, maxHeight / loadedImage.height);
|
||||||
|
const chosenSizeRatio = Math.min(desiredWidthRatio, desiredHeightRatio);
|
||||||
|
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.width = Math.round(loadedImage.width * chosenSizeRatio);
|
||||||
|
canvas.height = Math.round(loadedImage.height * chosenSizeRatio);
|
||||||
|
canvas.getContext('2d')?.drawImage(loadedImage, 0, 0, canvas.width, canvas.height);
|
||||||
|
canvas.toBlob(resolve, format);
|
||||||
|
});
|
||||||
|
const result: BrowserFile = {
|
||||||
|
id: ++elem._blazorInputFileNextFileId,
|
||||||
|
lastModified: originalFile.lastModified,
|
||||||
|
name: originalFile.name,
|
||||||
|
size: resizedImageBlob?.size || 0,
|
||||||
|
type: format,
|
||||||
|
readPromise: undefined,
|
||||||
|
arrayBuffer: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
elem._blazorFilesById[result.id] = result;
|
||||||
|
|
||||||
|
// Attach the blob data itself as a non-enumerable property so it doesn't appear in the JSON.
|
||||||
|
Object.defineProperty(result, 'blob', { value: resizedImageBlob });
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureArrayBufferReadyForSharedMemoryInterop(elem: InputElement, fileId: number): Promise<void> {
|
||||||
|
const arrayBuffer = await getArrayBufferFromFileAsync(elem, fileId);
|
||||||
|
getFileById(elem, fileId).arrayBuffer = arrayBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readFileData(elem: InputElement, fileId: number, startOffset: number, count: number): Promise<string> {
|
||||||
|
const arrayBuffer = await getArrayBufferFromFileAsync(elem, fileId);
|
||||||
|
return btoa(String.fromCharCode.apply(null, new Uint8Array(arrayBuffer, startOffset, count) as unknown as number[]));
|
||||||
|
}
|
||||||
|
|
||||||
|
function readFileDataSharedMemory(readRequest: any): number {
|
||||||
|
const inputFileElementReferenceId = monoPlatform.readStringField(readRequest, 0);
|
||||||
|
const inputFileElement = document.querySelector(`[_bl_${inputFileElementReferenceId}]`);
|
||||||
|
const fileId = monoPlatform.readInt32Field(readRequest, 4);
|
||||||
|
const sourceOffset = monoPlatform.readUint64Field(readRequest, 8);
|
||||||
|
const destination = monoPlatform.readInt32Field(readRequest, 16) as unknown as System_Array<number>;
|
||||||
|
const destinationOffset = monoPlatform.readInt32Field(readRequest, 20);
|
||||||
|
const maxBytes = monoPlatform.readInt32Field(readRequest, 24);
|
||||||
|
|
||||||
|
const sourceArrayBuffer = getFileById(inputFileElement as InputElement, fileId).arrayBuffer as ArrayBuffer;
|
||||||
|
const bytesToRead = Math.min(maxBytes, sourceArrayBuffer.byteLength - sourceOffset);
|
||||||
|
const sourceUint8Array = new Uint8Array(sourceArrayBuffer, sourceOffset, bytesToRead);
|
||||||
|
|
||||||
|
const destinationUint8Array = monoPlatform.toUint8Array(destination);
|
||||||
|
destinationUint8Array.set(sourceUint8Array, destinationOffset);
|
||||||
|
|
||||||
|
return bytesToRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFileById(elem: InputElement, fileId: number): BrowserFile {
|
||||||
|
const file = elem._blazorFilesById[fileId];
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
throw new Error(`There is no file with ID ${fileId}. The file list may have changed.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getArrayBufferFromFileAsync(elem: InputElement, fileId: number): Promise<ArrayBuffer> {
|
||||||
|
const file = getFileById(elem, fileId);
|
||||||
|
|
||||||
|
// On the first read, convert the FileReader into a Promise<ArrayBuffer>.
|
||||||
|
if (!file.readPromise) {
|
||||||
|
file.readPromise = new Promise(function(resolve: (buffer: ArrayBuffer) => void, reject): void {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = function(): void {
|
||||||
|
resolve(reader.result as ArrayBuffer);
|
||||||
|
};
|
||||||
|
reader.onerror = function(err): void {
|
||||||
|
reject(err);
|
||||||
|
};
|
||||||
|
reader.readAsArrayBuffer(file['blob']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return file.readPromise;
|
||||||
|
}
|
||||||
|
|
@ -10,10 +10,10 @@ using Microsoft.AspNetCore.Components.Rendering;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Microsoft.JSInterop;
|
using Microsoft.JSInterop;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.Web.Extensions
|
namespace Microsoft.AspNetCore.Components.Forms
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A component that wraps the HTML file input element and exposes a <see cref="Stream"/> for each file's contents.
|
/// A component that wraps the HTML file input element and supplies a <see cref="Stream"/> for each file's contents.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class InputFile : ComponentBase, IInputFileJsCallbacks, IDisposable
|
public class InputFile : ComponentBase, IInputFileJsCallbacks, IDisposable
|
||||||
{
|
{
|
||||||
|
|
@ -69,7 +69,7 @@ namespace Microsoft.AspNetCore.Components.Web.Extensions
|
||||||
(Stream)new SharedBrowserFileStream(JSRuntime, _jsUnmarshalledRuntime, _inputFileElement, file) :
|
(Stream)new SharedBrowserFileStream(JSRuntime, _jsUnmarshalledRuntime, _inputFileElement, file) :
|
||||||
new RemoteBrowserFileStream(JSRuntime, _inputFileElement, file, Options.Value, cancellationToken);
|
new RemoteBrowserFileStream(JSRuntime, _inputFileElement, file, Options.Value, cancellationToken);
|
||||||
|
|
||||||
internal async Task<IBrowserFile> ConvertToImageFileAsync(BrowserFile file, string format, int maxWidth, int maxHeight)
|
internal async ValueTask<IBrowserFile> ConvertToImageFileAsync(BrowserFile file, string format, int maxWidth, int maxHeight)
|
||||||
{
|
{
|
||||||
var imageFile = await JSRuntime.InvokeAsync<BrowserFile>(InputFileInterop.ToImageFile, _inputFileElement, file.Id, format, maxWidth, maxHeight);
|
var imageFile = await JSRuntime.InvokeAsync<BrowserFile>(InputFileInterop.ToImageFile, _inputFileElement, file.Id, format, maxWidth, maxHeight);
|
||||||
|
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components.Forms
|
||||||
|
{
|
||||||
|
internal sealed class BrowserFile : IBrowserFile
|
||||||
|
{
|
||||||
|
private long _size;
|
||||||
|
|
||||||
|
internal InputFile Owner { get; set; } = default!;
|
||||||
|
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public DateTimeOffset LastModified { get; set; }
|
||||||
|
|
||||||
|
public long Size
|
||||||
|
{
|
||||||
|
get => _size;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value < 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(Size), $"Size must be a non-negative value. Value provided: {value}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_size = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ContentType { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string? RelativePath { get; set; }
|
||||||
|
|
||||||
|
public Stream OpenReadStream(long maxAllowedSize = 512000, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (Size > maxAllowedSize)
|
||||||
|
{
|
||||||
|
throw new IOException($"Supplied file with size {Size} bytes exceeds the maximum of {maxAllowedSize} bytes.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Owner.OpenReadStream(this, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components.Forms
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains helper methods for <see cref="IBrowserFile"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static class BrowserFileExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to convert the current image file to a new one of the specified file type and maximum file dimensions.
|
||||||
|
/// <para>
|
||||||
|
/// Caution: there is no guarantee that the file will be converted, or will even be a valid image file at all, either
|
||||||
|
/// before or after conversion. The conversion is requested within the browser before it is transferred to .NET
|
||||||
|
/// code, so the resulting data should be treated as untrusted.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The image will be scaled to fit the specified dimensions while preserving the original aspect ratio.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="browserFile">The <see cref="IBrowserFile"/> to convert to a new image file.</param>
|
||||||
|
/// <param name="format">The new image format.</param>
|
||||||
|
/// <param name="maxWith">The maximum image width.</param>
|
||||||
|
/// <param name="maxHeight">The maximum image height</param>
|
||||||
|
/// <returns>A <see cref="ValueTask"/> representing the completion of the operation.</returns>
|
||||||
|
public static ValueTask<IBrowserFile> RequestImageFileAsync(this IBrowserFile browserFile, string format, int maxWith, int maxHeight)
|
||||||
|
{
|
||||||
|
if (browserFile is BrowserFile browserFileInternal)
|
||||||
|
{
|
||||||
|
return browserFileInternal.Owner.ConvertToImageFileAsync(browserFileInternal, format, maxWith, maxHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidOperationException($"Cannot perform this operation on custom {typeof(IBrowserFile)} implementations.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,7 +6,7 @@ using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.Web.Extensions
|
namespace Microsoft.AspNetCore.Components.Forms
|
||||||
{
|
{
|
||||||
internal abstract class BrowserFileStream : Stream
|
internal abstract class BrowserFileStream : Stream
|
||||||
{
|
{
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components.Forms
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the data of a file selected from an <see cref="InputFile"/> component.
|
||||||
|
/// <para>
|
||||||
|
/// Note: Metadata is provided by the client and is untrusted.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
public interface IBrowserFile
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name of the file as specified by the browser.
|
||||||
|
/// </summary>
|
||||||
|
string Name { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the last modified date as specified by the browser.
|
||||||
|
/// </summary>
|
||||||
|
DateTimeOffset LastModified { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the size of the file in bytes as specified by the browser.
|
||||||
|
/// </summary>
|
||||||
|
long Size { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the MIME type of the file as specified by the browser.
|
||||||
|
/// </summary>
|
||||||
|
string ContentType { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Opens the stream for reading the uploaded file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="maxAllowedSize">
|
||||||
|
/// The maximum number of bytes that can be supplied by the Stream. Defaults to 500 KB.
|
||||||
|
/// <para>
|
||||||
|
/// Calling <see cref="OpenReadStream(long, CancellationToken)"/>
|
||||||
|
/// will throw if the file's size, as specified by <see cref="Size"/> is larger than
|
||||||
|
/// <paramref name="maxAllowedSize"/>. By default, if the user supplies a file larger than 500 KB, this method will throw an exception.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// It is valuable to choose a size limit that corresponds to your use case. If you allow excessively large files, this
|
||||||
|
/// may result in excessive consumption of memory or disk/database space, depending on what your code does
|
||||||
|
/// with the supplied <see cref="Stream"/>.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// For Blazor Server in particular, beware of reading the entire stream into a memory buffer unless you have
|
||||||
|
/// passed a suitably low size limit, since you will be consuming that memory on the server.
|
||||||
|
/// </para>
|
||||||
|
/// </param>
|
||||||
|
/// <param name="cancellationToken">A cancellation token to signal the cancellation of streaming file data.</param>
|
||||||
|
/// <exception cref="IOException">Thrown if the file's length exceeds the <paramref name="maxAllowedSize"/> value.</exception>
|
||||||
|
Stream OpenReadStream(long maxAllowedSize = 500 * 1024, CancellationToken cancellationToken = default);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.Web.Extensions
|
namespace Microsoft.AspNetCore.Components.Forms
|
||||||
{
|
{
|
||||||
internal interface IInputFileJsCallbacks
|
internal interface IInputFileJsCallbacks
|
||||||
{
|
{
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components.Forms
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Supplies information about an <see cref="InputFile.OnChange"/> event being raised.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class InputFileChangeEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
private readonly IReadOnlyList<IBrowserFile> _files;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs a new <see cref="InputFileChangeEventArgs"/> instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="files">The list of <see cref="IBrowserFile"/>.</param>
|
||||||
|
public InputFileChangeEventArgs(IReadOnlyList<IBrowserFile> files)
|
||||||
|
{
|
||||||
|
_files = files ?? throw new ArgumentNullException(nameof(files));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the number of supplied files.
|
||||||
|
/// </summary>
|
||||||
|
public int FileCount => _files.Count;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the supplied file. Note that if the input accepts multiple files, then instead of
|
||||||
|
/// reading this property, you should call <see cref="GetMultipleFiles(int)"/>.
|
||||||
|
/// </summary>
|
||||||
|
public IBrowserFile File => _files.Count switch
|
||||||
|
{
|
||||||
|
0 => throw new InvalidOperationException("No file was supplied."),
|
||||||
|
1 => _files[0],
|
||||||
|
_ => throw new InvalidOperationException($"More than one file was supplied. Call {nameof(GetMultipleFiles)} to receive multiple files."),
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the file entries list. This method should be used for inputs that accept multiple
|
||||||
|
/// files. If the input accepts only a single file, then use the <see cref="File"/> property
|
||||||
|
/// instead.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="maximumFileCount">The maximum number of files to accept. If the number of files exceeds this value, this method will throw an exception.</param>
|
||||||
|
public IReadOnlyList<IBrowserFile> GetMultipleFiles(int maximumFileCount = 10)
|
||||||
|
{
|
||||||
|
if (_files.Count > maximumFileCount)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"The maximum number of files accepted is {maximumFileCount}, but {_files.Count} were supplied.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return _files;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.Web.Extensions
|
namespace Microsoft.AspNetCore.Components.Forms
|
||||||
{
|
{
|
||||||
internal static class InputFileInterop
|
internal static class InputFileInterop
|
||||||
{
|
{
|
||||||
private const string JsFunctionsPrefix = "_blazorInputFile.";
|
private const string JsFunctionsPrefix = "Blazor._internal.InputFile.";
|
||||||
|
|
||||||
public const string Init = JsFunctionsPrefix + "init";
|
public const string Init = JsFunctionsPrefix + "init";
|
||||||
|
|
||||||
|
|
@ -5,7 +5,7 @@ using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.JSInterop;
|
using Microsoft.JSInterop;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.Web.Extensions
|
namespace Microsoft.AspNetCore.Components.Forms
|
||||||
{
|
{
|
||||||
internal class InputFileJsCallbacksRelay : IDisposable
|
internal class InputFileJsCallbacksRelay : IDisposable
|
||||||
{
|
{
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.Web.Extensions
|
namespace Microsoft.AspNetCore.Components.Forms
|
||||||
{
|
{
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
internal struct ReadRequest
|
internal struct ReadRequest
|
||||||
|
|
@ -8,7 +8,7 @@ using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.JSInterop;
|
using Microsoft.JSInterop;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.Web.Extensions
|
namespace Microsoft.AspNetCore.Components.Forms
|
||||||
{
|
{
|
||||||
internal class RemoteBrowserFileStream : BrowserFileStream
|
internal class RemoteBrowserFileStream : BrowserFileStream
|
||||||
{
|
{
|
||||||
|
|
@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Components.Web.Extensions
|
||||||
{
|
{
|
||||||
_jsRuntime = jsRuntime;
|
_jsRuntime = jsRuntime;
|
||||||
_inputFileElement = inputFileElement;
|
_inputFileElement = inputFileElement;
|
||||||
_maxSegmentSize = options.SegmentSize;
|
_maxSegmentSize = options.MaxSegmentSize;
|
||||||
_segmentFetchTimeout = options.SegmentFetchTimeout;
|
_segmentFetchTimeout = options.SegmentFetchTimeout;
|
||||||
|
|
||||||
var pipe = new Pipe(new PipeOptions(pauseWriterThreshold: options.MaxBufferSize, resumeWriterThreshold: options.MaxBufferSize));
|
var pipe = new Pipe(new PipeOptions(pauseWriterThreshold: options.MaxBufferSize, resumeWriterThreshold: options.MaxBufferSize));
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.Web.Extensions
|
namespace Microsoft.AspNetCore.Components.Forms
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Repesents configurable options for <see cref="RemoteBrowserFileStream"/>.
|
/// Repesents configurable options for <see cref="RemoteBrowserFileStream"/>.
|
||||||
|
|
@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Components.Web.Extensions
|
||||||
/// This only has an effect when using Blazor Server.
|
/// This only has an effect when using Blazor Server.
|
||||||
/// </para>
|
/// </para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int SegmentSize { get; set; } = 20 * 1024; // SignalR limit is 32K.
|
public int MaxSegmentSize { get; set; } = 20 * 1024; // SignalR limit is 32K.
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the maximum internal buffer size for unread data sent over a SignalR circuit.
|
/// Gets or sets the maximum internal buffer size for unread data sent over a SignalR circuit.
|
||||||
|
|
@ -35,6 +35,6 @@ namespace Microsoft.AspNetCore.Components.Web.Extensions
|
||||||
/// This only has an effect when using Blazor Server.
|
/// This only has an effect when using Blazor Server.
|
||||||
/// </para>
|
/// </para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TimeSpan SegmentFetchTimeout { get; set; } = TimeSpan.FromSeconds(3);
|
public TimeSpan SegmentFetchTimeout { get; set; } = TimeSpan.FromMinutes(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -7,7 +7,7 @@ using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.JSInterop;
|
using Microsoft.JSInterop;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.Web.Extensions
|
namespace Microsoft.AspNetCore.Components.Forms
|
||||||
{
|
{
|
||||||
internal class SharedBrowserFileStream : BrowserFileStream
|
internal class SharedBrowserFileStream : BrowserFileStream
|
||||||
{
|
{
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components.Forms
|
||||||
|
{
|
||||||
|
public class BrowserFileTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void SetSize_ThrowsIfSizeIsNegative()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var file = new BrowserFile();
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var ex = Assert.Throws<ArgumentOutOfRangeException>(() => file.Size = -7);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void OpenReadStream_ThrowsIfFileSizeIsLargerThanAllowedSize()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var file = new BrowserFile { Size = 100 };
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var ex = Assert.Throws<IOException>(() => file.OpenReadStream(80));
|
||||||
|
Assert.Equal("Supplied file with size 100 bytes exceeds the maximum of 80 bytes.", ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components.Forms
|
||||||
|
{
|
||||||
|
public class InputFileChangeEventArgsTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void SuppliesNumberOfFiles()
|
||||||
|
{
|
||||||
|
var emptySet = new InputFileChangeEventArgs(Array.Empty<IBrowserFile>());
|
||||||
|
Assert.Equal(0, emptySet.FileCount);
|
||||||
|
|
||||||
|
var twoItemSet = new InputFileChangeEventArgs(new[] { new BrowserFile(), new BrowserFile() });
|
||||||
|
Assert.Equal(2, twoItemSet.FileCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void File_CanSupplySingle()
|
||||||
|
{
|
||||||
|
var file = new BrowserFile();
|
||||||
|
var instance = new InputFileChangeEventArgs(new[] { file });
|
||||||
|
Assert.Same(file, instance.File);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void File_ThrowsIfZeroFiles()
|
||||||
|
{
|
||||||
|
var instance = new InputFileChangeEventArgs(Array.Empty<IBrowserFile>());
|
||||||
|
var ex = Assert.Throws<InvalidOperationException>(() => instance.File);
|
||||||
|
Assert.StartsWith("No file was supplied", ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void File_ThrowsIfMultipleFiles()
|
||||||
|
{
|
||||||
|
var instance = new InputFileChangeEventArgs(new[] { new BrowserFile(), new BrowserFile() });
|
||||||
|
var ex = Assert.Throws<InvalidOperationException>(() => instance.File);
|
||||||
|
Assert.StartsWith("More than one file was supplied", ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetMultipleFiles_CanSupplyEmpty()
|
||||||
|
{
|
||||||
|
var instance = new InputFileChangeEventArgs(Array.Empty<IBrowserFile>());
|
||||||
|
Assert.Empty(instance.GetMultipleFiles());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetMultipleFiles_CanSupplyFiles()
|
||||||
|
{
|
||||||
|
var files = new[] { new BrowserFile(), new BrowserFile() };
|
||||||
|
var instance = new InputFileChangeEventArgs(files);
|
||||||
|
Assert.Same(files, instance.GetMultipleFiles());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetMultipleFiles_ThrowsIfTooManyFiles()
|
||||||
|
{
|
||||||
|
var files = new[] { new BrowserFile(), new BrowserFile() };
|
||||||
|
var instance = new InputFileChangeEventArgs(files);
|
||||||
|
var ex = Assert.Throws<InvalidOperationException>(() => instance.GetMultipleFiles(1));
|
||||||
|
Assert.Equal($"The maximum number of files accepted is 1, but 2 were supplied.", ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -75,4 +75,12 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class ServerInputFileTest : InputFileTest
|
||||||
|
{
|
||||||
|
public ServerInputFileTest(BrowserFixture browserFixture, ToggleExecutionModeServerFixture<Program> serverFixture, ITestOutputHelper output)
|
||||||
|
: base(browserFixture, serverFixture.WithServerExecution(), output)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using BasicTestApp;
|
using BasicTestApp;
|
||||||
|
using BasicTestApp.FormsTest;
|
||||||
using Microsoft.AspNetCore.Components.E2ETest;
|
using Microsoft.AspNetCore.Components.E2ETest;
|
||||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
|
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
||||||
|
|
@ -15,7 +16,7 @@ using OpenQA.Selenium.Support.Extensions;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.E2ETests.Tests
|
namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
{
|
{
|
||||||
public class InputFileTest : ServerTestBase<ToggleExecutionModeServerFixture<Program>>, IDisposable
|
public class InputFileTest : ServerTestBase<ToggleExecutionModeServerFixture<Program>>, IDisposable
|
||||||
{
|
{
|
||||||
|
|
@ -139,6 +140,45 @@ namespace Microsoft.AspNetCore.Components.E2ETests.Tests
|
||||||
Browser.Equal(480, () => uploadedImage.Size.Height);
|
Browser.Equal(480, () => uploadedImage.Size.Height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ThrowsWhenTooManyFilesAreSelected()
|
||||||
|
{
|
||||||
|
var maxAllowedFilesElement = Browser.FindElement(By.Id("max-allowed-files"));
|
||||||
|
maxAllowedFilesElement.Clear();
|
||||||
|
maxAllowedFilesElement.SendKeys("1\n");
|
||||||
|
|
||||||
|
// Save two files locally
|
||||||
|
var file1 = TempFile.Create(_tempDirectory, "txt", "This is file 1.");
|
||||||
|
var file2 = TempFile.Create(_tempDirectory, "txt", "This is file 2.");
|
||||||
|
|
||||||
|
// Select both files
|
||||||
|
var inputFile = Browser.FindElement(By.Id("input-file"));
|
||||||
|
inputFile.SendKeys($"{file1.Path}\n{file2.Path}");
|
||||||
|
|
||||||
|
// Validate that the proper exception is thrown
|
||||||
|
var exceptionMessage = Browser.FindElement(By.Id("exception-message"));
|
||||||
|
Browser.Equal("The maximum number of files accepted is 1, but 2 were supplied.", () => exceptionMessage.Text);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ThrowsWhenOversizedFileIsSelected()
|
||||||
|
{
|
||||||
|
var maxFileSizeElement = Browser.FindElement(By.Id("max-file-size"));
|
||||||
|
maxFileSizeElement.Clear();
|
||||||
|
maxFileSizeElement.SendKeys("10\n");
|
||||||
|
|
||||||
|
// Save a file that exceeds the specified file size limit
|
||||||
|
var file = TempFile.Create(_tempDirectory, "txt", "This file is over 10 bytes long.");
|
||||||
|
|
||||||
|
// Select the file
|
||||||
|
var inputFile = Browser.FindElement(By.Id("input-file"));
|
||||||
|
inputFile.SendKeys(file.Path);
|
||||||
|
|
||||||
|
// Validate that the proper exception is thrown
|
||||||
|
var exceptionMessage = Browser.FindElement(By.Id("exception-message"));
|
||||||
|
Browser.Equal("Supplied file with size 32 bytes exceeds the maximum of 10 bytes.", () => exceptionMessage.Text);
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Directory.Delete(_tempDirectory, recursive: true);
|
Directory.Delete(_tempDirectory, recursive: true);
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
<Reference Include="System.Net.Http.Json" />
|
<Reference Include="System.Net.Http.Json" />
|
||||||
<Reference Include="Microsoft.AspNetCore.Components.WebAssembly" />
|
<Reference Include="Microsoft.AspNetCore.Components.WebAssembly" />
|
||||||
<Reference Include="Microsoft.AspNetCore.Components.Authorization" />
|
<Reference Include="Microsoft.AspNetCore.Components.Authorization" />
|
||||||
|
<Reference Include="Microsoft.AspNetCore.Components.ProtectedBrowserStorage" />
|
||||||
<Reference Include="Microsoft.AspNetCore.Components.Web.Extensions" />
|
<Reference Include="Microsoft.AspNetCore.Components.Web.Extensions" />
|
||||||
<Reference Include="Microsoft.Extensions.Logging.Configuration" />
|
<Reference Include="Microsoft.Extensions.Logging.Configuration" />
|
||||||
<Reference Include="Newtonsoft.Json" />
|
<Reference Include="Newtonsoft.Json" />
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,102 @@
|
||||||
|
@using System.IO;
|
||||||
|
@using Microsoft.AspNetCore.Components.Forms
|
||||||
|
|
||||||
|
<h1>File preview</h1>
|
||||||
|
|
||||||
|
Max file size:
|
||||||
|
<br />
|
||||||
|
<input type="number" id="max-file-size" @bind-value="@maxFileSize" />
|
||||||
|
<br />
|
||||||
|
|
||||||
|
Max allowed files:
|
||||||
|
<br />
|
||||||
|
<input type="number" id="max-allowed-files" @bind-value="@maxAllowedFiles" />
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<InputFile OnChange="LoadFiles" id="input-file" multiple />
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<span id="exception-message">@exceptionMessage</span>
|
||||||
|
|
||||||
|
@if (isLoading)
|
||||||
|
{
|
||||||
|
<p>Loading...</p>
|
||||||
|
<br />
|
||||||
|
}
|
||||||
|
|
||||||
|
@foreach (var (file, content) in loadedFiles)
|
||||||
|
{
|
||||||
|
<p id="file-@(file.Name)">
|
||||||
|
<strong>File name:</strong> @(file.Name)<br />
|
||||||
|
<strong>File size (bytes):</strong> <span id="file-size">@(file.Size)</span><br />
|
||||||
|
<strong>File content:</strong> <span id="file-content">@content</span><br />
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
<h1>Image upload</h1>
|
||||||
|
|
||||||
|
<InputFile OnChange="LoadImage" id="input-image" />
|
||||||
|
<br />
|
||||||
|
|
||||||
|
@if (imageDataUri != null)
|
||||||
|
{
|
||||||
|
<p>
|
||||||
|
Uploaded image:<br />
|
||||||
|
<img id="image-uploaded" src="@imageDataUri" />
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Source image:<br />
|
||||||
|
<img id="image-source" src="images/blazor_logo_1000x.png" />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
Dictionary<IBrowserFile, string> loadedFiles = new Dictionary<IBrowserFile, string>();
|
||||||
|
|
||||||
|
long maxFileSize = 1024 * 1024 * 15;
|
||||||
|
int maxAllowedFiles = 3;
|
||||||
|
|
||||||
|
bool isLoading;
|
||||||
|
|
||||||
|
string imageDataUri;
|
||||||
|
|
||||||
|
string exceptionMessage;
|
||||||
|
|
||||||
|
async Task LoadFiles(InputFileChangeEventArgs e)
|
||||||
|
{
|
||||||
|
isLoading = true;
|
||||||
|
loadedFiles.Clear();
|
||||||
|
exceptionMessage = string.Empty;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
|
||||||
|
{
|
||||||
|
StateHasChanged();
|
||||||
|
|
||||||
|
using var reader = new StreamReader(file.OpenReadStream(maxFileSize));
|
||||||
|
|
||||||
|
loadedFiles.Add(file, await reader.ReadToEndAsync());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
exceptionMessage = ex.Message;
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task LoadImage(InputFileChangeEventArgs e)
|
||||||
|
{
|
||||||
|
var format = "image/jpeg";
|
||||||
|
var imageFile = await e.File.RequestImageFileAsync(format, 640, 480);
|
||||||
|
|
||||||
|
using var fileStream = imageFile.OpenReadStream(maxFileSize);
|
||||||
|
using var memoryStream = new MemoryStream();
|
||||||
|
await fileStream.CopyToAsync(memoryStream);
|
||||||
|
|
||||||
|
imageDataUri = $"data:{format};base64,{Convert.ToBase64String(memoryStream.ToArray())}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -36,6 +36,7 @@
|
||||||
<option value="BasicTestApp.FormsTest.SimpleValidationComponentUsingExperimentalValidator">Simple validation using experimental validator</option>
|
<option value="BasicTestApp.FormsTest.SimpleValidationComponentUsingExperimentalValidator">Simple validation using experimental validator</option>
|
||||||
<option value="BasicTestApp.FormsTest.TypicalValidationComponent">Typical validation</option>
|
<option value="BasicTestApp.FormsTest.TypicalValidationComponent">Typical validation</option>
|
||||||
<option value="BasicTestApp.FormsTest.TypicalValidationComponentUsingExperimentalValidator">Typical validation using experimental validator</option>
|
<option value="BasicTestApp.FormsTest.TypicalValidationComponentUsingExperimentalValidator">Typical validation using experimental validator</option>
|
||||||
|
<option value="BasicTestApp.FormsTest.InputFileComponent">Input file</option>
|
||||||
<option value="BasicTestApp.NavigateOnSubmit">Navigate to submit</option>
|
<option value="BasicTestApp.NavigateOnSubmit">Navigate to submit</option>
|
||||||
<option value="BasicTestApp.GlobalizationBindCases">Globalization Bind Cases</option>
|
<option value="BasicTestApp.GlobalizationBindCases">Globalization Bind Cases</option>
|
||||||
<option value="BasicTestApp.HierarchicalImportsTest.Subdir.ComponentUsingImports">Imports statement</option>
|
<option value="BasicTestApp.HierarchicalImportsTest.Subdir.ComponentUsingImports">Imports statement</option>
|
||||||
|
|
@ -46,7 +47,6 @@
|
||||||
<option value="BasicTestApp.HttpClientTest.CookieCounterComponent">HttpClient cookies</option>
|
<option value="BasicTestApp.HttpClientTest.CookieCounterComponent">HttpClient cookies</option>
|
||||||
<option value="BasicTestApp.HttpClientTest.HttpRequestsComponent">HttpClient tester</option>
|
<option value="BasicTestApp.HttpClientTest.HttpRequestsComponent">HttpClient tester</option>
|
||||||
<option value="BasicTestApp.InputEventComponent">Input events</option>
|
<option value="BasicTestApp.InputEventComponent">Input events</option>
|
||||||
<option value="BasicTestApp.InputFileComponent">Input file</option>
|
|
||||||
<option value="BasicTestApp.InteropComponent">Interop component</option>
|
<option value="BasicTestApp.InteropComponent">Interop component</option>
|
||||||
<option value="BasicTestApp.InteropOnInitializationComponent">Interop on initialization</option>
|
<option value="BasicTestApp.InteropOnInitializationComponent">Interop on initialization</option>
|
||||||
<option value="BasicTestApp.JsonSerializationCases">JSON serialization</option>
|
<option value="BasicTestApp.JsonSerializationCases">JSON serialization</option>
|
||||||
|
|
|
||||||
|
|
@ -1,80 +0,0 @@
|
||||||
@using System.IO;
|
|
||||||
@using Microsoft.AspNetCore.Components.Web.Extensions
|
|
||||||
|
|
||||||
<h1>File preview</h1>
|
|
||||||
|
|
||||||
<InputFile OnChange="LoadFiles" id="input-file" multiple /><br />
|
|
||||||
|
|
||||||
@if (isLoading)
|
|
||||||
{
|
|
||||||
<p>Loading...</p><br />
|
|
||||||
}
|
|
||||||
|
|
||||||
@foreach (var (file, content) in loadedFiles)
|
|
||||||
{
|
|
||||||
<p id="file-@(file.Name)">
|
|
||||||
<strong>File name:</strong> @(file.Name)<br />
|
|
||||||
<strong>File size (bytes):</strong> <span id="file-size">@(file.Size)</span><br />
|
|
||||||
<strong>File content:</strong> <span id="file-content">@content</span><br />
|
|
||||||
</p>
|
|
||||||
}
|
|
||||||
|
|
||||||
<h1>Image upload</h1>
|
|
||||||
|
|
||||||
<InputFile OnChange="LoadImage" id="input-image" /><br />
|
|
||||||
|
|
||||||
@if (imageDataUri != null)
|
|
||||||
{
|
|
||||||
<p>
|
|
||||||
Uploaded image:<br />
|
|
||||||
<img id="image-uploaded" src="@imageDataUri" />
|
|
||||||
</p>
|
|
||||||
}
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Source image:<br />
|
|
||||||
<img id="image-source" src="images/blazor_logo_1000x.png" />
|
|
||||||
</p>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
Dictionary<IBrowserFile, string> loadedFiles = new Dictionary<IBrowserFile, string>();
|
|
||||||
|
|
||||||
bool isLoading;
|
|
||||||
|
|
||||||
string imageDataUri;
|
|
||||||
|
|
||||||
async Task LoadFiles(InputFileChangeEventArgs e)
|
|
||||||
{
|
|
||||||
isLoading = true;
|
|
||||||
loadedFiles.Clear();
|
|
||||||
|
|
||||||
foreach (var file in e.Files)
|
|
||||||
{
|
|
||||||
StateHasChanged();
|
|
||||||
|
|
||||||
using var reader = new StreamReader(file.OpenReadStream());
|
|
||||||
|
|
||||||
loadedFiles.Add(file, await reader.ReadToEndAsync());
|
|
||||||
}
|
|
||||||
|
|
||||||
isLoading = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task LoadImage(InputFileChangeEventArgs e)
|
|
||||||
{
|
|
||||||
var file = e.Files.SingleOrDefault();
|
|
||||||
|
|
||||||
if (file != null)
|
|
||||||
{
|
|
||||||
var format = "image/jpeg";
|
|
||||||
var imageFile = await file.ToImageFileAsync(format, 640, 480);
|
|
||||||
|
|
||||||
using var fileStream = imageFile.OpenReadStream();
|
|
||||||
using var memoryStream = new MemoryStream();
|
|
||||||
await fileStream.CopyToAsync(memoryStream);
|
|
||||||
|
|
||||||
imageDataUri = $"data:{format};base64,{Convert.ToBase64String(memoryStream.ToArray())}";
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -19,6 +19,7 @@ using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Logging.Configuration;
|
using Microsoft.Extensions.Logging.Configuration;
|
||||||
using Microsoft.JSInterop;
|
using Microsoft.JSInterop;
|
||||||
|
using Microsoft.AspNetCore.Components.ProtectedBrowserStorage;
|
||||||
|
|
||||||
namespace BasicTestApp
|
namespace BasicTestApp
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
@using Microsoft.Extensions.DependencyInjection
|
@using Microsoft.Extensions.DependencyInjection
|
||||||
@using Microsoft.AspNetCore.Components.Web.Extensions
|
@using Microsoft.AspNetCore.Components.Web.Extensions
|
||||||
|
@using Microsoft.AspNetCore.Components.ProtectedBrowserStorage
|
||||||
@inject IServiceProvider ServiceProvider
|
@inject IServiceProvider ServiceProvider
|
||||||
|
|
||||||
<button id="inject-local" @onclick="Inject<ProtectedLocalStorage>">Inject @(nameof(ProtectedLocalStorage))</button>
|
<button id="inject-local" @onclick="Inject<ProtectedLocalStorage>">Inject @(nameof(ProtectedLocalStorage))</button>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
@using Microsoft.AspNetCore.Components.Web.Extensions
|
@using Microsoft.AspNetCore.Components.Web.Extensions
|
||||||
|
@using Microsoft.AspNetCore.Components.ProtectedBrowserStorage
|
||||||
@inject ProtectedLocalStorage ProtectedLocalStore
|
@inject ProtectedLocalStorage ProtectedLocalStore
|
||||||
@inject ProtectedSessionStorage ProtectedSessionStore
|
@inject ProtectedSessionStorage ProtectedSessionStore
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,8 +45,6 @@
|
||||||
|
|
||||||
<script src="_content/Microsoft.AspNetCore.Components.Web.Extensions/headManager.js"></script>
|
<script src="_content/Microsoft.AspNetCore.Components.Web.Extensions/headManager.js"></script>
|
||||||
|
|
||||||
<script src="_content/Microsoft.AspNetCore.Components.Web.Extensions/inputFile.js"></script>
|
|
||||||
|
|
||||||
<!-- Used by ExternalContentPackage -->
|
<!-- Used by ExternalContentPackage -->
|
||||||
<script src="_content/TestContentPackage/prompt.js"></script>
|
<script src="_content/TestContentPackage/prompt.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue