From bc2008268edac721a2f34e4112a836ae4d041ddd Mon Sep 17 00:00:00 2001 From: "Jeffrey T. Fritz" Date: Mon, 27 Apr 2026 09:50:53 -0400 Subject: [PATCH 01/11] feat(cli): target .NET 10 Blazor SSR scaffolds Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- README.md | 6 ++-- docs/Migration/migration_readiness.md | 2 +- docs/Migration/readme.md | 14 ++++----- docs/cli/index.md | 30 +++++++++---------- migration-toolkit/METHODOLOGY.md | 8 ++--- migration-toolkit/scripts/bwfc-migrate.ps1 | 13 ++++---- scripts/bwfc-migrate.ps1 | 16 +++++----- src/BlazorWebFormsComponents.Cli/Program.cs | 8 ++--- .../Scaffolding/ProjectScaffolder.cs | 11 +++---- .../ScaffoldingTests.cs | 9 ++++-- 10 files changed, 54 insertions(+), 63 deletions(-) diff --git a/README.md b/README.md index 7e901d048..24e3f89ef 100644 --- a/README.md +++ b/README.md @@ -46,12 +46,12 @@ Portions of the [original .NET Framework](https://github.com/microsoft/reference ## Migration CLI Tool -The **`webforms-to-blazor` CLI tool** automates the first phase of Web Forms to Blazor migration. It applies deterministic transforms to your markup and code-behind, removing boilerplate and converting patterns: +The **`webforms-to-blazor` CLI tool** automates the first phase of Web Forms to Blazor migration. It applies deterministic transforms to your markup and code-behind, removing boilerplate and converting patterns into a **.NET 10 Blazor Web App scaffolded for static server-side rendering (SSR)**: ```bash # Full project migration dotnet tool install --global Fritz.WebFormsToBlazor -webforms-to-blazor migrate --input ./MyWebFormsProject --output ./MyBlazorProject --scaffold +webforms-to-blazor migrate --input ./MyWebFormsProject --output ./MyBlazorProject # Or convert individual files webforms-to-blazor convert --input ProductCard.ascx --output ProductCard.razor @@ -62,7 +62,7 @@ webforms-to-blazor convert --input ProductCard.ascx --output ProductCard.razor - Removes `asp:` prefixes: `` → ` -

- Register as a new user -

-

- @* Enable this once you have account confirmation enabled for password reset functionality - Forgot your password? - *@ -

- - - -
-
- -
-
+ +

+ Register as a new user +

+ - + diff --git a/samples/AfterWingtipToys/Account/Login.razor.cs b/samples/AfterWingtipToys/Account/Login.razor.cs index 8832300ea..0dd9e9de8 100644 --- a/samples/AfterWingtipToys/Account/Login.razor.cs +++ b/samples/AfterWingtipToys/Account/Login.razor.cs @@ -1,13 +1,6 @@ namespace WingtipToys.Account; -// TODO: Requires ASP.NET Core Identity migration — Login page needs SignInManager/UserManager public partial class Login { - private string Email { get; set; } = ""; - private string Password { get; set; } = ""; - - private void LogIn(EventArgs e) - { - // TODO: Implement with SignInManager - } + private string? ErrorMessage => Request.QueryString["error"]; } diff --git a/samples/AfterWingtipToys/Account/Manage.razor b/samples/AfterWingtipToys/Account/Manage.razor index 6eea0a516..da36bac87 100644 --- a/samples/AfterWingtipToys/Account/Manage.razor +++ b/samples/AfterWingtipToys/Account/Manage.razor @@ -1,66 +1,18 @@ -@page "/Manage" +@page "/Account/Manage" Manage Account -

@(Title).

-
- -

@(SuccessMessage)

-
-
- -
-
-
-

Change your account settings

-
-
-
Password:
-
- - -
-
External Logins:
-
@(LoginsCount) - - -
- @* - Phone Numbers can used as a second factor of verification in a two-factor authentication system. - See this article - for details on setting up this ASP.NET application to support two-factor authentication using SMS. - Uncomment the following block after you have set up two-factor authentication - *@ +

Manage Account

-
Phone Number:
- @* - <% if (HasPhoneNumber) - { %> -
- -
- <% } - else - { %> -
-
- <% } %> - *@ - -
Two-Factor Authentication:
-
-

- There are no two-factor authentication providers configured. See this article - for details on setting up this ASP.NET application to support two-factor authentication. -

- @* TODO: Two-factor enable/disable requires ASP.NET Core Identity migration *@ - @* Two-factor authentication status placeholder *@ -
-
-
-
+@if (string.IsNullOrWhiteSpace(CurrentUserEmail)) +{ +
+ You are not currently signed in.
- - +} +else +{ +

Hello, @CurrentUserEmail!

+

+ Log out +

+} diff --git a/samples/AfterWingtipToys/Account/Manage.razor.cs b/samples/AfterWingtipToys/Account/Manage.razor.cs index 1bddcc92f..062f6c76c 100644 --- a/samples/AfterWingtipToys/Account/Manage.razor.cs +++ b/samples/AfterWingtipToys/Account/Manage.razor.cs @@ -1,8 +1,6 @@ namespace WingtipToys.Account; -// TODO: Requires ASP.NET Core Identity migration — account management needs UserManager public partial class Manage { - private string SuccessMessage { get; set; } = ""; - private int LoginsCount { get; set; } + private string? CurrentUserEmail => Session.Get(Services.UserStoreService.CurrentUserSessionKey); } diff --git a/samples/AfterWingtipToys/Account/ManageLogins.razor b/samples/AfterWingtipToys/Account/ManageLogins.razor index 5cef95946..0a38fd773 100644 --- a/samples/AfterWingtipToys/Account/ManageLogins.razor +++ b/samples/AfterWingtipToys/Account/ManageLogins.razor @@ -1,16 +1,16 @@ @page "/ManageLogins" -@using Microsoft.AspNetCore.Identity -

Manage your external logins.

- + +

Manage your external logins.

+

@(SuccessMessage)

-@* TODO: SelectMethod="GetLogins" preserved — convert to delegate: SelectMethod="@((maxRows, startRow, sort, out total) => YourService.GetLogins(maxRows, startRow, sort, out total))" *@ + TItem="UserLoginInfo" + SelectMethod="GetLogins" DeleteMethod="RemoveLogin" DataKeyNames="LoginProvider,ProviderKey"> +@* TODO(bwfc-select-method): Review DeleteMethod="RemoveLogin" migration for BWFC event/CRUD handling *@

Registered Logins

@@ -25,9 +25,9 @@ @context.LoginProvider - @* TODO: ToolTip and Visible attributes used Web Forms <%# %> data-binding syntax — migrate to Blazor *@
- +

Change Password Form


- +
- +
- +
- +
- +
- + @@ -82,11 +82,10 @@
-
- diff --git a/samples/AfterWingtipToys/Account/ManagePassword.razor.cs b/samples/AfterWingtipToys/Account/ManagePassword.razor.cs index 4c64cd21e..b0c2a16b4 100644 --- a/samples/AfterWingtipToys/Account/ManagePassword.razor.cs +++ b/samples/AfterWingtipToys/Account/ManagePassword.razor.cs @@ -1,17 +1,141 @@ -namespace WingtipToys.Account; +// ============================================================================= +// TODO(bwfc-general): This code-behind was copied from Web Forms and needs manual migration. +// +// Common transforms needed (use the BWFC Copilot skill for assistance): +// TODO(bwfc-lifecycle): Page_Load / Page_Init → OnInitializedAsync / OnParametersSetAsync +// TODO(bwfc-lifecycle): Page_PreRender → OnAfterRenderAsync +// TODO(bwfc-ispostback): IsPostBack checks → remove or convert to state logic +// TODO(bwfc-viewstate): ViewState usage → component [Parameter] or private fields +// TODO(bwfc-session-state): Session/Cache access → auto-wired on WebFormsPageBase via SessionShim/CacheShim +// TODO(bwfc-navigation): Response.Redirect → auto-wired on WebFormsPageBase via ResponseShim +// TODO(bwfc-form): Request.Form["key"] → auto-wired on WebFormsPageBase via FormShim (use for interactive mode) +// TODO(bwfc-server): Server.MapPath/HtmlEncode → auto-wired on WebFormsPageBase via ServerShim +// TODO(bwfc-config): ConfigurationManager.AppSettings → BWFC shim (call app.UseConfigurationManagerShim() in Program.cs) +// TODO(bwfc-general): ClientScript.RegisterStartupScript → auto-wired on WebFormsPageBase via ClientScriptShim +// TODO(bwfc-general): Event handlers (Button_Click, etc.) → convert to Blazor event callbacks +// TODO(bwfc-datasource): Data binding (DataBind, DataSource) → component parameters or OnInitialized +// TODO(bwfc-general): ScriptManager code-behind references → use ScriptManagerShim via ScriptManager.GetCurrent(this) +// TODO(bwfc-general): UpdatePanel markup preserved by BWFC (ContentTemplate supported) — remove only code-behind API calls +// TODO(bwfc-general): User controls → Blazor component references +// ============================================================================= +using System; +using System.Collections.Generic; +using System.Linq; +using BlazorWebFormsComponents.Identity; +using IdentityResult = BlazorWebFormsComponents.Identity.IdentityResult; -// TODO: Requires ASP.NET Core Identity migration — password management needs UserManager -public partial class ManagePassword +namespace WingtipToys.Account { - private string Password { get; set; } = ""; - - private void SetPassword_Click(EventArgs e) + public partial class ManagePassword { - // TODO: Implement with UserManager - } + // TODO(bwfc-general): ClientScript calls preserved — works via WebFormsPageBase (no injection needed). ScriptManagerShim may need @inject ScriptManagerShim ScriptManager for non-page classes. - private void ChangePassword_Click(EventArgs e) - { - // TODO: Implement with UserManager + // --- Request.Form Migration --- + // TODO(bwfc-form): Request.Form calls work automatically via RequestShim on WebFormsPageBase. + // For interactive mode, wrap your form in . + // Form keys found: key + // For non-page classes, inject RequestShim via DI. + + // --- Response.Redirect Migration --- + // TODO(bwfc-navigation): Response.Redirect() works via ResponseShim on WebFormsPageBase. Handles ~/ and .aspx automatically. + // For non-page classes, inject ResponseShim via DI. + + private PlaceHolder changePasswordHolder = default!; + private TextBox ConfirmNewPassword = default!; + private Label ConfirmNewPasswordLabel = default!; + private TextBox confirmPassword = default!; + private TextBox CurrentPassword = default!; + private Label CurrentPasswordLabel = default!; + private TextBox NewPassword = default!; + private Label NewPasswordLabel = default!; + private TextBox password = default!; + private PlaceHolder setPassword = default!; + // --- ConfigurationManager Migration --- + // TODO(bwfc-config): ConfigurationManager calls work via BWFC shim. + // Ensure app.UseConfigurationManagerShim() is called in Program.cs. + + protected string SuccessMessage + { + get; + private set; + } + + private bool HasPassword(ApplicationUserManager manager) + { + return manager.HasPassword(User.Identity.GetUserId()); + } + + protected override async Task OnInitializedAsync() + { + // TODO(bwfc-lifecycle): Review lifecycle conversion — verify async behavior + await base.OnInitializedAsync(); + + var manager = Context.GetOwinContext().GetUserManager(); + + // BWFC: IsPostBack guard unwrapped — Blazor re-renders on every state change + // Determine the sections to render + if (HasPassword(manager)) + { + changePasswordHolder.Visible = true; + } + else + { + setPassword.Visible = true; + changePasswordHolder.Visible = false; + } + + // Render success message + var message = Request.QueryString["m"]; + if (message != null) + { + // Strip the query string from action + Form.Action = ResolveUrl("~/Account/Manage"); + } + } + + protected void ChangePassword_Click() + { + if (IsValid) + { + var manager = Context.GetOwinContext().GetUserManager(); + IdentityResult result = manager.ChangePassword(User.Identity.GetUserId(), CurrentPassword.Text, NewPassword.Text); + if (result.Succeeded) + { + var user = manager.FindById(User.Identity.GetUserId()); + IdentityHelper.SignIn(manager, user, isPersistent: false); + Response.Redirect("~/Account/Manage?m=ChangePwdSuccess"); + } + else + { + AddErrors(result); + } + } + } + + protected void SetPassword_Click() + { + if (IsValid) + { + // Create the local login info and link the local account to the user + var manager = Context.GetOwinContext().GetUserManager(); + IdentityResult result = manager.AddPassword(User.Identity.GetUserId(), password.Text); + if (result.Succeeded) + { + Response.Redirect("~/Account/Manage?m=SetPwdSuccess"); + } + else + { + AddErrors(result); + } + } + } + + private void AddErrors(IdentityResult result) + { + foreach (var error in result.Errors) + { + ModelState.AddModelError("", error); + } + } } -} +} \ No newline at end of file diff --git a/samples/AfterWingtipToys/Account/OpenAuthProviders.razor b/samples/AfterWingtipToys/Account/OpenAuthProviders.razor index e32f58afa..e300fe201 100644 --- a/samples/AfterWingtipToys/Account/OpenAuthProviders.razor +++ b/samples/AfterWingtipToys/Account/OpenAuthProviders.razor @@ -1,9 +1,8 @@

Use another service to log in.


- -@* TODO: SelectMethod="GetProviderNames" preserved — convert to delegate: SelectMethod="@((maxRows, startRow, sort, out total) => YourService.GetProviderNames(maxRows, startRow, sort, out total))" *@ +

- diff --git a/samples/AfterWingtipToys/Account/OpenAuthProviders.razor.cs b/samples/AfterWingtipToys/Account/OpenAuthProviders.razor.cs index f0c6cbe26..ba3dabc8c 100644 --- a/samples/AfterWingtipToys/Account/OpenAuthProviders.razor.cs +++ b/samples/AfterWingtipToys/Account/OpenAuthProviders.razor.cs @@ -1,14 +1,75 @@ +// ============================================================================= +// TODO(bwfc-general): This code-behind was copied from Web Forms and needs manual migration. +// +// Common transforms needed (use the BWFC Copilot skill for assistance): +// TODO(bwfc-lifecycle): Page_Load / Page_Init → OnInitializedAsync / OnParametersSetAsync +// TODO(bwfc-lifecycle): Page_PreRender → OnAfterRenderAsync +// TODO(bwfc-ispostback): IsPostBack checks → remove or convert to state logic +// TODO(bwfc-viewstate): ViewState usage → component [Parameter] or private fields +// TODO(bwfc-session-state): Session/Cache access → auto-wired on WebFormsPageBase via SessionShim/CacheShim +// TODO(bwfc-navigation): Response.Redirect → auto-wired on WebFormsPageBase via ResponseShim +// TODO(bwfc-form): Request.Form["key"] → auto-wired on WebFormsPageBase via FormShim (use for interactive mode) +// TODO(bwfc-server): Server.MapPath/HtmlEncode → auto-wired on WebFormsPageBase via ServerShim +// TODO(bwfc-config): ConfigurationManager.AppSettings → BWFC shim (call app.UseConfigurationManagerShim() in Program.cs) +// TODO(bwfc-general): ClientScript.RegisterStartupScript → auto-wired on WebFormsPageBase via ClientScriptShim +// TODO(bwfc-general): Event handlers (Button_Click, etc.) → convert to Blazor event callbacks +// TODO(bwfc-datasource): Data binding (DataBind, DataSource) → component parameters or OnInitialized +// TODO(bwfc-general): ScriptManager code-behind references → use ScriptManagerShim via ScriptManager.GetCurrent(this) +// TODO(bwfc-general): UpdatePanel markup preserved by BWFC (ContentTemplate supported) — remove only code-behind API calls +// TODO(bwfc-general): User controls → Blazor component references +// ============================================================================= +using System; +using System.Collections.Generic; +using System.Globalization; using System.Linq; - -namespace WingtipToys.Account; - -// TODO: Requires ASP.NET Core Identity migration — external auth providers need AuthenticationSchemeProvider -public partial class OpenAuthProviders +namespace WingtipToys.Account { - private IQueryable GetProviderNames(int maxRows, int startRowIndex, string sortByExpression, out int totalRowCount) + public partial class OpenAuthProviders { - // TODO: Implement with AuthenticationSchemeProvider - totalRowCount = 0; - return Array.Empty().AsQueryable(); + // TODO(bwfc-general): ClientScript calls preserved — works via WebFormsPageBase (no injection needed). ScriptManagerShim may need @inject ScriptManagerShim ScriptManager for non-page classes. + + // --- Request.Form Migration --- + // TODO(bwfc-form): Request.Form calls work automatically via RequestShim on WebFormsPageBase. + // For interactive mode, wrap your form in . + // Form keys found: key, provider + // For non-page classes, inject RequestShim via DI. + + private ListView providerDetails = default!; + // --- ConfigurationManager Migration --- + // TODO(bwfc-config): ConfigurationManager calls work via BWFC shim. + // Ensure app.UseConfigurationManagerShim() is called in Program.cs. + + protected override async Task OnInitializedAsync() + { + // TODO(bwfc-lifecycle): Review lifecycle conversion — verify async behavior + await base.OnInitializedAsync(); + + if (IsPostBack) + { + var provider = Request.Form["provider"]; + if (provider == null) + { + return; + } + // Request a redirect to the external login provider + string redirectUrl = ResolveUrl(String.Format(CultureInfo.InvariantCulture, "~/Account/RegisterExternalLogin?{0}={1}&returnUrl={2}", IdentityHelper.ProviderNameKey, provider, ReturnUrl)); + var properties = new AuthenticationProperties() { RedirectUri = redirectUrl }; + // Add xsrf verification when linking accounts + if (Context.User.Identity.IsAuthenticated) + { + properties.Dictionary[IdentityHelper.XsrfKey] = Context.User.Identity.GetUserId(); + } + Context.GetOwinContext().Authentication.Challenge(properties, provider); + Response.StatusCode = 401; + Response.End(); + } + } + + public string ReturnUrl { get; set; } + + public IEnumerable GetProviderNames() + { + return Context.GetOwinContext().Authentication.GetExternalAuthenticationTypes().Select(t => t.AuthenticationType); + } } -} +} \ No newline at end of file diff --git a/samples/AfterWingtipToys/Account/Register.razor b/samples/AfterWingtipToys/Account/Register.razor index c0a3e2bfb..616317788 100644 --- a/samples/AfterWingtipToys/Account/Register.razor +++ b/samples/AfterWingtipToys/Account/Register.razor @@ -1,44 +1,38 @@ @page "/Register" +@page "/Account/Register" Register -

@(Title).

-

- -

-
-

Create a new account

-
- -
- -
- - -
+

Register

+ +@if (!string.IsNullOrWhiteSpace(ErrorMessage)) +{ +

@ErrorMessage

+} + +
+

Create a new account

+
+
+ +
+
-
- -
- - -
+
+
+ +
+
-
- -
- - - -
+
+
+ +
+
-
-
-
+
+
+
+
- + diff --git a/samples/AfterWingtipToys/Account/Register.razor.cs b/samples/AfterWingtipToys/Account/Register.razor.cs index e681bb394..c99c01fd6 100644 --- a/samples/AfterWingtipToys/Account/Register.razor.cs +++ b/samples/AfterWingtipToys/Account/Register.razor.cs @@ -1,13 +1,6 @@ namespace WingtipToys.Account; -// TODO: Requires ASP.NET Core Identity migration — registration needs UserManager public partial class Register { - private string Email { get; set; } = ""; - private string Password { get; set; } = ""; - - private void CreateUser_Click(EventArgs e) - { - // TODO: Implement with UserManager - } + private string? ErrorMessage => Request.QueryString["error"]; } diff --git a/samples/AfterWingtipToys/Account/RegisterExternalLogin.razor b/samples/AfterWingtipToys/Account/RegisterExternalLogin.razor index 63ab4bba5..082161cf8 100644 --- a/samples/AfterWingtipToys/Account/RegisterExternalLogin.razor +++ b/samples/AfterWingtipToys/Account/RegisterExternalLogin.razor @@ -15,7 +15,7 @@
- + @@ -24,9 +24,8 @@
-
- diff --git a/samples/AfterWingtipToys/Account/RegisterExternalLogin.razor.cs b/samples/AfterWingtipToys/Account/RegisterExternalLogin.razor.cs index 2bf6cf05c..fa21218af 100644 --- a/samples/AfterWingtipToys/Account/RegisterExternalLogin.razor.cs +++ b/samples/AfterWingtipToys/Account/RegisterExternalLogin.razor.cs @@ -1,13 +1,169 @@ -namespace WingtipToys.Account; +// ============================================================================= +// TODO(bwfc-general): This code-behind was copied from Web Forms and needs manual migration. +// +// Common transforms needed (use the BWFC Copilot skill for assistance): +// TODO(bwfc-lifecycle): Page_Load / Page_Init → OnInitializedAsync / OnParametersSetAsync +// TODO(bwfc-lifecycle): Page_PreRender → OnAfterRenderAsync +// TODO(bwfc-ispostback): IsPostBack checks → remove or convert to state logic +// TODO(bwfc-viewstate): ViewState usage → component [Parameter] or private fields +// TODO(bwfc-session-state): Session/Cache access → auto-wired on WebFormsPageBase via SessionShim/CacheShim +// TODO(bwfc-navigation): Response.Redirect → auto-wired on WebFormsPageBase via ResponseShim +// TODO(bwfc-form): Request.Form["key"] → auto-wired on WebFormsPageBase via FormShim (use for interactive mode) +// TODO(bwfc-server): Server.MapPath/HtmlEncode → auto-wired on WebFormsPageBase via ServerShim +// TODO(bwfc-config): ConfigurationManager.AppSettings → BWFC shim (call app.UseConfigurationManagerShim() in Program.cs) +// TODO(bwfc-general): ClientScript.RegisterStartupScript → auto-wired on WebFormsPageBase via ClientScriptShim +// TODO(bwfc-general): Event handlers (Button_Click, etc.) → convert to Blazor event callbacks +// TODO(bwfc-datasource): Data binding (DataBind, DataSource) → component parameters or OnInitialized +// TODO(bwfc-general): ScriptManager code-behind references → use ScriptManagerShim via ScriptManager.GetCurrent(this) +// TODO(bwfc-general): UpdatePanel markup preserved by BWFC (ContentTemplate supported) — remove only code-behind API calls +// TODO(bwfc-general): User controls → Blazor component references +// ============================================================================= -// TODO: Requires ASP.NET Core Identity migration — external login registration needs UserManager/SignInManager -public partial class RegisterExternalLogin -{ - private string ProviderName { get; set; } = ""; - private string Email { get; set; } = ""; +// --- ViewState Migration --- +// ViewState is in-memory only in Blazor (does not survive navigation). +// Convert to private fields or [Parameter] properties: +// private object _providerName; // was ViewState["ProviderName"] +// private object _providerAccountKey; // was ViewState["ProviderAccountKey"] +// Note: BaseWebFormsComponent.ViewState exists as an [Obsolete] compatibility shim. + +using System; +using WingtipToys.Models; - private void LogIn_Click(EventArgs e) +using BlazorWebFormsComponents.Identity; +using IdentityResult = BlazorWebFormsComponents.Identity.IdentityResult; + +namespace WingtipToys.Account +{ + public partial class RegisterExternalLogin { - // TODO: Implement with UserManager/SignInManager + // TODO(bwfc-general): ClientScript calls preserved — works via WebFormsPageBase (no injection needed). ScriptManagerShim may need @inject ScriptManagerShim ScriptManager for non-page classes. + + // --- Request.Form Migration --- + // TODO(bwfc-form): Request.Form calls work automatically via RequestShim on WebFormsPageBase. + // For interactive mode, wrap your form in . + // Form keys found: key + // For non-page classes, inject RequestShim via DI. + + // --- Response.Redirect Migration --- + // TODO(bwfc-navigation): Response.Redirect() works via ResponseShim on WebFormsPageBase. Handles ~/ and .aspx automatically. + // For non-page classes, inject ResponseShim via DI. + + private TextBox email = default!; + // --- ConfigurationManager Migration --- + // TODO(bwfc-config): ConfigurationManager calls work via BWFC shim. + // Ensure app.UseConfigurationManagerShim() is called in Program.cs. + + protected string ProviderName + { + get { return (string)ViewState["ProviderName"] ?? String.Empty; } + private set { ViewState["ProviderName"] = value; } + } + + protected string ProviderAccountKey + { + get { return (string)ViewState["ProviderAccountKey"] ?? String.Empty; } + private set { ViewState["ProviderAccountKey"] = value; } + } + + private void RedirectOnFail() + { + Response.Redirect((User.Identity.IsAuthenticated) ? "~/Account/Manage" : "~/Account/Login"); + } + + protected void Page_Load() + { + // Process the result from an auth provider in the request + ProviderName = IdentityHelper.GetProviderNameFromRequest(Request); + if (String.IsNullOrEmpty(ProviderName)) + { + RedirectOnFail(); + return; + } + // BWFC: IsPostBack guard unwrapped — Blazor re-renders on every state change + var manager = Context.GetOwinContext().GetUserManager(); + var loginInfo = Context.GetOwinContext().Authentication.GetExternalLoginInfo(); + if (loginInfo == null) + { + RedirectOnFail(); + return; + } + var user = manager.Find(loginInfo.Login); + if (user != null) + { + IdentityHelper.SignIn(manager, user, isPersistent: false); + IdentityHelper.RedirectToReturnUrl(Request.QueryString["ReturnUrl"], Response); + } + else if (User.Identity.IsAuthenticated) + { + // Apply Xsrf check when linking + var verifiedloginInfo = Context.GetOwinContext().Authentication.GetExternalLoginInfo(IdentityHelper.XsrfKey, User.Identity.GetUserId()); + if (verifiedloginInfo == null) + { + RedirectOnFail(); + return; + } + + var result = manager.AddLogin(User.Identity.GetUserId(), verifiedloginInfo.Login); + if (result.Succeeded) + { + IdentityHelper.RedirectToReturnUrl(Request.QueryString["ReturnUrl"], Response); + } + else + { + AddErrors(result); + return; + } + } + else + { + email.Text = loginInfo.Email; + } + } + + protected void LogIn_Click() + { + CreateAndLoginUser(); + } + + private void CreateAndLoginUser() + { + if (!IsValid) + { + return; + } + var manager = Context.GetOwinContext().GetUserManager(); + var user = new ApplicationUser() { UserName = email.Text, Email = email.Text }; + IdentityResult result = manager.Create(user); + if (result.Succeeded) + { + var loginInfo = Context.GetOwinContext().Authentication.GetExternalLoginInfo(); + if (loginInfo == null) + { + RedirectOnFail(); + return; + } + result = manager.AddLogin(user.Id, loginInfo.Login); + if (result.Succeeded) + { + IdentityHelper.SignIn(manager, user, isPersistent: false); + + // For more information on how to enable account confirmation and password reset please visit http://go.microsoft.com/fwlink/?LinkID=320771 + // var code = manager.GenerateEmailConfirmationToken(user.Id); + // Send this link via email: IdentityHelper.GetUserConfirmationRedirectUrl(code, user.Id) + + IdentityHelper.RedirectToReturnUrl(Request.QueryString["ReturnUrl"], Response); + return; + } + } + AddErrors(result); + } + + private void AddErrors(IdentityResult result) + { + foreach (var error in result.Errors) + { + ModelState.AddModelError("", error); + } + } } -} +} \ No newline at end of file diff --git a/samples/AfterWingtipToys/Account/ResetPassword.razor b/samples/AfterWingtipToys/Account/ResetPassword.razor index a9b62fa5d..0390f7299 100644 --- a/samples/AfterWingtipToys/Account/ResetPassword.razor +++ b/samples/AfterWingtipToys/Account/ResetPassword.razor @@ -1,8 +1,8 @@ @page "/ResetPassword" Reset Password -

@(Title).

+

@(Title).

- +

@@ -12,7 +12,7 @@
- +
@@ -20,7 +20,7 @@
- +
@@ -28,7 +28,7 @@
- +
-
- diff --git a/samples/AfterWingtipToys/Account/ResetPassword.razor.cs b/samples/AfterWingtipToys/Account/ResetPassword.razor.cs index a5d0f06fd..b5fd4929a 100644 --- a/samples/AfterWingtipToys/Account/ResetPassword.razor.cs +++ b/samples/AfterWingtipToys/Account/ResetPassword.razor.cs @@ -1,13 +1,83 @@ -namespace WingtipToys.Account; +// ============================================================================= +// TODO(bwfc-general): This code-behind was copied from Web Forms and needs manual migration. +// +// Common transforms needed (use the BWFC Copilot skill for assistance): +// TODO(bwfc-lifecycle): Page_Load / Page_Init → OnInitializedAsync / OnParametersSetAsync +// TODO(bwfc-lifecycle): Page_PreRender → OnAfterRenderAsync +// TODO(bwfc-ispostback): IsPostBack checks → remove or convert to state logic +// TODO(bwfc-viewstate): ViewState usage → component [Parameter] or private fields +// TODO(bwfc-session-state): Session/Cache access → auto-wired on WebFormsPageBase via SessionShim/CacheShim +// TODO(bwfc-navigation): Response.Redirect → auto-wired on WebFormsPageBase via ResponseShim +// TODO(bwfc-form): Request.Form["key"] → auto-wired on WebFormsPageBase via FormShim (use for interactive mode) +// TODO(bwfc-server): Server.MapPath/HtmlEncode → auto-wired on WebFormsPageBase via ServerShim +// TODO(bwfc-config): ConfigurationManager.AppSettings → BWFC shim (call app.UseConfigurationManagerShim() in Program.cs) +// TODO(bwfc-general): ClientScript.RegisterStartupScript → auto-wired on WebFormsPageBase via ClientScriptShim +// TODO(bwfc-general): Event handlers (Button_Click, etc.) → convert to Blazor event callbacks +// TODO(bwfc-datasource): Data binding (DataBind, DataSource) → component parameters or OnInitialized +// TODO(bwfc-general): ScriptManager code-behind references → use ScriptManagerShim via ScriptManager.GetCurrent(this) +// TODO(bwfc-general): UpdatePanel markup preserved by BWFC (ContentTemplate supported) — remove only code-behind API calls +// TODO(bwfc-general): User controls → Blazor component references +// ============================================================================= +using System; +using System.Linq; +using WingtipToys.Models; -// TODO: Requires ASP.NET Core Identity migration — password reset needs UserManager -public partial class ResetPassword -{ - private string Email { get; set; } = ""; - private string Password { get; set; } = ""; +using BlazorWebFormsComponents.Identity; - private void Reset_Click(EventArgs e) +namespace WingtipToys.Account +{ + public partial class ResetPassword { - // TODO: Implement with UserManager + // TODO(bwfc-general): ClientScript calls preserved — works via WebFormsPageBase (no injection needed). ScriptManagerShim may need @inject ScriptManagerShim ScriptManager for non-page classes. + + // --- Request.Form Migration --- + // TODO(bwfc-form): Request.Form calls work automatically via RequestShim on WebFormsPageBase. + // For interactive mode, wrap your form in . + // Form keys found: key + // For non-page classes, inject RequestShim via DI. + + // --- Response.Redirect Migration --- + // TODO(bwfc-navigation): Response.Redirect() works via ResponseShim on WebFormsPageBase. Handles ~/ and .aspx automatically. + // For non-page classes, inject ResponseShim via DI. + + private TextBox ConfirmPassword = default!; + private TextBox Email = default!; + private Literal ErrorMessage = default!; + private TextBox Password = default!; + // --- ConfigurationManager Migration --- + // TODO(bwfc-config): ConfigurationManager calls work via BWFC shim. + // Ensure app.UseConfigurationManagerShim() is called in Program.cs. + + protected string StatusMessage + { + get; + private set; + } + + protected void Reset_Click() + { + string code = IdentityHelper.GetCodeFromRequest(Request); + if (code != null) + { + var manager = Context.GetOwinContext().GetUserManager(); + + var user = manager.FindByName(Email.Text); + if (user == null) + { + ErrorMessage.Text = "No user found"; + return; + } + var result = manager.ResetPassword(user.Id, code, Password.Text); + if (result.Succeeded) + { + Response.Redirect("~/Account/ResetPasswordConfirmation"); + return; + } + ErrorMessage.Text = result.Errors.FirstOrDefault(); + return; + } + + ErrorMessage.Text = "An error has occurred"; + } } -} +} \ No newline at end of file diff --git a/samples/AfterWingtipToys/Account/ResetPasswordConfirmation.razor b/samples/AfterWingtipToys/Account/ResetPasswordConfirmation.razor index a11d58978..f2880e4f6 100644 --- a/samples/AfterWingtipToys/Account/ResetPasswordConfirmation.razor +++ b/samples/AfterWingtipToys/Account/ResetPasswordConfirmation.razor @@ -1,7 +1,6 @@ @page "/ResetPasswordConfirmation" Password Changed -

@(Title).

+

@(Title).

-

Your password has been changed. Click here to login

+

Your password has been changed. Click here to login

- diff --git a/samples/AfterWingtipToys/Account/ResetPasswordConfirmation.razor.cs b/samples/AfterWingtipToys/Account/ResetPasswordConfirmation.razor.cs index 8d7866ec9..8d60b8d01 100644 --- a/samples/AfterWingtipToys/Account/ResetPasswordConfirmation.razor.cs +++ b/samples/AfterWingtipToys/Account/ResetPasswordConfirmation.razor.cs @@ -1,6 +1,39 @@ -namespace WingtipToys.Account; - -// TODO: Requires ASP.NET Core Identity migration — password reset confirmation page -public partial class ResetPasswordConfirmation +// ============================================================================= +// TODO(bwfc-general): This code-behind was copied from Web Forms and needs manual migration. +// +// Common transforms needed (use the BWFC Copilot skill for assistance): +// TODO(bwfc-lifecycle): Page_Load / Page_Init → OnInitializedAsync / OnParametersSetAsync +// TODO(bwfc-lifecycle): Page_PreRender → OnAfterRenderAsync +// TODO(bwfc-ispostback): IsPostBack checks → remove or convert to state logic +// TODO(bwfc-viewstate): ViewState usage → component [Parameter] or private fields +// TODO(bwfc-session-state): Session/Cache access → auto-wired on WebFormsPageBase via SessionShim/CacheShim +// TODO(bwfc-navigation): Response.Redirect → auto-wired on WebFormsPageBase via ResponseShim +// TODO(bwfc-form): Request.Form["key"] → auto-wired on WebFormsPageBase via FormShim (use for interactive mode) +// TODO(bwfc-server): Server.MapPath/HtmlEncode → auto-wired on WebFormsPageBase via ServerShim +// TODO(bwfc-config): ConfigurationManager.AppSettings → BWFC shim (call app.UseConfigurationManagerShim() in Program.cs) +// TODO(bwfc-general): ClientScript.RegisterStartupScript → auto-wired on WebFormsPageBase via ClientScriptShim +// TODO(bwfc-general): Event handlers (Button_Click, etc.) → convert to Blazor event callbacks +// TODO(bwfc-datasource): Data binding (DataBind, DataSource) → component parameters or OnInitialized +// TODO(bwfc-general): ScriptManager code-behind references → use ScriptManagerShim via ScriptManager.GetCurrent(this) +// TODO(bwfc-general): UpdatePanel markup preserved by BWFC (ContentTemplate supported) — remove only code-behind API calls +// TODO(bwfc-general): User controls → Blazor component references +// ============================================================================= +namespace WingtipToys.Account { -} + public partial class ResetPasswordConfirmation + { + // TODO(bwfc-general): ClientScript calls preserved — works via WebFormsPageBase (no injection needed). ScriptManagerShim may need @inject ScriptManagerShim ScriptManager for non-page classes. + + // --- Request.Form Migration --- + // TODO(bwfc-form): Request.Form calls work automatically via RequestShim on WebFormsPageBase. + // For interactive mode, wrap your form in . + // Form keys found: key + // For non-page classes, inject RequestShim via DI. + + private HyperLink login = default!; + // --- ConfigurationManager Migration --- + // TODO(bwfc-config): ConfigurationManager calls work via BWFC shim. + // Ensure app.UseConfigurationManagerShim() is called in Program.cs. + + } +} \ No newline at end of file diff --git a/samples/AfterWingtipToys/Account/TwoFactorAuthenticationSignIn.razor b/samples/AfterWingtipToys/Account/TwoFactorAuthenticationSignIn.razor index e828f889b..28a7dfd9c 100644 --- a/samples/AfterWingtipToys/Account/TwoFactorAuthenticationSignIn.razor +++ b/samples/AfterWingtipToys/Account/TwoFactorAuthenticationSignIn.razor @@ -1,49 +1,48 @@ @page "/TwoFactorAuthenticationSignIn" Two-Factor Authentication -

@(Title).

- +

@(Title).

+

Send verification code


Select Two-Factor Authentication Provider: - + -
- +

Enter verification code


- - + +

- +

-
- diff --git a/samples/AfterWingtipToys/Account/TwoFactorAuthenticationSignIn.razor.cs b/samples/AfterWingtipToys/Account/TwoFactorAuthenticationSignIn.razor.cs index 9cc2bcf67..d9f3d296e 100644 --- a/samples/AfterWingtipToys/Account/TwoFactorAuthenticationSignIn.razor.cs +++ b/samples/AfterWingtipToys/Account/TwoFactorAuthenticationSignIn.razor.cs @@ -1,15 +1,124 @@ -namespace WingtipToys.Account; +// ============================================================================= +// TODO(bwfc-general): This code-behind was copied from Web Forms and needs manual migration. +// +// Common transforms needed (use the BWFC Copilot skill for assistance): +// TODO(bwfc-lifecycle): Page_Load / Page_Init → OnInitializedAsync / OnParametersSetAsync +// TODO(bwfc-lifecycle): Page_PreRender → OnAfterRenderAsync +// TODO(bwfc-ispostback): IsPostBack checks → remove or convert to state logic +// TODO(bwfc-viewstate): ViewState usage → component [Parameter] or private fields +// TODO(bwfc-session-state): Session/Cache access → auto-wired on WebFormsPageBase via SessionShim/CacheShim +// TODO(bwfc-navigation): Response.Redirect → auto-wired on WebFormsPageBase via ResponseShim +// TODO(bwfc-form): Request.Form["key"] → auto-wired on WebFormsPageBase via FormShim (use for interactive mode) +// TODO(bwfc-server): Server.MapPath/HtmlEncode → auto-wired on WebFormsPageBase via ServerShim +// TODO(bwfc-config): ConfigurationManager.AppSettings → BWFC shim (call app.UseConfigurationManagerShim() in Program.cs) +// TODO(bwfc-general): ClientScript.RegisterStartupScript → auto-wired on WebFormsPageBase via ClientScriptShim +// TODO(bwfc-general): Event handlers (Button_Click, etc.) → convert to Blazor event callbacks +// TODO(bwfc-datasource): Data binding (DataBind, DataSource) → component parameters or OnInitialized +// TODO(bwfc-general): ScriptManager code-behind references → use ScriptManagerShim via ScriptManager.GetCurrent(this) +// TODO(bwfc-general): UpdatePanel markup preserved by BWFC (ContentTemplate supported) — remove only code-behind API calls +// TODO(bwfc-general): User controls → Blazor component references +// ============================================================================= +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using WingtipToys.Models; -// TODO: Requires ASP.NET Core Identity migration — 2FA sign-in needs SignInManager/UserManager -public partial class TwoFactorAuthenticationSignIn +using BlazorWebFormsComponents.Identity; + +namespace WingtipToys.Account { - private void ProviderSubmit_Click(EventArgs e) + public partial class TwoFactorAuthenticationSignIn { - // TODO: Implement with SignInManager - } + // TODO(bwfc-general): ClientScript calls preserved — works via WebFormsPageBase (no injection needed). ScriptManagerShim may need @inject ScriptManagerShim ScriptManager for non-page classes. - private void CodeSubmit_Click(EventArgs e) - { - // TODO: Implement with SignInManager + // --- Request.Form Migration --- + // TODO(bwfc-form): Request.Form calls work automatically via RequestShim on WebFormsPageBase. + // For interactive mode, wrap your form in . + // Form keys found: key + // For non-page classes, inject RequestShim via DI. + + // TODO(bwfc-navigation): endResponse=true is silently ignored by ResponseShim. Code after redirect calls WILL execute (unlike Web Forms where it threw ThreadAbortException). + + // --- Response.Redirect Migration --- + // TODO(bwfc-navigation): Response.Redirect() works via ResponseShim on WebFormsPageBase. Handles ~/ and .aspx automatically. + // For non-page classes, inject ResponseShim via DI. + + private TextBox Code = default!; + private Button CodeSubmit = default!; + private PlaceHolder ErrorMessage = default!; + private Literal FailureText = default!; + private DropDownList Providers = default!; + private Button ProviderSubmit = default!; + private CheckBox RememberBrowser = default!; + private HiddenField SelectedProvider = default!; + private PlaceHolder sendcode = default!; + private PlaceHolder verifycode = default!; + // --- ConfigurationManager Migration --- + // TODO(bwfc-config): ConfigurationManager calls work via BWFC shim. + // Ensure app.UseConfigurationManagerShim() is called in Program.cs. + + private ApplicationSignInManager signinManager; + private ApplicationUserManager manager; + + public TwoFactorAuthenticationSignIn() + { + manager = Context.GetOwinContext().GetUserManager(); + signinManager = Context.GetOwinContext().GetUserManager(); + } + + protected override async Task OnInitializedAsync() + { + // TODO(bwfc-lifecycle): Review lifecycle conversion — verify async behavior + await base.OnInitializedAsync(); + + var userId = signinManager.GetVerifiedUserId(); + if (userId == null) + { + Response.Redirect("/Account/Error", true); + } + var userFactors = manager.GetValidTwoFactorProviders(userId); + Providers.DataSource = userFactors.Select(x => x).ToList(); + } + + protected void CodeSubmit_Click() + { + bool rememberMe = false; + bool.TryParse(Request.QueryString["RememberMe"], out rememberMe); + + var result = signinManager.TwoFactorSignIn(SelectedProvider.Value, Code.Text, isPersistent: rememberMe, rememberBrowser: RememberBrowser.Checked); + switch (result) + { + case SignInStatus.Success: + IdentityHelper.RedirectToReturnUrl(Request.QueryString["ReturnUrl"], Response); + break; + case SignInStatus.LockedOut: + Response.Redirect("/Account/Lockout"); + break; + case SignInStatus.Failure: + default: + FailureText.Text = "Invalid code"; + ErrorMessage.Visible = true; + break; + } + } + + protected void ProviderSubmit_Click() + { + if (!signinManager.SendTwoFactorCode(Providers.SelectedValue)) + { + Response.Redirect("/Account/Error"); + } + + var user = manager.FindById(signinManager.GetVerifiedUserId()); + if (user != null) + { + var code = manager.GenerateTwoFactorToken(user.Id, Providers.SelectedValue); + } + + SelectedProvider.Value = Providers.SelectedValue; + sendcode.Visible = false; + verifycode.Visible = true; + } } -} +} \ No newline at end of file diff --git a/samples/AfterWingtipToys/Account/VerifyPhoneNumber.razor b/samples/AfterWingtipToys/Account/VerifyPhoneNumber.razor index 9fbd09a26..a687a786e 100644 --- a/samples/AfterWingtipToys/Account/VerifyPhoneNumber.razor +++ b/samples/AfterWingtipToys/Account/VerifyPhoneNumber.razor @@ -1,27 +1,26 @@ @page "/VerifyPhoneNumber" Verify Phone Number -

@(Title).

+

@(Title).

- +

Enter verification code


- +
- +
-
- diff --git a/samples/AfterWingtipToys/Account/VerifyPhoneNumber.razor.cs b/samples/AfterWingtipToys/Account/VerifyPhoneNumber.razor.cs index 6882933dd..0aa120495 100644 --- a/samples/AfterWingtipToys/Account/VerifyPhoneNumber.razor.cs +++ b/samples/AfterWingtipToys/Account/VerifyPhoneNumber.razor.cs @@ -1,10 +1,87 @@ -namespace WingtipToys.Account; +// ============================================================================= +// TODO(bwfc-general): This code-behind was copied from Web Forms and needs manual migration. +// +// Common transforms needed (use the BWFC Copilot skill for assistance): +// TODO(bwfc-lifecycle): Page_Load / Page_Init → OnInitializedAsync / OnParametersSetAsync +// TODO(bwfc-lifecycle): Page_PreRender → OnAfterRenderAsync +// TODO(bwfc-ispostback): IsPostBack checks → remove or convert to state logic +// TODO(bwfc-viewstate): ViewState usage → component [Parameter] or private fields +// TODO(bwfc-session-state): Session/Cache access → auto-wired on WebFormsPageBase via SessionShim/CacheShim +// TODO(bwfc-navigation): Response.Redirect → auto-wired on WebFormsPageBase via ResponseShim +// TODO(bwfc-form): Request.Form["key"] → auto-wired on WebFormsPageBase via FormShim (use for interactive mode) +// TODO(bwfc-server): Server.MapPath/HtmlEncode → auto-wired on WebFormsPageBase via ServerShim +// TODO(bwfc-config): ConfigurationManager.AppSettings → BWFC shim (call app.UseConfigurationManagerShim() in Program.cs) +// TODO(bwfc-general): ClientScript.RegisterStartupScript → auto-wired on WebFormsPageBase via ClientScriptShim +// TODO(bwfc-general): Event handlers (Button_Click, etc.) → convert to Blazor event callbacks +// TODO(bwfc-datasource): Data binding (DataBind, DataSource) → component parameters or OnInitialized +// TODO(bwfc-general): ScriptManager code-behind references → use ScriptManagerShim via ScriptManager.GetCurrent(this) +// TODO(bwfc-general): UpdatePanel markup preserved by BWFC (ContentTemplate supported) — remove only code-behind API calls +// TODO(bwfc-general): User controls → Blazor component references +// ============================================================================= +using System; +using System.Collections.Generic; +using System.Linq; +using BlazorWebFormsComponents.Identity; -// TODO: Requires ASP.NET Core Identity migration — phone verification needs UserManager -public partial class VerifyPhoneNumber +namespace WingtipToys.Account { - private void Code_Click(EventArgs e) + public partial class VerifyPhoneNumber { - // TODO: Implement with UserManager + // TODO(bwfc-general): ClientScript calls preserved — works via WebFormsPageBase (no injection needed). ScriptManagerShim may need @inject ScriptManagerShim ScriptManager for non-page classes. + + // --- Request.Form Migration --- + // TODO(bwfc-form): Request.Form calls work automatically via RequestShim on WebFormsPageBase. + // For interactive mode, wrap your form in . + // Form keys found: key + // For non-page classes, inject RequestShim via DI. + + // --- Response.Redirect Migration --- + // TODO(bwfc-navigation): Response.Redirect() works via ResponseShim on WebFormsPageBase. Handles ~/ and .aspx automatically. + // For non-page classes, inject ResponseShim via DI. + + private TextBox Code = default!; + private Literal ErrorMessage = default!; + private HiddenField PhoneNumber = default!; + // --- ConfigurationManager Migration --- + // TODO(bwfc-config): ConfigurationManager calls work via BWFC shim. + // Ensure app.UseConfigurationManagerShim() is called in Program.cs. + + protected override async Task OnInitializedAsync() + { + // TODO(bwfc-lifecycle): Review lifecycle conversion — verify async behavior + await base.OnInitializedAsync(); + + var manager = Context.GetOwinContext().GetUserManager(); + var phonenumber = Request.QueryString["PhoneNumber"]; + var code = manager.GenerateChangePhoneNumberToken(User.Identity.GetUserId(), phonenumber); + PhoneNumber.Value = phonenumber; + } + + protected void Code_Click() + { + if (!ModelState.IsValid) + { + ModelState.AddModelError("", "Invalid code"); + return; + } + + var manager = Context.GetOwinContext().GetUserManager(); + + var result = manager.ChangePhoneNumber(User.Identity.GetUserId(), PhoneNumber.Value, Code.Text); + + if (result.Succeeded) + { + var user = manager.FindById(User.Identity.GetUserId()); + + if (user != null) + { + IdentityHelper.SignIn(manager, user, false); + Response.Redirect("/Account/Manage?m=AddPhoneNumberSuccess"); + } + } + + // If we got this far, something failed, redisplay form + ModelState.AddModelError("", "Failed to verify phone"); + } } -} +} \ No newline at end of file diff --git a/samples/AfterWingtipToys/AddToCart.razor b/samples/AfterWingtipToys/AddToCart.razor index 9922f2946..43e05ee0c 100644 --- a/samples/AfterWingtipToys/AddToCart.razor +++ b/samples/AfterWingtipToys/AddToCart.razor @@ -1,53 +1,4 @@ @page "/AddToCart" -@using Microsoft.EntityFrameworkCore - -@inject IDbContextFactory DbFactory -@inject IHttpContextAccessor HttpContextAccessor - -@code { - [SupplyParameterFromQuery(Name = "productID")] - public int? ProductID { get; set; } - - protected override async Task OnInitializedAsync() - { - if (ProductID.HasValue && ProductID > 0) - { - using var db = DbFactory.CreateDbContext(); - var cartId = GetCartId(); - var existingItem = await db.ShoppingCartItems - .FirstOrDefaultAsync(c => c.CartId == cartId && c.ProductId == ProductID.Value); - - if (existingItem != null) - { - existingItem.Quantity++; - } - else - { - var cartItem = new CartItem - { - ItemId = Guid.NewGuid().ToString(), - CartId = cartId, - ProductId = ProductID.Value, - Quantity = 1, - DateCreated = DateTime.Now - }; - db.ShoppingCartItems.Add(cartItem); - } - await db.SaveChangesAsync(); - } - - HttpContextAccessor.HttpContext?.Response.Redirect("/ShoppingCart"); - } - - private string GetCartId() - { - var httpContext = HttpContextAccessor.HttpContext; - if (httpContext?.Request.Cookies.TryGetValue("CartId", out var cartId) == true && !string.IsNullOrEmpty(cartId)) - return cartId; - - var newCartId = Guid.NewGuid().ToString(); - httpContext?.Response.Cookies.Append("CartId", newCartId, new CookieOptions { Expires = DateTimeOffset.Now.AddDays(30) }); - return newCartId; - } -} +Add To Cart +

Adding item to your cart...

diff --git a/samples/AfterWingtipToys/AddToCart.razor.cs b/samples/AfterWingtipToys/AddToCart.razor.cs index c2ba2ab02..b4eb330a6 100644 --- a/samples/AfterWingtipToys/AddToCart.razor.cs +++ b/samples/AfterWingtipToys/AddToCart.razor.cs @@ -1,10 +1,27 @@ using Microsoft.AspNetCore.Components; -using Microsoft.EntityFrameworkCore; -using WingtipToys.Models; +using WingtipToys.Services; namespace WingtipToys; public partial class AddToCart { - // Code is in the @code block of AddToCart.razor + [Inject] private CartService Cart { get; set; } = default!; + private bool _handled; + + protected override void OnParametersSet() + { + if (_handled) + { + return; + } + + _handled = true; + + if (int.TryParse(Request.QueryString["productID"], out var productId)) + { + Cart.AddItem(productId); + } + + Response.Redirect("/ShoppingCart"); + } } diff --git a/samples/AfterWingtipToys/Admin/AdminPage.razor b/samples/AfterWingtipToys/Admin/AdminPage.razor index e02504389..5f45d2186 100644 --- a/samples/AfterWingtipToys/Admin/AdminPage.razor +++ b/samples/AfterWingtipToys/Admin/AdminPage.razor @@ -1,65 +1,65 @@ -@page "/Admin/AdminPage" -

Administration

+@page "/AdminPage" + +

Administration


Add Product:

- + - + - + - + - +
-
- - + +
- - + +
- - - + + +
- - + +

-