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

Hi enis using the Object Extensions or Module Entity Extensions: in microservice template it not even adding the extra column to the extra properties can you try once from your side in microservice template for Saas Tenant Table.

Please send me the Documentation for microservice template of Entity Extensions

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


        }
    }
}

**Sometimes in-built eye icon is coming and diapering **

Please find the complete code here below

@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.Theme.LeptonX.Themes.LeptonX.Components.Common.PageAlerts
@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(LoginModel).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>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>

    <script>
      ~~~~

                   document.addEventListener('DOMContentLoaded', () => {
            document.querySelectorAll('.alert .btn-close').forEach(button => {
                button.addEventListener('click', () => {
                    const alertBox = button.closest('.alert');
                    if (alertBox) {
                        alertBox.remove();
                    }
                });
            });
        });
    </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 >
    /* Custom switch styles */
    .form-check-input:checked {
        background-color: red; /* Change the background color when checked */
        border-color: red; /* Change the border color when checked */
    }

    .form-check-input:focus {
        box-shadow: 0 0 0 0.2rem rgba(255, 0, 0, 0.25); /* Optional: Add a red focus ring */
    }

    .form-check-input {
        background-color: #e9ecef; /* Change the background color when unchecked */
        border-color: #ced4da; /* Change the border color when unchecked */
    }

    #passwordInput {
        border-top-right-radius: 0.5rem !important;
        border-bottom-right-radius: 0.5rem !important;
    }
</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: 460px;">
            <div class="card-body p-2">
                <h3 class="text-center mb-3">@L["Login"]</h3>
                <abp-alerts>
                        @await Component.InvokeAsync(typeof(PageAlertsViewComponent))
                </abp-alerts>


                    <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" />

                          
                         
                        </div>
                        <div>
                            <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>

I am currently working with version 9.1 of the Angular frontend, and I have encountered some issues that persist in both the login and registration pages.

If you have any updated code or solutions to address these issues, I would greatly appreciate your assistance in providing that information.

Yes, this worked but in the custom login page Password Eye icon disappears all the time can you help me how to resolve it

@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

@await Component.InvokeAsync(typeof(PageAlertsViewComponent))

it resolved after adding this line in index.chtml

Showing 1 to 10 of 13 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.0.0-preview. Updated on July 17, 2025, 06:22