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

EditForm requires either a Model parameter, or an EditContext parameter, please provide one of these. #55701

Closed
sam-wheat opened this issue May 14, 2024 · 9 comments
Labels
area-blazor Includes: Blazor, Razor Components Needs: Author Feedback The author of this issue needs to respond in order for us to continue investigating this issue.

Comments

@sam-wheat
Copy link

sam-wheat commented May 14, 2024

I am very sorry I am unable to figure this out on my own.
Here are links I have found that have not assisted me in resolving this:

https://learn.microsoft.com/en-us/aspnet/core/blazor/forms/binding?view=aspnetcore-8.0
https://learn.microsoft.com/en-us/aspnet/core/blazor/components/render-modes?view=aspnetcore-8.0
https://stackoverflow.com/questions/67641707/editform-requires-either-a-model-parameter-or-an-editcontext-parameter
dotnet/AspNetCore.Docs#14869
#51420 // supplying formname as shown here does not work.
#43400 // applies to Maui however I am newing the model as suggested by the ticket.

<EditForm Model="Input" method="post" OnSubmit="loginClickHandler" FormName="login">
		<div style="height:100%;display:flex;flex-direction:column">
		<DataAnnotationsValidator />
		<h2>Use a local account to log in.</h2>
		<hr />
		<ValidationSummary class="text-danger" role="alert" />
			<MudContainer Class="d-flex flex-column align-self-center align-center gap-4 control-container">
				<MudTextField @bind-Value="Input.UserName" For="@(() => Input.UserName)" @ref=userNameTextField Label="User name" Variant="Variant.Filled" Style="width:250px"></MudTextField>
				<MudTextField @bind-Value="Input.Password" For="@(() => Input.Password)" Label="Password" Variant="Variant.Filled"
					InputType="@PasswordInput" Adornment="Adornment.End" AdornmentIcon="@PasswordInputIcon" 
					OnAdornmentClick="TogglePasswordVisibility" AdornmentAriaLabel="Show Password" Style="width:250px">
				</MudTextField>
				<MudButton ButtonType="ButtonType.Submit"  Variant="Variant.Filled" StartIcon="@Icons.Material.Filled.Login">Login</MudButton>
			</MudContainer>
			<div style="display:flex;margin-left:auto;margin-right:auto;margin-top:20vh;">
					<MudImage Style="width:20vw;" Src="/pp.png"></MudImage>
			</div>
		</div>
</EditForm>
@code{

	[SupplyParameterFromForm]
	public LoginRequest Input { get; set; } = new LoginRequest { UserName = string.Empty, Password = string.Empty };
}


public class LoginRequest
{
	public string UserName { get; set; }
	public string Password { get; set; }
}

Server side App.razor:

The route for the form above is "/Account/Login". As shown below when I change the render mode to InteractiveServerRenderMode the page is not found.

@code {
	[CascadingParameter]
	private HttpContext HttpContext { get; set; } = default!;

	private IComponentRenderMode? RenderModeForPage => HttpContext.Request.Path.StartsWithSegments("/Account")
		? null   // new InteractiveServerRenderMode(prerender: false) results in "Not found"
		: new InteractiveWebAssemblyRenderMode(prerender: false);
}

When I remove [SupplyParameterFromForm] the form submits however the values are empty strings (not bound to user input).

Error message:

Microsoft.AspNetCore.Server.Kestrel[13]
Connection id "0HN3JPDFNRESR", Request id "0HN3JPDFNRESR:00000001": An unhandled exception was thrown by the application.
System.InvalidOperationException: EditForm requires either a Model parameter, or an EditContext parameter, please provide one of these.
at Microsoft.AspNetCore.Components.Forms.EditForm.OnParametersSet()
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.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, ArrayRange1 oldTree, ArrayRange1 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(HttpContext context)
at Microsoft.AspNetCore.Components.Endpoints.RazorComponentEndpointInvoker.RenderComponentCore(HttpContext context)
at Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext.<>c.<b__10_0>d.MoveNext()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Antiforgery.Internal.AntiforgeryMiddleware.InvokeAwaited(HttpContext context)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Microsoft.WebTools.BrowserLink.Net.BrowserLinkMiddleware.InvokeAsync(HttpContext context)
at Microsoft.AspNetCore.Watch.BrowserRefresh.BrowserRefreshMiddleware.InvokeAsync(HttpContext context)
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequests[TContext](IHttpApplication`1 application)

@dotnet-issue-labeler dotnet-issue-labeler bot added the area-blazor Includes: Blazor, Razor Components label May 14, 2024
@mohiyo
Copy link

mohiyo commented May 14, 2024

"/Account/Login" will be in Static Server Mode and that is why MudBlazor and its UI Controls will not work.

Try using HTML5 controls or in-built Blazor Input Controls such as InputText

Also inside OnInitializedAsync use

Input ??= new(); //This will re-initialize the Model in case of Null

Note: MudBlazor requires InteractiveServerRenderMode

@sam-wheat
Copy link
Author

sam-wheat commented May 14, 2024

Thank you - as noted in my question the following results in the page being displayed briefly then the page becomes blank and the text "Not found" is displayed. Same behavior if (prerender: false) is ommitted.

The text "Not found" does not exist anywhere in my .sln. Is this a dotnet error? There are no errors in the browser console and all network calls complete normally. Where does the error "Not found" come from? It is not MudBlazor as I have commented out all html on the page.

@code {
    [CascadingParameter]
    private HttpContext HttpContext { get; set; } = default!;

    private IComponentRenderMode? RenderModeForPage => HttpContext.Request.Path.StartsWithSegments("/Account")
        ? new InteractiveServerRenderMode(prerender: false) //results in "Not found"
    :    new InteractiveWebAssemblyRenderMode(prerender: false);

}

image

@sam-wheat
Copy link
Author

The "Not found" behavior can be reproduced as follows:

Create a new Blazor app with individual account auth, render mode = Auto

Change this line in server project App.razor:

 private IComponentRenderMode? RenderModeForPage => HttpContext.Request.Path.StartsWithSegments("/Account")
     ? new InteractiveServerRenderMode(prerender: false)
     : InteractiveAuto;

Run the app and click Login on the nav menu Result:

image

F12 in the browser and refresh, note no 404 errors or console errors.

What is Blazor looking for that is not found?

@mkArtakMSFT
Copy link
Member

This is how you can avoid the Not found issue that you're facing:

protected override void OnParametersSet()
{
if (HttpContext is null)
{
// If this code runs, we're currently rendering in interactive mode, so there is no HttpContext.
// The identity pages need to set cookies, so they require an HttpContext. To achieve this we
// must transition back from interactive mode to a server-rendered page.
NavigationManager.Refresh(forceReload: true);
}

Please note, that all the account pages share this logic (through the common layout), to force rendering on the server.

@mkArtakMSFT mkArtakMSFT closed this as not planned Won't fix, can't repro, duplicate, stale May 14, 2024
@mkArtakMSFT mkArtakMSFT added the ✔️ Resolution: Answered Resolved because the question asked by the original author has been answered. label May 14, 2024
@sam-wheat
Copy link
Author

@mkArtakMSFT

Please note, that all the account pages share this logic (through the common layout), to force rendering on the server.

I'm sorry I don't understand this. If you follow the steps to reproduce that I provided above the account pages do not render.

@halter73
Copy link
Member

I told @mkArtakMSFT to link to the AccountLayout.razor logic because I incorrectly assumed your issue was that the app wasn't forcing a hard reload which is necessary for server-side endpoint routing to run as part of UseRouting().

Where does the error "Not found" come from? It is not MudBlazor as I have commented out all html on the page.

It comes from here in the Router component. If you added <NotFound> content to your Routes.razor, you would see that instead.

You'll notice that when you choose the "Global" interactivity location when creating a new Blazor project, it puts Routes.razor in the client project where it calls <Router AppAssembly="@typeof(Program).Assembly">. Program comes from the client project in this case, so the Router only searches in the client project for components. Since the account components are in the server project, the Router cannot find them. The only reason the Router can find the account components during prerendering is that it relies on the route matched in UseRouting() in that case.

If you create a Blazor project with the default "Per page/component" interactivity location, the Router should also find the account pages during interactive navigation post-prerendering. That's because it puts Routes.razor in the server project and uses <Router AppAssembly="@typeof(Program).Assembly" AdditionalAssemblies="new[] { typeof(Client._Imports).Assembly }"> to search both the client and server assemblies.

Unfortunately, even if the Router can find the account pages during interactive navigation, you'll still run into problems if you try to render the account pages interactively because of the lack of an HttpContext. The account pages work only during non-interactive prerendering because they rely on modifying HTTP response headers via the HttpContext both directly using cascading parameters and indirectly via SignInManager in order to allow arbitrary authentication handlers to do things like redirect to login and set cookies. This is not possible during interactive server or WebAssembly rendering because all HTTP response headers have already been sent by that point.

The purpose of the AccountLayout.razor logic is to force a non-interactive prerender in case the Router does find it and attempt to navigate to it interactively. In .NET 9, the [ExcludeFromInteractiveRouting] attribute (#55204) will be used for the account pages making the AccountLayout.razor logic unnecessary.

If you want to use interactive rendering for your account pages, you'll probably want to set up separate API endpoints for things like registration and login which you can trigger via a new fetch request. In WebAssembly this is as simple as using HttpClient, but for server interactivity you'll need to use JS interop. The upside is that these API endpoints can then use the SignInManager to modify HTTP responses.

MapIdentityApi provides an easy way to add these API endpoints, and our doc on how to Secure ASP.NET Core Blazor WebAssembly with ASP.NET Core Identity provides sample code demonstrating how to use the MapIdentityApi endpoints from a Blazor WebAssembly application.

In some cases, you might be able to leave the account pages as non-interactive and make only the MudBlazor components contained within interactive (e.g. <MudDialog @rendermode="@InteractiveServer" ...), but you might run into issues trying to pass non-serializable parameters across render mode boundaries.

If you don't actually need interactive account components, and you just need the account components to match your MudBlazor style, I recommend copying the CSS classes from the MudBlazor components to your own components that don't require interactivity.

Does this help resolve your issue?

@halter73 halter73 reopened this May 14, 2024
@halter73 halter73 added Needs: Author Feedback The author of this issue needs to respond in order for us to continue investigating this issue. and removed ✔️ Resolution: Answered Resolved because the question asked by the original author has been answered. Status: Resolved labels May 14, 2024
Copy link
Contributor

Hi @sam-wheat. We have added the "Needs: Author Feedback" label to this issue, which indicates that we have an open question for you before we can take further action. This issue will be closed automatically in 7 days if we do not hear back from you by then - please feel free to re-open it if you come back to this issue after that time.

@sam-wheat
Copy link
Author

@halter73 Thank you for the great explanation I'm going to have read this a couple of times. I hope this makes its way into the documentation.

@danielmoriya
Copy link

danielmoriya commented May 17, 2024

Hey @sam-wheat , I'm having the same issue and in my case, if I inspect the HTML elements in the form, the MudTextField component is not creating the input name, which prevents the form from being submitted with the values, raising the error EditForm requires either a Model parameter, or an EditContext parameter

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 Needs: Author Feedback The author of this issue needs to respond in order for us to continue investigating this issue.
Projects
None yet
Development

No branches or pull requests

5 participants