using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Options;
using Owl.reCAPTCHA;
using System;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.Account.ExternalProviders;
using Volo.Abp.Account.Public.Web;
using Volo.Abp.Account.Public.Web.Pages.Account;
using Volo.Abp.Account.Security.Recaptcha;
using Volo.Abp.AspNetCore.MultiTenancy;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Identity;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Security.Claims;
using Volo.Abp.Ui.Branding;
using Volo.Saas.Tenants;
namespace Hon.IFS.Admin.Pages.Account
{
    [ExposeServices(typeof(CustomLoginModel), typeof(LoginModel))]
    public class CustomLoginModel : LoginModel
    {
        private readonly ITenantRepository _tenantRepository;
        public  AdminBrandingProvider _adminBrandingProvider;
        public DefaultBrandingProvider _defaultBrandingProvider;
        public IIdentityUserRepository _identityUserRepository;
        public CustomLoginModel(
          IAuthenticationSchemeProvider schemeProvider,
          IOptions<AbpAccountOptions> accountOptions,
          IAccountExternalProviderAppService accountExternalProviderAppService,
          ICurrentPrincipalAccessor currentPrincipalAccessor,
          IAbpRecaptchaValidatorFactory recaptchaValidatorFactory,
          IOptions<IdentityOptions> identityOptions,
          IOptionsSnapshot<reCAPTCHAOptions> reCaptchaOptions,
          ITenantRepository tenantRepository,
          AdminBrandingProvider adminBrandingProvider,
          DefaultBrandingProvider defaultBrandingProvider,
          IIdentityUserRepository identityUserRepository)
          : base(schemeProvider,
            accountOptions,
            recaptchaValidatorFactory,
            accountExternalProviderAppService,
            currentPrincipalAccessor,
            identityOptions,
            reCaptchaOptions)
        {
            _tenantRepository = tenantRepository;
            _adminBrandingProvider = adminBrandingProvider;
            _defaultBrandingProvider = defaultBrandingProvider;
            _identityUserRepository = identityUserRepository;
        }
        public override async Task<IActionResult> OnGetAsync()
        {
            // Decode the entire query string to handle encoded parameters
            var decodedQueryString = Uri.UnescapeDataString(Request.QueryString.ToString());
            // Log the decoded query string for debugging purposes
            System.Diagnostics.Debug.WriteLine(decodedQueryString);
            // Parse the decoded query string manually to extract parameters
            var queryParams = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(decodedQueryString);
            if (queryParams.TryGetValue("tenantKey", out var resolvedTenant))
            {
                _adminBrandingProvider.SetAppName(resolvedTenant.ToString());
                ViewData["sathavva"] = resolvedTenant; 
            }
            return await base.OnGetAsync();
        }
        public override async Task<IActionResult> OnPostAsync(string action)
        {
            // Decode the entire query string to handle encoded parameters
            var decodedQueryString = Uri.UnescapeDataString(Request.QueryString.ToString());
            // Extract the username and password from the posted form data
            var username = Request.Form["LoginInput.UserNameOrEmailAddress"].ToString();
            var password = Request.Form["LoginInput.Password"].ToString(); 
                    // Log the decoded query string for debugging purposes
                    System.Diagnostics.Debug.WriteLine(decodedQueryString);
            // Parse the decoded query string manually to extract parameters
            var queryParams = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(decodedQueryString);
            // Extract the tenantKey from the query parameters
            if (!queryParams.TryGetValue("tenantKey", out var tenantKey))
            {
                //return BadRequest("Tenant key is not provided in the query parameters.");
            }
          //  var resolvedUser = await _identityUserRepository.FindAsync()
            var resolvedTenant = await _tenantRepository.FindByNameAsync(tenantKey);
           // var tenantIdOrName = resolvedTenantId.TenantIdOrName;
            if (resolvedTenant == null)
            {
                // Handle the case where the tenant could not be resolved (e.g., return an error view or redirect).
                //return NotFound("Tenant could not be resolved.");
            }
            // Step 3: Switch tenant and call base login logic with error handling
            try
            {
                using (CurrentTenant.Change(resolvedTenant?.Id, resolvedTenant?.Name ?? "Default"))
                {
                    System.Diagnostics.Debug.WriteLine(CurrentTenant?.Name);
                    // Check ModelState before proceeding (e.g., from data annotations)
                    if (!ModelState.IsValid)
                    {
                        var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage);
                        System.Diagnostics.Debug.WriteLine("Pre-Login Validation Errors: " + string.Join(", ", errors));
                        return Page();
                    }
                    // Call base method with try-catch for additional safety
                    var result = await base.OnPostAsync(action);
                    // Check if base method added errors to ModelState (e.g., invalid credentials)
                    if (!ModelState.IsValid)
                    {
                        var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage);
                        System.Diagnostics.Debug.WriteLine("Post-Login Errors: " + string.Join(", ", errors));
                        return Page();
                    }
                    return result; // Success: redirect or other action result
                }
            }
            catch (Exception ex)
            {
                // Handle exceptions from base login or tenant context switching
                System.Diagnostics.Debug.WriteLine($"Login Processing Error: {ex.Message}");
                ModelState.AddModelError("", "An unexpected error occurred during login. Please try again.");
                return Page();
            }
            //using (CurrentTenant.Change(resolvedTenant?.Id, resolvedTenant?.Name))
            //{
            //    System.Diagnostics.Debug.WriteLine(CurrentTenant?.Name);
            //    // Call base method and handle its result
            //    var result = await base.OnPostAsync(action);
            //    // Check if base method added errors to ModelState
            //    if (!ModelState.IsValid)
            //    {
            //        var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage);
            //        System.Diagnostics.Debug.WriteLine("Post-Login Errors: " + string.Join(", ", errors));
            //        return Page();
            //    }
            //    return result;
            //}
        }
    }
}
                        7 Answer(s)
- 
    0
hi
You can use
Alerts.Warning(L["Message"]);.try { //... } catch (Exception ex) { // Handle exceptions from base login or tenant context switching System.Diagnostics.Debug.WriteLine($"Login Processing Error: {ex.Message}"); ModelState.AddModelError("", "An unexpected error occurred during login. Please try again."); Alerts.Warning(L["Message"]); return Page(); } - 
    0
public override async Task<IActionResult> OnPostAsync(string action) { // Decode the entire query string to handle encoded parameters var decodedQueryString = Uri.UnescapeDataString(Request.QueryString.ToString()); // Extract the username and password from the posted form data var username = Request.Form["LoginInput.UserNameOrEmailAddress"].ToString(); var password = Request.Form["LoginInput.Password"].ToString(); // Log the decoded query string for debugging purposes System.Diagnostics.Debug.WriteLine(decodedQueryString); // Parse the decoded query string manually to extract parameters var queryParams = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(decodedQueryString); // Extract the tenantKey from the query parameters if (!queryParams.TryGetValue("tenantKey", out var tenantKey)) { //return BadRequest("Tenant key is not provided in the query parameters."); } // var resolvedUser = await _identityUserRepository.FindAsync() var resolvedTenant = await _tenantRepository.FindByNameAsync(tenantKey); // var tenantIdOrName = resolvedTenantId.TenantIdOrName; if (resolvedTenant == null) { // Handle the case where the tenant could not be resolved (e.g., return an error view or redirect). //return NotFound("Tenant could not be resolved."); } // Step 3: Switch tenant and call base login logic with error handling try { using (CurrentTenant.Change(resolvedTenant?.Id, resolvedTenant?.Name ?? "Default")) { System.Diagnostics.Debug.WriteLine(CurrentTenant?.Name); // Check ModelState before proceeding (e.g., from data annotations) if (!ModelState.IsValid) { var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage); System.Diagnostics.Debug.WriteLine("Pre-Login Validation Errors: " + string.Join(", ", errors)); return Page(); } // Call base method with try-catch for additional safety var result = await base.OnPostAsync(action); // Check if base method added errors to ModelState (e.g., invalid credentials) if (!ModelState.IsValid) { var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage); System.Diagnostics.Debug.WriteLine("Post-Login Errors: " + string.Join(", ", errors)); return Page(); } return result; // Success: redirect or other action result } } catch (Exception ex) { // Handle exceptions from base login or tenant context switching System.Diagnostics.Debug.WriteLine($"Login Processing Error: {ex.Message}"); ModelState.AddModelError("", "An unexpected error occurred during login. Please try again."); return Page(); } //using (CurrentTenant.Change(resolvedTenant?.Id, resolvedTenant?.Name)) //{ // System.Diagnostics.Debug.WriteLine(CurrentTenant?.Name); // // Call base method and handle its result // var result = await base.OnPostAsync(action); // // Check if base method added errors to ModelState // if (!ModelState.IsValid) // { // var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage); // System.Diagnostics.Debug.WriteLine("Post-Login Errors: " + string.Join(", ", errors)); // return Page(); // } // return result; //} } } }Here in the OnPost method. I am not getting Any Exception when I am logging with incorrect password or username
 - 
    0
 - 
    0
I want to customize this login page when I am typing incorrect password, I am not getting any error like "Invalid username or password" Alert and it not catching any exception as well. I am following this documentation https://abp.io/docs/2.8/How-To/Customize-Login-Page-MVC Below I am providing my complete code login.chtml @page @using Hon.IFS.Admin @using Microsoft.AspNetCore.Mvc.Localization @using Volo.Abp.Account.Localization @using Volo.Abp.Account.Settings @using Volo.Abp.Account.Web.Pages.Account @using Volo.Abp.AspNetCore.Mvc.UI.Theming @using Volo.Abp.Identity @using Volo.Abp.Settings @model Hon.IFS.Admin.Pages.Account.CustomLoginModel @inject IHtmlLocalizer<AccountResource> L @inject IThemeManager ThemeManager @inject Volo.Abp.Settings.ISettingProvider SettingProvider @inject AdminBrandingProvider adminBrandingProvider
@{ Layout = ThemeManager.CurrentTheme.GetAccountLayout(); }
@section scripts { <abp-script-bundle name="@typeof(Hon.IFS.Admin.Pages.Account.CustomLoginModel).FullName"> <abp-script src="/Pages/Account/Login.js" /> </abp-script-bundle> <script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-3.5.1.min.js"></script> <script src="https://ajax.aspnetcdn.com/ajax/jquery.unobtrusive-ajax/3.2.6/jquery.unobtrusive-ajax.min.js"></script> }
<style> .card-padding { padding: 0.5rem !important; /* Adjusted back to 0.5rem for consistency */ }
.full-height { min-height: 100vh; /* Kept at 100vh for full height */ display: flex; align-items: flex-start; /* Move content to the top */ padding-top: 50px; /* Adds space from the top to shift content down slightly */ } .static-content { padding-top: 40px; padding-right: 20px; } .red-button { background-color: red !important; border-color: red !important; color: white !important; } .form-check-input.small-switch { width: 2em; /* Adjust width */ height: 1em; /* Adjust height */ }</style>
<div class="row" style="padding-top: 20px; padding-left: 20px"> <div class="col-12 col-md-6 static-content"> <h2 style="color: red">STAY</h2> <h2 style="color: red">CONNECTED</h2> <h2 style="color: white">STAY</h2> <h2 style="color: white">PROTECTED</h2> </div>
<div class="col-12 col-md-5 align-content-end pe-md-5"> <div style="display: flex; justify-content: center; align-items: center;"> <h3 style="color: white">@adminBrandingProvider.AppName</h3> </div> <div class="card shadow-sm rounded card-padding" style="max-height: 400px; max-width: 600px;"> <div class="card-body p-3"> <h3 class="text-center mb-3">@L["Login"]</h3> <form method="post" class="mt-1"> <div class="mb-1"> <label asp-for="LoginInput.UserNameOrEmailAddress" class="form-label"></label> <input asp-for="LoginInput.UserNameOrEmailAddress" class="form-control form-control-sm" /> <span asp-validation-for="LoginInput.UserNameOrEmailAddress" class="text-danger"></span> </div> <div class="mb-2"> <label asp-for="LoginInput.Password" class="form-label"></label> <div class="input-group"> <input type="password" class="form-control form-control-sm" autocomplete="new-password" maxlength="@IdentityUserConsts.MaxPasswordLength" asp-for="LoginInput.Password" /> </div> <span asp-validation-for="LoginInput.Password" class="text-danger"></span> </div> <div class="row"> <div class="col"> <div class="form-check form-switch mb-3"> <input type="checkbox" class="form-check-input" id="LoginInput_RememberMe" name="LoginInput.RememberMe" value="true" /> <label class="form-check-label " for="LoginInput_RememberMe">Remember Me</label> </div> </div> <div class="col text-end"> <a href="@Url.Page("./ForgotPassword", new { returnUrl = Model.ReturnUrl, returnUrlHash = Model.ReturnUrlHash })">@L["ForgotPassword"]</a> </div> </div> <div class="d-grid gap-1"> <abp-button type="submit" button-type="Primary" name="Action" value="Login" class="btn-sm mt-2 red-button">@L["Login"]</abp-button> @if (Model.ShowCancelButton) { <abp-button type="submit" button-type="Secondary" formnovalidate="formnovalidate" name="Action" value="Cancel" class="btn-sm mt-2">@L["Cancel"]</abp-button> } </div> </form> @if (Model.VisibleExternalProviders.Any()) { <div class="mt-1"> <h4>@L["UseAnotherServiceToLogIn"]</h4> <form asp-page="./Login" asp-page-handler="ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" asp-route-returnUrlHash="@Model.ReturnUrlHash" method="post"> @foreach (var provider in Model.VisibleExternalProviders) { <button type="submit" class="btn btn-primary btn-sm m-1" name="provider" value="@provider.AuthenticationScheme" title="@L["GivenTenantIsNotAvailable", provider.DisplayName]">@provider.DisplayName</button> } </form> </div> } @* @if (!Model.EnableLocalLogin && !Model.VisibleExternalProviders.Any()) { <div class="alert alert-warning"> <strong>@L["InvalidLoginRequest"]</strong> @L["ThereAreNoLoginSchemesConfiguredForThisClient"] </div> } *@ </div> </div> </div></div>
login.chtml.cs using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.Options; using Owl.reCAPTCHA; using System; using System.Linq; using System.Threading.Tasks; using Volo.Abp.Account.ExternalProviders; using Volo.Abp.Account.Public.Web; using Volo.Abp.Account.Public.Web.Pages.Account; using Volo.Abp.Account.Security.Recaptcha; using Volo.Abp.AspNetCore.MultiTenancy; using Volo.Abp.DependencyInjection; using Volo.Abp.Identity; using Volo.Abp.MultiTenancy; using Volo.Abp.Security.Claims; using Volo.Abp.Ui.Branding; using Volo.Saas.Tenants;
namespace Hon.IFS.Admin.Pages.Account { [ExposeServices(typeof(CustomLoginModel), typeof(LoginModel))] public class CustomLoginModel : LoginModel { private readonly ITenantRepository _tenantRepository; public AdminBrandingProvider _adminBrandingProvider; public DefaultBrandingProvider _defaultBrandingProvider; public IIdentityUserRepository _identityUserRepository; public CustomLoginModel( IAuthenticationSchemeProvider schemeProvider, IOptions<AbpAccountOptions> accountOptions, IAccountExternalProviderAppService accountExternalProviderAppService, ICurrentPrincipalAccessor currentPrincipalAccessor, IAbpRecaptchaValidatorFactory recaptchaValidatorFactory, IOptions<IdentityOptions> identityOptions, IOptionsSnapshot<reCAPTCHAOptions> reCaptchaOptions, ITenantRepository tenantRepository, AdminBrandingProvider adminBrandingProvider, DefaultBrandingProvider defaultBrandingProvider, IIdentityUserRepository identityUserRepository) : base(schemeProvider, accountOptions, recaptchaValidatorFactory, accountExternalProviderAppService, currentPrincipalAccessor, identityOptions, reCaptchaOptions) { _tenantRepository = tenantRepository; _adminBrandingProvider = adminBrandingProvider; _defaultBrandingProvider = defaultBrandingProvider; _identityUserRepository = identityUserRepository; } public override async Task<IActionResult> OnGetAsync() { // Decode the entire query string to handle encoded parameters var decodedQueryString = Uri.UnescapeDataString(Request.QueryString.ToString());
// Log the decoded query string for debugging purposes System.Diagnostics.Debug.WriteLine(decodedQueryString); // Parse the decoded query string manually to extract parameters var queryParams = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(decodedQueryString); if (queryParams.TryGetValue("tenantKey", out var resolvedTenant)) { _adminBrandingProvider.SetAppName(resolvedTenant.ToString()); ViewData["sathavva"] = resolvedTenant; } return await base.OnGetAsync(); } public override async Task<IActionResult> OnPostAsync(string action) { // Decode the entire query string to handle encoded parameters var decodedQueryString = Uri.UnescapeDataString(Request.QueryString.ToString()); // Extract the username and password from the posted form data var username = Request.Form["LoginInput.UserNameOrEmailAddress"].ToString(); var password = Request.Form["LoginInput.Password"].ToString(); // Log the decoded query string for debugging purposes System.Diagnostics.Debug.WriteLine(decodedQueryString); // Parse the decoded query string manually to extract parameters var queryParams = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(decodedQueryString); // Extract the tenantKey from the query parameters if (!queryParams.TryGetValue("tenantKey", out var tenantKey)) { //return BadRequest("Tenant key is not provided in the query parameters."); } // var resolvedUser = await _identityUserRepository.FindAsync() var resolvedTenant = await _tenantRepository.FindByNameAsync(tenantKey); // var tenantIdOrName = resolvedTenantId.TenantIdOrName; if (resolvedTenant == null) { // Handle the case where the tenant could not be resolved (e.g., return an error view or redirect). //return NotFound("Tenant could not be resolved."); } // Step 3: Switch tenant and call base login logic with error handling try { using (CurrentTenant.Change(resolvedTenant?.Id, resolvedTenant?.Name ?? "Default")) { System.Diagnostics.Debug.WriteLine(CurrentTenant?.Name); // Check ModelState before proceeding (e.g., from data annotations) if (!ModelState.IsValid) { var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage); System.Diagnostics.Debug.WriteLine("Pre-Login Validation Errors: " + string.Join(", ", errors)); return Page(); } // Call base method with try-catch for additional safety var result = await base.OnPostAsync(action); // Check if base method added errors to ModelState (e.g., invalid credentials) if (!ModelState.IsValid) { var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage); System.Diagnostics.Debug.WriteLine("Post-Login Errors: " + string.Join(", ", errors)); // Add custom error message if no specific model errors regarding the username/password ModelState.AddModelError("", "Invalid username or password."); return Page(); } return result; // Success: redirect or other action result } } catch (Exception ex) { // Handle exceptions from base login or tenant context switching System.Diagnostics.Debug.WriteLine($"Login Processing Error: {ex.Message}"); ModelState.AddModelError("", "An unexpected error occurred during login. Please try again."); Alerts.Warning(L["Message"]); return Page(); } //using (CurrentTenant.Change(resolvedTenant?.Id, resolvedTenant?.Name)) //{ // System.Diagnostics.Debug.WriteLine(CurrentTenant?.Name); // // Call base method and handle its result // var result = await base.OnPostAsync(action); // // Check if base method added errors to ModelState // if (!ModelState.IsValid) // { // var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage); // System.Diagnostics.Debug.WriteLine("Post-Login Errors: " + string.Join(", ", errors)); // return Page(); // } // return result; //} } }}
 - 
    0
hi
Can you share a test project with your custom code with liming.ma@volosoft.com?
Thanks
 - 
    0
@await Component.InvokeAsync(typeof(PageAlertsViewComponent))it resolved after adding this line in index.chtml
 - 
    0
Great. We also use it in theme layout.
 
