| File: FileDownload.cs | Web Access |
| Project: src\aspnetcore\src\Components\Media\src\Microsoft.AspNetCore.Components.Media.csproj (Microsoft.AspNetCore.Components.Media) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.AspNetCore.Components.Rendering; using Microsoft.JSInterop; namespace Microsoft.AspNetCore.Components.Media; /* This is equivalent to a .razor file containing: * * <a data-blazor-file-download * href="javascript:void(0)" * data-state=@(IsLoading ? "loading" : _hasError ? "error" : null) * @attributes="AdditionalAttributes" * @ref="Element" * @onclick="OnClickAsync">@(Text ?? "Download")</a> * */ /// <summary> /// A component that provides an anchor element to download the provided media source. /// </summary> public sealed class FileDownload : MediaComponentBase { /// <summary> /// File name to suggest to the browser for the download. Must be provided. /// </summary> [Parameter, EditorRequired] public string FileName { get; set; } = default!; /// <summary> /// Provides custom link text. Defaults to "Download". /// </summary> [Parameter] public string? Text { get; set; } internal override string TargetAttributeName => string.Empty; // Not used – object URL not tracked for downloads. /// <inheritdoc /> internal override bool ShouldAutoLoad => false; /// <summary> /// Allows customizing the rendering of the file download component. /// </summary> [Parameter] public RenderFragment<FileDownloadContext>? ChildContent { get; set; } private protected override void BuildRenderTree(RenderTreeBuilder builder) { if (ChildContent is not null) { var context = new FileDownloadContext { IsLoading = IsLoading, HasError = _hasError, FileName = FileName, }; context.Initialize(r => Element = r, EventCallback.Factory.Create(this, OnClickAsync)); builder.AddContent(0, ChildContent, context); return; } // Default rendering builder.OpenElement(0, "a"); builder.AddAttribute(1, "data-blazor-file-download", ""); if (IsLoading) { builder.AddAttribute(2, "data-state", "loading"); } else if (_hasError) { builder.AddAttribute(2, "data-state", "error"); } builder.AddAttribute(3, "href", "javascript:void(0)"); builder.AddAttribute(4, "onclick", EventCallback.Factory.Create(this, OnClickAsync)); IEnumerable<KeyValuePair<string, object>>? attributesToRender = AdditionalAttributes; if (AdditionalAttributes is not null && AdditionalAttributes.ContainsKey("href")) { var copy = new Dictionary<string, object>(AdditionalAttributes.Count); foreach (var kvp in AdditionalAttributes) { if (kvp.Key == "href") { continue; } copy.Add(kvp.Key, kvp.Value!); } attributesToRender = copy; } builder.AddMultipleAttributes(6, attributesToRender); builder.AddElementReferenceCapture(7, elementReference => Element = elementReference); builder.AddContent(8, Text ?? "Download"); builder.CloseElement(); } private async Task OnClickAsync() { if (Source is null || !IsInteractive || string.IsNullOrWhiteSpace(FileName)) { return; } CancelPreviousLoad(); var token = ResetCancellationToken(); _hasError = false; _currentSource = Source; Render(); var source = Source; using var streamRef = new DotNetStreamReference(source.Stream, leaveOpen: true); try { var result = await JSRuntime.InvokeAsync<bool>( "Blazor._internal.BinaryMedia.downloadAsync", token, Element, streamRef, source.MimeType, source.Length, FileName); if (!token.IsCancellationRequested) { _currentSource = null; if (!result) { _hasError = true; } Render(); } } catch (OperationCanceledException) { _currentSource = null; Render(); } catch { _currentSource = null; _hasError = true; Render(); } } } /// <summary> /// Extended media context for the FileDownload component providing click invocation and filename. /// </summary> public sealed class FileDownloadContext : MediaContext { /// <summary> /// Gets the file name suggested to the browser when initiating the download. /// </summary> public string FileName { get; internal set; } = string.Empty; private EventCallback _onClick; internal void Initialize(Action<ElementReference> capture, EventCallback onClick) { base.Initialize(capture); _onClick = onClick; } /// <summary> /// Initiates the download by invoking the underlying click handler of the parent. /// </summary> public Task InvokeAsync() => _onClick.InvokeAsync(); }