Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

InputFile in Blazor SSR #50614

Closed
1 task done
Markz878 opened this issue Sep 10, 2023 · 7 comments
Closed
1 task done

InputFile in Blazor SSR #50614

Markz878 opened this issue Sep 10, 2023 · 7 comments
Labels
area-blazor Includes: Blazor, Razor Components ✔️ Resolution: Duplicate Resolved as a duplicate of another issue Status: Resolved

Comments

@Markz878
Copy link

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

I'm trying to use InputFile component in a Blazor 8 Web app with only SSR.
I've tried binding the value to an IFormFile and IBrowserFile types, but get an error with both of them.
And the LoadFile method does not work obviously.

How are we supposed to use InputFile with only server side rendering?

Expected Behavior

I was hoping I could just put an IFormFile property (called File in this example) to my model and have this code in my form:

<InputFile @bind-Value="Model.File"/>

and on submit it would bind to the File property.

Steps To Reproduce

No response

Exceptions (if any)

With IFormFile the exception looks like this:

System.InvalidOperationException: No converter registered for type 'Microsoft.Extensions.Primitives.StringValues'.
   at Microsoft.AspNetCore.Components.Endpoints.FormMapping.FormDataMapperOptions.CreateConverter(Type type, FormDataMapperOptions options)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd[TArg](TKey key, Func`3 valueFactory, TArg factoryArgument)
   at Microsoft.AspNetCore.Components.Endpoints.FormMapping.DictionaryConverterFactory.CanConvert(Type type, FormDataMapperOptions options)
   at Microsoft.AspNetCore.Components.Endpoints.FormMapping.Metadata.FormDataMetadataFactory.GetOrCreateMetadataFor(Type type, FormDataMapperOptions options)
   at Microsoft.AspNetCore.Components.Endpoints.FormMapping.Metadata.FormDataMetadataFactory.GetOrCreateMetadataFor(Type type, FormDataMapperOptions options)
   at Microsoft.AspNetCore.Components.Endpoints.FormMapping.Metadata.FormDataMetadataFactory.GetOrCreateMetadataFor(Type type, FormDataMapperOptions options)
   at Microsoft.AspNetCore.Components.Endpoints.FormMapping.ComplexTypeConverterFactory.CanConvert(Type type, FormDataMapperOptions options)
   at Microsoft.AspNetCore.Components.Endpoints.FormMapping.FormDataMapperOptions.CreateConverter(Type type, FormDataMapperOptions options)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd[TArg](TKey key, Func`3 valueFactory, TArg factoryArgument)
   at Microsoft.AspNetCore.Components.Endpoints.HttpContextFormValueMapper.CanMap(Type valueType, String scopeName, String formName)
   at Microsoft.AspNetCore.Components.CascadingParameterState.GetMatchingCascadingValueSupplier(CascadingParameterInfo& info, Renderer renderer, ComponentState componentState)
   at Microsoft.AspNetCore.Components.CascadingParameterState.FindCascadingParameters(ComponentState componentState, Boolean& hasSingleDeliveryParameters)
   at Microsoft.AspNetCore.Components.Rendering.ComponentState..ctor(Renderer renderer, Int32 componentId, IComponent component, ComponentState parentComponentState)
   at Microsoft.AspNetCore.Components.Endpoints.EndpointComponentState..ctor(Renderer renderer, Int32 componentId, IComponent component, ComponentState parentComponentState)
   at Microsoft.AspNetCore.Components.Endpoints.EndpointHtmlRenderer.CreateComponentState(Int32 componentId, IComponent component, ComponentState parentComponentState)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.AttachAndInitComponent(IComponent component, Int32 parentComponentId)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.InstantiateChildComponentOnFrame(RenderTreeFrame[] frames, Int32 frameIndex, Int32 parentComponentId)
   at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InitializeNewComponentFrame(DiffContext& diffContext, Int32 frameIndex)
   at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InitializeNewSubtree(DiffContext& diffContext, Int32 frameIndex)
   at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InsertNewFrame(DiffContext& diffContext, Int32 newFrameIndex)
   at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.AppendDiffEntriesForRange(DiffContext& diffContext, Int32 oldStartIndex, Int32 oldEndIndexExcl, Int32 newStartIndex, Int32 newEndIndexExcl)
   at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.ComputeDiff(Renderer renderer, RenderBatchBuilder batchBuilder, Int32 componentId, ArrayRange`1 oldTree, ArrayRange`1 newTree)
   at Microsoft.AspNetCore.Components.Rendering.ComponentState.RenderIntoBatch(RenderBatchBuilder batchBuilder, RenderFragment renderFragment, Exception& renderFragmentException)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessRenderQueue()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessRenderQueue()
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.AddToRenderQueue(Int32 componentId, RenderFragment renderFragment)
   at Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged()
   at Microsoft.AspNetCore.Components.ComponentBase.CallOnParametersSetAsync()
   at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()
   at Microsoft.AspNetCore.Components.Rendering.ComponentState.SetDirectParameters(ParameterView parameters)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.RenderRootComponentAsync(Int32 componentId, ParameterView initialParameters)
   at Microsoft.AspNetCore.Components.HtmlRendering.Infrastructure.StaticHtmlRenderer.BeginRenderingComponent(IComponent component, ParameterView initialParameters)
   at Microsoft.AspNetCore.Components.Endpoints.EndpointHtmlRenderer.RenderEndpointComponent(HttpContext httpContext, Type rootComponentType, ParameterView parameters, Boolean waitForQuiescence)
   at Microsoft.AspNetCore.Components.Endpoints.RazorComponentEndpointInvoker.RenderComponentCore()
   at Microsoft.AspNetCore.Components.Endpoints.RazorComponentEndpointInvoker.RenderComponentCore()
   at Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext.<>c.<<InvokeAsync>b__9_0>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Antiforgery.Internal.AntiforgeryMiddleware.InvokeAwaited(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

.NET Version

8.0.100-preview.7.23376.3

Anything else?

No response

@dotnet-issue-labeler dotnet-issue-labeler bot added the area-blazor Includes: Blazor, Razor Components label Sep 10, 2023
@javiercn
Copy link
Member

This is being potentially addressed as part of #50537

@mkArtakMSFT
Copy link
Member

Closing as a dupe of #49526

@mkArtakMSFT mkArtakMSFT added the ✔️ Resolution: Duplicate Resolved as a duplicate of another issue label Sep 11, 2023
@ghost ghost added the Status: Resolved label Sep 11, 2023
@Markz878
Copy link
Author

@mkArtakMSFT @javiercn This was closed as a possible duplicate, but in RC 2 I still can't bind a Blazor SSR page input type="file" to a InputFile property, how should this be done?

Here is some sample code for the Home page:

@page "/"

<PageTitle>Home</PageTitle>

<EditForm method="post" Model="NewCustomer" OnValidSubmit="SubmitForm" FormName="customer" Enhance>
    <input type="file" name="NewCustomer.File" />
    <button>Submit</button>
</EditForm>

@code {
    [SupplyParameterFromForm]
    public Customer? NewCustomer { get; set; }

    protected override void OnInitialized()
    {
        NewCustomer ??= new();
    }

    public async Task SubmitForm()
    {
        ArgumentNullException.ThrowIfNull(NewCustomer?.File);
        using FileStream fileStream = File.OpenWrite(NewCustomer.File.Name);
        using Stream submittedFileStream = NewCustomer.File.OpenReadStream();
        await submittedFileStream.CopyToAsync(fileStream);
    }

    public class Customer
    {
        public IFormFile? File { get; set; }
    }
}

@danroth27
Copy link
Member

@Markz878 If you're still hitting issues with this, could you please open a new GitHub issue so that we can investigate?

@Markz878
Copy link
Author

Markz878 commented Nov 10, 2023

@danroth27 I created this: #51980
Not a priority though since file submit works with the Enhance attribute.

BUT HERE IS A PRIORITY TICKET: 51584
If it's too late for the .NET 8 release, could you fix it in a patch, the same way you fixed bind:after in .NET 7.
I guarantee that nobody is reliant on the inconsistent behavior of the State Persist mechanism in SSR to WASM page navigation, and not fixing that for a LTS is a huge mistake.

I finally got my team excited about using Blazor with SSR, but the bad UX with WASM pages ruins all that. Imagine if Github first showed page content, then cleared it, and then showed it again. Really annoying.

I'm hopeful you will make the right decision, and thanks for the hard work so far.

@danroth27
Copy link
Member

Hi @Markz878. I think it's unlikely that we'll be able to deliver a solution to #51584 in a patch. That issue really requires a new feature to work properly. But we can look at providing guidance on how to deal with the currently limitations. Let's discuss that on the #51584 issue.

@mammadkoma
Copy link

mammadkoma commented Jan 11, 2024

I get the file value null and I resolved by 2 changes:

  1. Adding enctype="multipart/form-data" to the EditForm tag.
  2. Adding name="vm.PdfFile" to the InputFile tag.
<EditForm Model="@vm" OnValidSubmit="@Submit" FormName="CommodityAddEdit"
          method="post" enctype="multipart/form-data" Enhance>
    <DataAnnotationsValidator />
    <div class="form-floating mb-3">
        <InputText @bind-Value="vm.Title" class="form-control" placeholder autofocus />
        <label>Title</label>
        <ValidationMessage For="() => vm.Title" class="text-danger" />
        @if (ShowAlert == true)
        {
            <Alert Text="@AlertText" />
        }
    </div>

    <div class="form-floating mb-3">
        <InputFile name="vm.PdfFile" class="form-control" placeholder />
        <ValidationMessage For="() => vm.PdfFile" class="text-danger" />
    </div>
    <div class="d-flex justify-content-center">
        <button type="submit" class="btn btn-outline-success w-50">Submit</button>
    </div>
</EditForm>


[SupplyParameterFromForm]
private AddCommodityVm vm { get; set; } = new();

public async Task Submit(EditContext editContext)
{

}

public class AddCommodityVm
{
    [Required, Length(2, 50, ErrorMessage = Constants.LengthMsg)]
    public string Title { get; set; } = "";

    [Required]
    public IFormFile? PdfFile { get; set; }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-blazor Includes: Blazor, Razor Components ✔️ Resolution: Duplicate Resolved as a duplicate of another issue Status: Resolved
Projects
None yet
Development

No branches or pull requests

5 participants