Pass extra data (Model's value, for example) when uploading file

1 Answer 352 Views
Upload
DoomerDGR8
Top achievements
Rank 2
Iron
Iron
Iron
DoomerDGR8 asked on 14 Aug 2022, 06:05 AM | edited on 14 Aug 2022, 06:09 AM

I have the Upload control in an EditorTemplate as follows:

/Views/Letter/EditorTemplates/LetterAttachmentsManage.cshtml:

@using DACRL.Portal.ColorAdmin.Controllers
@using DACRL.Portal.ColorAdmin.Extensions

@model Guid?

@{
    string[] extensions = { ".jpg", ".png", ".pdf", ".ppt", ".pptx", ".doc", ".docx", ".xls", ".xlsx" };
}

@Html.HiddenFor(m => m)

<kendo-upload name="letterFiles" multiple="true">
    <async save-url="@Url.Action(nameof(UploadController.ChunkSave), ControllerExtensions.Nameof<UploadController>())"
           remove-url="@Url.Action(nameof(UploadController.Remove), ControllerExtensions.Nameof<DashboardController>())"
           auto-upload="true"
           chunk-size="11000" />
    <validation allowed-extensions="@extensions" max-file-size="36700160" />
</kendo-upload>

This template is placed within a form on a parent view as

/Views/Letter/Create.cshtml:

@model LetterModel

@using (Html.BeginForm("", "Letter", FormMethod.Post))
{
	@Html.AntiForgeryToken()

	@Html.HiddenFor(m => m.IdCode)

	<div class="panel mt-20px" data-sortable-id="ui-widget-16">
		<div class="panel-heading bg-da-blue text-white">
			<h4 class="panel-title">RL Info</h4>
		</div>
		<div class="panel-body">
			@Html.EditorFor(m => m, "Letter")
		</div>
	</div>

	<div class="panel mt-20px" data-sortable-id="ui-widget-16">
		<div class="panel-heading bg-da-blue text-white">
			<h4 class="panel-title">Attachments</h4>
		</div>
		<div class="panel-body">
			@Html.EditorFor(m => m.IdCode, "LetterAttachmentsManage")
		</div>
	</div>
	
	<div class="row mt-3">
		<div class="col-md-1">
			<button type="submit" class="btn btn-primary w-100 me-5px" formaction="CreateSave" title="@(Model.IsUpdateCase ? "Update letter" : "Save letter")">@(Model.IsUpdateCase ? "Update" : "Save")</button>
		</div>
		<div class="col-md-1">
			<button type="submit" class="btn btn-default w-100" formaction="CreateSubmit" title="@(Model.IsUpdateCase ? "Update letter & submit" : "Save letter & submit")">Submit</button>
		</div>
	</div>
}

I'm trying to handle the File logic separately so I have the Upload Controller:

public class UploadController : BaseControllerWithAuth<UploadController>
{
    private readonly IWebHostEnvironment hostEnvironment;

    public UploadController(IWebHostEnvironment hostingEnvironment, IHttpContextAccessor httpContextAccess, IUserService userService) : base(httpContextAccess, userService) => hostEnvironment = hostingEnvironment;

    public async Task<ActionResult> ChunkSave([Bind(Prefix = "IdCode.letterFiles")] IEnumerable<IFormFile>? letterFiles, string? metaData, Guid? idCode)
    {
        try
        {
            if (metaData == null)
                return await Save(letterFiles);

            var chunkData = JsonSerializer.Deserialize<ChunkMetaDataModel>(metaData)!;

            if (letterFiles != null)
            {
                foreach (var file in letterFiles) AppendToFile(Path.Combine(hostEnvironment.WebRootPath, Constants.FileUploadPath, chunkData!.FileName), file, idCode?.ToString());
            }

            var fileBlob = new FileResultModel
            {
                uploaded = chunkData!.TotalChunks - 1 <= chunkData.ChunkIndex,
                fileUid  = chunkData.UploadUid
            };

            return Json(fileBlob);
        }
        catch (Exception ex)
        {
            Logger.LogError(ex, $"{nameof(UploadController)} => {nameof(ChunkSave)}: Error: {ex.Message}");
            throw;
        }
    }

    public ActionResult Remove(string[]? fileNames)
    {
        try
        {
            if (fileNames == null) return Content("");

            foreach (var fullName in fileNames)
            {
                var fileName     = Path.GetFileName(fullName);
                var physicalPath = Path.Combine(hostEnvironment.WebRootPath, Constants.FileUploadPath, fileName);

                if (System.IO.File.Exists(physicalPath)) System.IO.File.Delete(physicalPath);
            }

            return Content("");
        }
        catch (Exception ex)
        {
            Logger.LogError(ex, $"{nameof(UploadController)} => {nameof(Remove)}: Error: {ex.Message}");
            throw;
        }
    }

    private void AppendToFile(string fullPath, IFormFile content, string? idCode)
    {
        try
        {
            var basePath = Path.Combine(hostEnvironment.WebRootPath, Constants.FileUploadPath);

            if (!Directory.Exists(basePath)) Directory.CreateDirectory(basePath);

            var letterPath = Path.Combine(basePath, idCode!);

            if (!Directory.Exists(letterPath)) Directory.CreateDirectory(letterPath);

            using var stream = new FileStream(fullPath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
            content.CopyTo(stream);
        }
        catch (IOException ex)
        {
            Logger.LogError(ex, $"{nameof(UploadController)} => {nameof(AppendToFile)}: Error: {ex.Message}");
            throw;
        }
        catch (Exception ex)
        {
            Logger.LogError(ex, $"{nameof(UploadController)} => {nameof(AppendToFile)}: Error: {ex.Message}");
            throw;
        }
    }

    private async Task<ActionResult> Save([Bind(Prefix = "IdCode.letterFiles")] IEnumerable<IFormFile>? letterFiles)
    {
        try
        {
            if (letterFiles == null) return Content("");

            foreach (var file in letterFiles)
            {
                var fileContent  = ContentDispositionHeaderValue.Parse(file.ContentDisposition);
                var fileName     = Path.GetFileName(fileContent.FileName!.Trim('"'));
                var physicalPath = Path.Combine(hostEnvironment.WebRootPath, Constants.FileUploadPath, fileName);

                await using var fileStream = new FileStream(physicalPath, FileMode.Create);
                await file.CopyToAsync(fileStream);
            }

            return Content("");
        }
        catch (Exception ex)
        {
            Logger.LogError(ex, $"{nameof(UploadController)} => {nameof(Save)}: Error: {ex.Message}");
            throw;
        }
    }

}

Note: Due to this being an EditorTemplate, I had to add the 

[Bind(Prefix = "IdCode.letterFiles")]

Now, once the file is being chunked back to the Controller's ChunkSave action, I need to get the value of the model of this EditorTemplate as it will correlate with the primary entity later.

I have tried adding a parameter to the Controller's ChunkSave action as Guid? idCode. But I always get a null there.

How do I go about this dilemma?

 

1 Answer, 1 is accepted

Sort by
0
Alexander
Telerik team
answered on 17 Aug 2022, 04:52 PM

Hi Hassan,

Thank you for reaching out.

In order to pass extra data apart from the file information, I would recommend reviewing the following article:

Sending Metadata

The example demonstrates how additional information can be passed by utilizing the data object to the passed event. For your scenario specifically, I would recommend passing the Guid model as follows:

@model Guid?

<kendo-upload on-upload="onUpload" name="files"> // Omitted for brevity... </kendo-upload> <script> function onUpload(e){ e.data = { id: "@Model" }; } </script>

This would produce the following result:

For your convenience, I am also attaching a runnable sample that tackles the mentioned above.

Please give this suggestion a try and let me know how it works out for you.

Kind Regards,
Alexander
Progress Telerik

The Premier Dev Conference is back! 

Coming to you live from Progress360 in-person or on your own time, DevReach for all. Register Today.


Tags
Upload
Asked by
DoomerDGR8
Top achievements
Rank 2
Iron
Iron
Iron
Answers by
Alexander
Telerik team
Share this question
or