Activities of "Ravikumar-Morrawthu_HON"

Question

My ABP account to be unlocked and unable to login.

Team,

Need help in abp documentation for LiptonX Theme customization as per our style guide. Is there any documentation or video tutorials share the links with us. Waiting for the quick response on this as blocked with css customization.

Thanks and Regards, ABP Development Team

I am working with an ABP microservice template , and I want to extend the SaaS Tenant table by adding an extra property like TenantType.

I have tried using Object Extension features provided by ABP, and I've also created and applied EF Core migrations , but the new property does not appear in the UI (Angular front-end) .

Is it possible to extend the SaaS Tenant entity in the microservice template using object extensions and migrations?

If yes, please provide a step-by-step resolution on how to properly do this in the microservice architecture, including integration with the Angular UI

using Hon.IFS.SaasService.Enums;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.ObjectExtending;
using Volo.Abp.Threading;

namespace Hon.IFS.SaasService
{
    public static class IFSModuleExtensionConfigurator
    {
        private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner();
        public static void Configure()
        {
            OneTimeRunner.Run(() =>
            {
                ConfigureExistingProperties();
            });
        }
        private static void ConfigureExistingProperties()
        {
            ObjectExtensionManager.Instance.Modules()
                  .ConfigureSaas(saas =>
                  {
                      saas.ConfigureTenant(tenant =>
                      {
                          tenant.AddOrUpdateProperty<TenantType>( //property type: string
                              "tenantType", //property name
                              property =>
                              {
                                  //validation rules
                                  property.Attributes.Add(new RequiredAttribute());
                                  //property.Attributes.Add(new StringLengthAttribute(64) { MinimumLength = 4 });

                                  //...other configurations for this property
                              }
                          );
                      });
                  });


        }
    }
}
@page
@using Hon.IFS.Admin



@{
    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>



}


</style>
  



<div class="row" style="padding-top: 10px; padding-left: 20px">
    <div class="col-12 col-md-6 static-content">
        <h2 style="color: red">STAY</h2>
        <h2 style="color: red"><strong>CONNECTED</strong></h2>
        <h2 style="color: white">STAY</h2>
        <h2 style="color: white"><strong>PROTECTED</strong></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-2">
                <h3 class="text-center mb-3">@L["Login"]</h3>
                @await Component.InvokeAsync(typeof(PageAlertsViewComponent))

                    <form method="post" class="mt-1">
                    <div class="mb-1">
                    
                        <input asp-for="LoginInput.UserNameOrEmailAddress" class="form-control form-control-sm"
                               placeholder="User Name" />
                        <span asp-validation-for="LoginInput.UserNameOrEmailAddress" class="text-danger"></span>
                    </div>
                    <div class="mb-2" style="padding-top: 10px";>
        
                        <div class="input-group">
                            <input  asp-for="LoginInput.Password" class="form-control form-control-sm"
                                   autocomplete="new-password" maxlength="@IdentityUserConsts.MaxPasswordLength"
                                       id="passwordInput" placeholder="Password" required-symbol="false" />

                          @*   <span class="input-group-text" id="togglePassword" style="cursor: pointer;">
                                <i class="fa fa-eye" id="eyeIcon"></i>
                            </span> *@
                            <span asp-validation-for="LoginInput.Password" style="cursor: pointer; class="text-danger"></span>
                        </div>
                    </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">@Html.DisplayNameFor(m => m.LoginInput.RememberMe)</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>
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 IDirectoryDescriptorAppService _directoryDescriptorAppService;

        public CoustomFileManager _coustomFileManager;
        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,
          IDirectoryDescriptorAppService directoryDescriptorAppService,
          CoustomFileManager coustomFileManager)
          : base(schemeProvider,
            accountOptions,
            recaptchaValidatorFactory,
            accountExternalProviderAppService,
            currentPrincipalAccessor,
            identityOptions,
            reCaptchaOptions)
        {
            _tenantRepository = tenantRepository;
            _adminBrandingProvider = adminBrandingProvider;
            _defaultBrandingProvider = defaultBrandingProvider;
            _identityUserRepository = identityUserRepository;
            _directoryDescriptorAppService = directoryDescriptorAppService;
            _coustomFileManager = coustomFileManager;


        }
        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))
            {
                // Convert resolvedTenant to a string
                string tenantKey = resolvedTenant.ToString(); // Safely convert to string

                // Replace "undefined" with an empty string
                if (tenantKey.Equals("undefined"))
                {
                    tenantKey = ""; // Set tenantKey to empty string
                }

                // Check if tenantKey is null or an empty string
                if (string.IsNullOrEmpty(tenantKey))
                {
                    // Set the app name to the default value
                    _adminBrandingProvider.SetAppName("IFS ADMIN"); // Defaults to "IFS ADMIN"
                }
                else
                {
                    // If tenantKey is valid, set the app name to the resolved tenant
                    _adminBrandingProvider.SetAppName(tenantKey);
                }
            }
            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);

            // Step 3: Switch tenant and call base login logic with error handling
            try
            {
                var user = await FindUserAsync(LoginInput.UserNameOrEmailAddress);
                using (CurrentTenant.Change(user?.TenantId))
                {
                    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();
                    }


                    // 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();
                    }
                    var result = await base.OnPostAsync(action);
                    if (!ModelState.IsValid)
                    {
                        return result;
                    }
                    var tenantId = user.TenantId.GetValueOrDefault(Guid.Empty);
                    // **Check if directories already exist**
                    var existingDirectories = await _coustomFileManager.GetFilesByTenantIdAsync(tenantId);
                    var existingNames = existingDirectories.Select(d => d.Name).ToList();
                    var defaultFolders = new List<CreateDirectoryInput>
            {
                new CreateDirectoryInput { Name = "Documents" },
                new CreateDirectoryInput { Name = "Images" },
                new CreateDirectoryInput { Name = "Backups" }
            };

                    foreach (var folder in defaultFolders)
                    {
                        if (!existingNames.Contains(folder.Name)) // **Insert only if not present**
                        {
                            await _directoryDescriptorAppService.CreateAsync(folder);
                        }
                    }
                    return 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;
            //}
        }

        protected virtual async Task<IdentityUser> FindUserAsync(string uniqueUserNameOrEmailAddress)
        {
            IdentityUser user = null;
            using (CurrentTenant.Change(null))
            {
                user = await UserManager.FindByNameAsync(LoginInput.UserNameOrEmailAddress) ??
                       await UserManager.FindByEmailAsync(LoginInput.UserNameOrEmailAddress);

                if (user != null)
                {
                    return user;
                }
            }

            foreach (var tenant in await _tenantRepository.GetListAsync())
            {
                using (CurrentTenant.Change(tenant.Id))
                {
                    user = await UserManager.FindByNameAsync(LoginInput.UserNameOrEmailAddress) ??
                           await UserManager.FindByEmailAsync(LoginInput.UserNameOrEmailAddress);

                    if (user != null)
                    {
                        return user;
                    }
                }
            }
            return null;
        }

    }

}

I getting $ undefined error for login.js file Login.js?_v=638730725080000000:1 Uncaught ReferenceError: $ is not defined at Login.js?_v=638730725080000000:1:1

Message= Source= StackTrace: at https://localhost:44330/Pages/Account/Login.js?_v=638730725080000000:1:1

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;
            //}
        }
    }

}

Showing 1 to 5 of 5 entries
Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.1.0-preview. Updated on December 12, 2025, 10:36
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.