Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ There are a significant number of controls in ASP.NET Web Forms, and we will foc
- [CheckBox](docs/EditorControls/CheckBox.md)
- [CheckBoxList](docs/EditorControls/CheckBoxList.md)
- [DropDownList](docs/EditorControls/DropDownList.md)
- FileUpload
- [FileUpload](docs/EditorControls/FileUpload.md)
- [HiddenField](docs/EditorControls/HiddenField.md)
- [Image](docs/EditorControls/Image.md)
- [ImageButton](docs/EditorControls/ImageButton.md)
Expand Down
195 changes: 195 additions & 0 deletions docs/EditorControls/FileUpload.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
# FileUpload

The **FileUpload** component allows users to select files from their local file system for upload to the server. It emulates the ASP.NET Web Forms FileUpload control with similar properties and behavior.

Original Microsoft documentation: https://docs.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.fileupload?view=netframework-4.8

## Features Supported in Blazor

- `HasFile` - indicates whether a file has been selected
- `FileName` - gets the name of the selected file
- `FileBytes` - gets the file contents as a byte array
- `FileContent` - gets a Stream to read the file data
- `PostedFile` - gets a wrapper object compatible with HttpPostedFile
- `AllowMultiple` - allows selection of multiple files
- `Accept` - specifies file type restrictions (e.g., "image/*", ".pdf,.doc")
- `MaxFileSize` - sets maximum allowed file size in bytes (default: 500KB)
- `SaveAs(path)` - saves the uploaded file to a specified server path
- `GetMultipleFiles()` - retrieves all selected files when AllowMultiple is true
- `SaveAllFiles(directory)` - saves all selected files to a directory
- `Enabled` - enables or disables the control
- `Visible` - controls visibility
- `ToolTip` - tooltip text on hover
- All style properties (`BackColor`, `ForeColor`, `BorderColor`, `BorderStyle`, `BorderWidth`, `CssClass`, `Width`, `Height`, `Font`)

### Blazor Notes

- The control renders as a standard HTML `<input type="file">` element
- File processing must be handled through component properties or methods
- The `OnFileSelected` event fires when files are selected
- Maximum file size should be configured based on your server's capabilities
- For Blazor WebAssembly, file data is read in the browser before being sent to the server

## Web Forms Features NOT Supported

- Direct postback behavior - use event handlers instead
- Automatic form submission - implement form handling in Blazor
- Server-side file system access in WebAssembly - must send to API endpoint

## Web Forms Declarative Syntax

```html
<asp:FileUpload
ID="FileUpload1"
AllowMultiple="True|False"
Enabled="True|False"
BackColor="color name|#dddddd"
BorderColor="color name|#dddddd"
BorderStyle="NotSet|None|Dotted|Dashed|Solid|Double|Groove|Ridge|Inset|Outset"
BorderWidth="size"
CssClass="string"
Height="size"
Width="size"
ToolTip="string"
Visible="True|False"
runat="server" />
```

## Blazor Syntax

```razor
<FileUpload @ref="myFileUpload"
Accept="image/*"
AllowMultiple="false"
MaxFileSize="1048576"
Width="Unit.Pixel(300)" />

<Button Text="Upload" OnClick="HandleUpload" />

@code {
FileUpload myFileUpload;

async Task HandleUpload()
{
if (myFileUpload.HasFile)
{
string fileName = myFileUpload.FileName;
await myFileUpload.SaveAs($"uploads/{fileName}");
}
}
}
```

## Common Usage Patterns

### Single File Upload

```razor
<FileUpload @ref="fileControl" Accept=".pdf" />
<Button Text="Upload PDF" OnClick="UploadPdf" />

@code {
FileUpload fileControl;

async Task UploadPdf()
{
if (fileControl.HasFile)
{
await fileControl.SaveAs($"documents/{fileControl.FileName}");
}
}
}
```

### Multiple File Upload

```razor
<FileUpload @ref="fileControl" AllowMultiple="true" />
<Button Text="Upload All" OnClick="UploadAll" />

@code {
FileUpload fileControl;

async Task UploadAll()
{
if (fileControl.HasFile)
{
var savedPaths = await fileControl.SaveAllFiles("uploads");
// Process savedPaths list
}
}
}
```

### Image Upload with Preview

```razor
<FileUpload @ref="imageUpload" Accept="image/*" MaxFileSize="5242880" />

@code {
FileUpload imageUpload;

void HandleImageSelected()
{
if (imageUpload.HasFile)
{
byte[] imageData = imageUpload.FileBytes;
// Process image data
}
}
}
```

## Security Considerations

- Always validate file types on the server side, not just through the `Accept` attribute
- Set appropriate `MaxFileSize` limits to prevent denial-of-service attacks
- Sanitize file names before saving to prevent directory traversal attacks
- Scan uploaded files for malware before processing
- Store uploaded files outside of the web root when possible
- Implement authentication and authorization for file upload endpoints

## Migration from Web Forms

When migrating from Web Forms FileUpload:

1. Replace `<asp:FileUpload>` with `<FileUpload>` (remove `asp:` prefix and `runat="server"`)
2. Replace `FileUpload1.HasFile` with direct property access (no change needed)
3. Replace `FileUpload1.SaveAs(Server.MapPath("~/path"))` with `await FileUpload1.SaveAs(path)`
4. Handle file uploads asynchronously using `async/await` pattern
5. Use `@ref` instead of `ID` to reference the component in code

### Before (Web Forms)

```aspx
<asp:FileUpload ID="FileUpload1" runat="server" />
<asp:Button ID="UploadButton" Text="Upload" OnClick="UploadButton_Click" runat="server" />

// Code-behind
protected void UploadButton_Click(object sender, EventArgs e)
{
if (FileUpload1.HasFile)
{
FileUpload1.SaveAs(Server.MapPath("~/uploads/" + FileUpload1.FileName));
}
}
```

### After (Blazor)

```razor
<FileUpload @ref="FileUpload1" />
<Button Text="Upload" OnClick="UploadButton_Click" />

@code {
FileUpload FileUpload1;

async Task UploadButton_Click()
{
if (FileUpload1.HasFile)
{
await FileUpload1.SaveAs($"uploads/{FileUpload1.FileName}");
}
}
}
```
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ nav:
- CheckBox: EditorControls/CheckBox.md
- CheckBoxList: EditorControls/CheckBoxList.md
- DropDownList: EditorControls/DropDownList.md
- FileUpload: EditorControls/FileUpload.md
- HiddenField: EditorControls/HiddenField.md
- Image: EditorControls/Image.md
- ImageButton: EditorControls/ImageButton.md
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
@page "/ControlSamples/FileUpload"
@using BlazorWebFormsComponents

<h1>FileUpload Examples</h1>

<h2>Basic Usage</h2>
<FileUpload @ref="_basicFileControl" />
<Button Text="Check Selection" OnClick="CheckBasicFile" />
@if (_basicStatusText != null) { <div>@_basicStatusText</div> }
<pre><code>&lt;FileUpload /&gt;</code></pre>

<hr />

<h2>Restrict to Images</h2>
<FileUpload @ref="_imageFileControl" Accept="image/*" />
<Button Text="Verify Image" OnClick="CheckImageFile" />
@if (_imageStatusText != null) { <div>@_imageStatusText</div> }
<pre><code>&lt;FileUpload Accept="image/*" /&gt;</code></pre>

<hr />

<h2>Allow Multiple Selection</h2>
<FileUpload @ref="_multiFileControl" AllowMultiple="true" Width="Unit.Pixel(320)" />
<Button Text="Count Files" OnClick="CheckMultiFiles" />
@if (_multiStatusText != null) { <div>@_multiStatusText</div> }
<pre><code>&lt;FileUpload AllowMultiple="true" /&gt;</code></pre>

<hr />

<h2>Inactive Control</h2>
<FileUpload Enabled="false" />
<pre><code>&lt;FileUpload Enabled="false" /&gt;</code></pre>

<hr />

<h2>Styled Control</h2>
<FileUpload Width="Unit.Pixel(380)" BackColor="WebColor.Lavender" />
<pre><code>&lt;FileUpload Width="Unit.Pixel(380)" BackColor="WebColor.Lavender" /&gt;</code></pre>

@code {
FileUpload _basicFileControl, _imageFileControl, _multiFileControl;
string _basicStatusText, _imageStatusText, _multiStatusText;

void CheckBasicFile() => _basicStatusText = "Basic file control clicked";
void CheckImageFile() => _imageStatusText = "Image control clicked";
void CheckMultiFiles() => _multiStatusText = "Multi-file control clicked";
}
28 changes: 28 additions & 0 deletions src/BlazorWebFormsComponents.Test/FileUpload/Accept.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
@inherits BlazorWebFormsTestContext
@using Shouldly

@code {

[Fact]
public void FileUpload_WithAccept_RendersAcceptAttribute()
{
// Arrange & Act
var cut = Render(@<FileUpload Accept=".jpg,.png" />);

// Assert
var input = cut.Find("input[type='file']");
input.GetAttribute("accept").ShouldBe(".jpg,.png");
}

[Fact]
public void FileUpload_WithImageAccept_RendersCorrectly()
{
// Arrange & Act
var cut = Render(@<FileUpload Accept="image/*" />);

// Assert
var input = cut.Find("input[type='file']");
input.GetAttribute("accept").ShouldBe("image/*");
}

}
28 changes: 28 additions & 0 deletions src/BlazorWebFormsComponents.Test/FileUpload/AllowMultiple.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
@inherits BlazorWebFormsTestContext
@using Shouldly

@code {

[Fact]
public void FileUpload_AllowMultiple_RendersMultipleAttribute()
{
// Arrange & Act
var cut = Render(@<FileUpload AllowMultiple="true" />);

// Assert
var input = cut.Find("input[type='file']");
input.HasAttribute("multiple").ShouldBeTrue();
}

[Fact]
public void FileUpload_AllowMultipleFalse_DoesNotRenderMultipleAttribute()
{
// Arrange & Act
var cut = Render(@<FileUpload AllowMultiple="false" />);

// Assert
var input = cut.Find("input[type='file']");
input.HasAttribute("multiple").ShouldBeFalse();
}

}
21 changes: 21 additions & 0 deletions src/BlazorWebFormsComponents.Test/FileUpload/Debug.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
@inherits BlazorWebFormsTestContext
@using Shouldly
@using System
@using Xunit.Abstractions

@code {

[Fact]
public void FileUpload_Debug_ShowsRenderedHtml()
{
// Arrange & Act
var cut = Render(@<FileUpload />);

// Assert - just checking the HTML structure
var markup = cut.Markup;

// The component should render something
markup.ShouldNotBeNullOrEmpty();
}

}
28 changes: 28 additions & 0 deletions src/BlazorWebFormsComponents.Test/FileUpload/Enabled.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
@inherits BlazorWebFormsTestContext
@using Shouldly

@code {

[Fact]
public void FileUpload_Enabled_RendersWithoutDisabled()
{
// Arrange & Act
var cut = Render(@<FileUpload Enabled="true" />);

// Assert
var input = cut.Find("input[type='file']");
input.HasAttribute("disabled").ShouldBeFalse();
}

[Fact]
public void FileUpload_Disabled_RendersWithDisabledAttribute()
{
// Arrange & Act
var cut = Render(@<FileUpload Enabled="false" />);

// Assert
var input = cut.Find("input[type='file']");
input.HasAttribute("disabled").ShouldBeTrue();
}

}
17 changes: 17 additions & 0 deletions src/BlazorWebFormsComponents.Test/FileUpload/Render.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
@inherits BlazorWebFormsTestContext
@using Shouldly

@code {

[Fact]
public void FileUpload_Render_RendersInputFile()
{
// Arrange & Act
var cut = Render(@<FileUpload />);

// Assert
var input = cut.Find("input[type='file']");
input.ShouldNotBeNull();
}

}
Loading
Loading