Upload action of InputFile submits parent EditForm
Hi Steve, first, thank you for this great component!
I wrapped InputFile in a reusable component CustomInputFile that supports upload/download/delete functionality by calling methods on a back-end FileController, and notifies the parent component about changes through OnFileUploaded EventCallback.
@inject HttpClient HttpClient
@using System.IO
@using BlazorInputFile
@using TuServicioWebsite.Shared.Utils
@inject NavigationManager UriHelper
<InputFile OnChange="HandleFileSelected" UnmatchedParameters="UnmatchedParameters" />
@if (selectedFile != null)
{
isLoading = selectedFile.Data.Position > 0;
<div class="file-row">
<div>
<h2>@selectedFile.Name</h2>
Size: <strong>@savedFileSize</strong>;
Last update: <strong>@selectedFile.LastModified.ToShortDateString()</strong>;
Type: <strong>@selectedFile.Type</strong>
</div>
<!-- Upload button -->
<button @onclick="() => LoadFile(selectedFile)" disabled="@isLoading">
@if (!isLoading)
{
<span>Subir</span>
}
else
{
<span>Loaded @((100.0 * selectedFile.Data.Position / selectedFile.Size).ToString("0"))%</span>
}
</button>
@if (isLoading && (savedFileId == Guid.Empty))
{
<button @onclick="() => ClearInputFileFields()">Delete</button>
}
@if (savedFileId != Guid.Empty)
{
<button @onclick="() => DownloadFile()">Download</button>
<button @onclick="() => DeleteFile()">Delete</button>
}
</div>
}
@code {
IFileListEntry selectedFile;
bool isLoading;
[Parameter] public EventCallback<Tuple<Guid, string, string, string>> OnFileUploaded { get; set; }
[Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object> UnmatchedParameters { get; set; }
Guid savedFileId { get; set; } = Guid.Empty;
string savedFileName { get; set; } = "";
string savedFileType { get; set; } = "";
string savedFileSize { get; set; } = "";
void HandleFileSelected(IFileListEntry[] files)
{
selectedFile = files.FirstOrDefault();
}
async Task LoadFile(IFileListEntry file)
{
// So the UI updates to show progress
file.OnDataRead += (sender, eventArgs) => InvokeAsync(StateHasChanged);
// Load content into .NET memory stream
// Alternatively it could be saved to disk, or parsed in memory, or similar
var ms = new MemoryStream();
await file.Data.CopyToAsync(ms);
//Send file content to back-end
var content = new MultipartFormDataContent {
{ new ByteArrayContent(ms.GetBuffer()), "\"upload\"", file.Name }
};
HttpResponseMessage response = await HttpClient.PostAsync("api/File/Upload", content);
//Notify parent component
string guidText = await response.Content.ReadAsStringAsync();
savedFileId = new Guid(guidText);
savedFileName = file.Name;
savedFileSize = UiHelper.ConvertSizeInBytesToHumanReadableDescription(file.Size);
savedFileType = file.Type;
await OnFileUploaded.InvokeAsync(new Tuple<Guid, string, string, string>(savedFileId, savedFileName, savedFileType, savedFileSize));
}
async void DownloadFile()
{
if (savedFileId != Guid.Empty)
UriHelper.NavigateTo($"api/File/Download/{savedFileId}", forceLoad: true);
}
async void DeleteFile()
{
if (savedFileId != Guid.Empty)
{
await HttpClient.PostJsonAsync($"api/File/Delete/{savedFileId}", savedFileId);
await ClearInputFileFields();
}
}
async Task ClearInputFileFields()
{
savedFileId = Guid.Empty;
savedFileName = "";
savedFileType = "";
selectedFile = null;
isLoading = false;
await OnFileUploaded.InvokeAsync(new Tuple<Guid, string, string, string>(savedFileId, savedFileName, savedFileType, savedFileSize));
}
}
When using it in EditForm of the parent .razor page, on finished upload, EditForm is also submitted. Here is the code of parent component:
<EditForm id="editFormDocumentation" Model="@pd" OnValidSubmit="@(async () => await CreateProjectDataModel())">
<DataAnnotationsValidator />
<ValidationSummary />
<div class="form-group row">
<label for="projectCode" class="col-form-label col-sm-4">Project code</label>
<InputText id="projectCode" class="form-control col-sm-8" @bind-Value="@pd.ProjectCode" />
<ValidationMessage For="@(() => pd.ProjectCode)" />
</div>
<div class="form-group row">
<label for="projectName" class="col-form-label col-sm-4">Project name</label>
<InputText id="projectName" class="form-control col-sm-8" @bind-Value="@pd.ProjectName" />
<ValidationMessage For="@(() => pd.ProjectName)" />
</div>
<div class="form-group row">
<label for="dbc" class="col-sm-4 col-form-label">DBC file</label>
<div id="dbc" class="col-sm-8">
<CustomInputFile OnFileUploaded="@((args) => {
pd.DbcFileId = args.Item1;
pd.DbcFileName = args.Item2;
StateHasChanged();
})" />
<ValidationMessage For="@(() => pd.DbcFileId)" />
</div>
</div>
<div class="form-group row">
<label for="terms" class="col-sm-4 col-form-label">Reference terms</label>
<div id="terms" class="col-sm-8">
<CustomInputFile OnFileUploaded="@((args) => {
pd.ReferenceTermsFileId = args.Item1;
pd.ReferenceTermsFileName = args.Item2;
StateHasChanged();
})" />
<ValidationMessage For="@(() => pd.ReferenceTermsFileId)" />
</div>
</div>
<div class="form-group row">
<div class="col-sm-4">
<input type="submit" class="btn btn-danger" @onclick="@Cancel" style="width:220px;" value="Cancel" />
</div>
<div class="col-sm-8">
<input type="submit" class="btn btn-success" @onclick="@SubmitData" style="width:220px;" value="Save" />
</div>
</div>
</EditForm>
Code behind the parent page is very simple
ProjectDataModel pd = new ProjectDataModel();
...
private async Task SubmitData()
{
System.Console.WriteLine("Window 1: Data submitted.");
}
async Task CreateProjectDataModel()
{
if (pd != null && !string.IsNullOrEmpty(paramProjectCode))
{
await HttpClient.SendJsonAsync(HttpMethod.Put, "api/ProjectsData/Edit", pd);
}
else
{
pd = await HttpClient.SendJsonAsync<ProjectDataModel>(HttpMethod.Post, "api/ProjectsData/Create", pd);
}
UriHelper.NavigateTo("/Dashboard/fetch");
}
void Cancel()
{
UriHelper.NavigateTo("/Dashboard/fetch");
}
public class ProjectDataModel
{
[Required]
[Key]
public string ProjectCode { get; set; }
[Required]
public string ProjectName { get; set; }
public Guid DbcFileId { get; set; }
public string DbcFileName {get; set; }
public Guid ReferenceTermsFileId { get; set; }
public string ReferenceTermsFileName { get; set; }
}
Because ProjectCode and ProjectName properties are specified, when I upload a file (in my case Dbc file), the validation passes and method CreateProjectDataModel gets called before I upload the second file, ReferenceTerms.
My question is, how do I make InputFile not to call CreateProjectDataModel of parent EditForm ? I still want to have validation on the file info(DbcFileId !=Guid.Empty or regex validation on file name).
File presence is not necessary for the EditForm model to be valid. File can be uploaded later, after saving ProjectDataModel.
Any updates on this?
The input triggers the EditForms OnValidSubmit Method.
Edit: I add @onclick:preventDefault to my button and it works.