Yes, it's working now. But there is one more issue, i.e., I am not able to use the bootstrap icons on my Login page. We have eye slash icon to show the password. But that is not showing up on the UI.
Yes, it's there
I already have done changes to the global-styles.css file under the Auth project but it's not reflecting. Do I have to provide any reference of the global stylesheet in my cshtml file?
I tried this solution, and it's redirecting the user directly into it's profile. However, because we have put the condition that if the user is not authenticated, it should simply be redirected to the login page, I won't be able to use the landing page of the angular application.
As soon as, I try to access this page, it will redirect the user to the login page and I don't want that. I'll need that page in use.
Isn't there any way we can configure it from the Auth Server side? So that it directly redirects the user into it's profile as soon as the login button is clicked? Please suggest.
But will it work on the Auth Server as well? Because our Auth Server is an MVC project, separate from angular.
This will work, but it's basically calling the API again to get the details of the input fields, and that doesn't sound great. Can you give me something else? Something which can hold the values of the variable till the page is refreshed or the instance of the class is destroyed.
If you run this, now you will see that it works and it will hold the values, but here I have done a work around. I have created a separate input field for all the properties and I am keeping them hidden for now, because that's the only way I could find in order to hold the values. I'd like something more proper to do that.
For example :
<input type="hidden" asp-for="@Model.Fields[i].Index" />
<input type="hidden" asp-for="@Model.Fields[i].Title" />
<input type="hidden" asp-for="@Model.Fields[i].IsRequired" />
<input type="hidden" asp-for="@Model.Fields[i].HasOtherOption" />
<input type="hidden" asp-for="@Model.Fields[i].QuestionType" />
<input type="hidden" asp-for="@Model.Fields[i].Choices" />
Yeah, here you go.
@page @using Microsoft.AspNetCore.Mvc.Localization @using Volo.Abp.Account.Localization @using Volo.Abp.Account.Public.Web.Security.Recaptcha @using Volo.Abp.Account.Settings @using Volo.Abp.Settings @model G1.health.AuthServer.Pages.Account.RegisterModel @using Volo.Forms; @inject IHtmlLocalizer<AccountResource> L @inject Volo.Abp.AspNetCore.Mvc.UI.Layout.IPageLayout PageLayout @inject ISettingProvider SettingProvider @{ PageLayout.Content.Title = L["Register"].Value; var reCaptchaVersion = await SettingProvider.GetAsync<int>(AccountSettingNames.Captcha.Version); }
@if (!Model.LocalLoginDisabled) { @section scripts { <abp-script-bundle name="@typeof(G1.health.AuthServer.Pages.Account.RegisterModel).FullName"> <abp-script src="/Pages/Account/Register.js" /> </abp-script-bundle>
@if (Model.UseCaptcha)
{
if (reCaptchaVersion == 3)
{
<recaptcha-script-v3 />
<recaptcha-script-v3-js action="register" callback="(function(){$('#@RecaptchaValidatorBase.RecaptchaResponseKey').val(token)})" />
}
else
{
<recaptcha-script-v2 />
}
}
}
<style>
.hidden-scroll::-webkit-scrollbar {
display: none;
}
.hidden-scroll{
height: 500px;
overflow: scroll
}
</style>
<div class="account-module-form hidden-scroll">
<h5 class="mb-2">@L["AlreadyRegistered"] <a class="text-decoration-none" href="@Url.Page("./Login", new {returnUrl = Model.ReturnUrl, returnUrlHash = Model.ReturnUrlHash})">@L["Login"]</a></h5>
<form method="post">
@if (Model.UseCaptcha)
{
<div class="mb-2">
<input type="hidden" name="@RecaptchaValidatorBase.RecaptchaResponseKey" id="@RecaptchaValidatorBase.RecaptchaResponseKey" />
</div>
}
@if (!Model.IsExternalLogin)
{
<div class="form-floating mb-2">
<input asp-for="Input.UserName" type="text" class="form-control" placeholder="name@example.com" auto-focus="true">
@Html.LabelFor(m => m.Input.UserName)
</div>
}
<div class="form-floating mb-2">
<input asp-for="Input.EmailAddress" type="email" class="form-control" placeholder="name@example.com" auto-focus="true">
@Html.LabelFor(m => m.Input.EmailAddress)
</div>
@if (!Model.IsExternalLogin)
{
<div class="form-floating mb-2">
<input asp-for="Input.Password" id="password-input" type="password" class="form-control" placeholder="Password">
@Html.LabelFor(m => m.Input.Password)
<i id="PasswordVisibilityButton" class="bi bi-eye-slash show-pass-icon" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-html="true" aria-label="@L["ShowPassword"]" data-bs-original-title="@L["ShowPassword"]"></i>
<i id="capslockicon" class="bi bi-capslock caps-lock-icon" style="display: none;" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-html="true" aria-label="<i class='bi bi-exclamation-circle'></i> @L["CapsLockOn"]!" data-bs-original-title="<i class='bi bi-exclamation-circle'></i> @L["CapsLockOn"]!"></i>
</div>
}
@for (int i = 0; i < Model.Fields.Count; i++)
{
switch (Model.Fields[i].QuestionType)
{
case QuestionTypes.ShortText:
<div class="form-floating mb-2">
<input type="hidden" asp-for="@Model.Fields[i].Index" />
<input type="hidden" asp-for="@Model.Fields[i].Title" />
<input type="hidden" asp-for="@Model.Fields[i].IsRequired" />
<input type="hidden" asp-for="@Model.Fields[i].QuestionType" />
<input asp-for="@Model.Fields[i].Answer" required="@Model.Fields[i].IsRequired" type="text" class="form-control" auto-focus="true">
@Html.Label(Model.Fields[i].Title)
</div>
break;
case QuestionTypes.ParagraphText:
<div class="form-floating mb-2">
<input type="hidden" asp-for="@Model.Fields[i].Index" />
<input type="hidden" asp-for="@Model.Fields[i].Title" />
<input type="hidden" asp-for="@Model.Fields[i].IsRequired" />
<input type="hidden" asp-for="@Model.Fields[i].QuestionType" />
<input asp-for="@Model.Fields[i].Answer" required="@Model.Fields[i].IsRequired" type="text" class="form-control" auto-focus="true">
@Html.Label(Model.Fields[i].Title)
</div>
break;
case QuestionTypes.ChoiceMultiple:
<div class="form-floating mb-2">
<input type="hidden" asp-for="@Model.Fields[i].Index" />
<input type="hidden" asp-for="@Model.Fields[i].Title" />
<input type="hidden" asp-for="@Model.Fields[i].IsRequired" />
<input type="hidden" asp-for="@Model.Fields[i].HasOtherOption" />
<input type="hidden" asp-for="@Model.Fields[i].QuestionType" />
<input type="hidden" asp-for="@Model.Fields[i].Choices" />
<lable>@Model.Fields[i].Title</lable>
<abp-radio asp-for="@Model.Fields[i].Answer" required="@Model.Fields[i].IsRequired" asp-items="@Model.Fields[i].Choices" />
</div>
break;
case QuestionTypes.Checkbox:
<div class="form-floating mb-2">
<input type="hidden" asp-for="@Model.Fields[i].Index" />
<input type="hidden" asp-for="@Model.Fields[i].Title" />
<input type="hidden" asp-for="@Model.Fields[i].IsRequired" />
<input type="hidden" asp-for="@Model.Fields[i].HasOtherOption" />
<input type="hidden" asp-for="@Model.Fields[i].QuestionType" />
<input type="hidden" asp-for="@Model.Fields[i].Choices" />
@foreach (var choice in Model.Fields[i].Choices)
{
<abp-input asp-for="@Model.Fields[i].Answer" required="@Model.Fields[i].IsRequired" label="@choice.Value" type="checkbox" />
}
</div>
break;
case QuestionTypes.DropdownList:
<div class="form-floating mb-2">
<input type="hidden" asp-for="@Model.Fields[i].Index" />
<input type="hidden" asp-for="@Model.Fields[i].Title" />
<input type="hidden" asp-for="@Model.Fields[i].IsRequired" />
<input type="hidden" asp-for="@Model.Fields[i].HasOtherOption" />
<input type="hidden" asp-for="@Model.Fields[i].QuestionType" />
<input type="hidden" asp-for="@Model.Fields[i].Choices" />
<abp-select asp-for="@Model.Fields[i].Answer" required="@Model.Fields[i].IsRequired" label="@Model.Fields[i].Title" asp-items="@Model.Fields[i].Choices" />
</div>
break;
case QuestionTypes.MultipleSelect:
<div class="form-floating mb-2">
<input type="hidden" asp-for="@Model.Fields[i].Index" />
<input type="hidden" asp-for="@Model.Fields[i].Title" />
<input type="hidden" asp-for="@Model.Fields[i].IsRequired" />
<input type="hidden" asp-for="@Model.Fields[i].HasOtherOption" />
<input type="hidden" asp-for="@Model.Fields[i].QuestionType" />
<input type="hidden" asp-for="@Model.Fields[i].Choices" />
<abp-select asp-for="@Model.Fields[i].Answers" required="@Model.Fields[i].IsRequired" label="@Model.Fields[i].Title" asp-items="@Model.Fields[i].Choices" />
</div>
break;
case QuestionTypes.Calendar:
<div class="form-floating mb-2">
<input type="hidden" asp-for="@Model.Fields[i].Index" />
<input type="hidden" asp-for="@Model.Fields[i].Title" />
<input type="hidden" asp-for="@Model.Fields[i].IsRequired" />
<input type="hidden" asp-for="@Model.Fields[i].QuestionType" />
<input asp-for="@Model.Fields[i].Answer" required="@Model.Fields[i].IsRequired" type="datetime-local" class="form-control" auto-focus="true" />
@Html.Label(Model.Fields[i].Title)
</div>
break;
case QuestionTypes.Date:
<div class="form-floating mb-2">
<input type="hidden" asp-for="@Model.Fields[i].Index" />
<input type="hidden" asp-for="@Model.Fields[i].Title" />
<input type="hidden" asp-for="@Model.Fields[i].IsRequired" />
<input type="hidden" asp-for="@Model.Fields[i].QuestionType" />
<input asp-for="@Model.Fields[i].Answer" required="@Model.Fields[i].IsRequired" type="date" class="form-control" auto-focus="true">
@Html.Label(Model.Fields[i].Title)
</div>
break;
case QuestionTypes.Time:
<div class="form-floating mb-2">
<input type="hidden" asp-for="@Model.Fields[i].Index" />
<input type="hidden" asp-for="@Model.Fields[i].Title" />
<input type="hidden" asp-for="@Model.Fields[i].IsRequired" />
<input type="hidden" asp-for="@Model.Fields[i].QuestionType" />
<input asp-for="@Model.Fields[i].Answer" required="@Model.Fields[i].IsRequired" type="time" class="form-control" auto-focus="true">
@Html.Label(Model.Fields[i].Title)
</div>
break;
}
}
@if (reCaptchaVersion == 2)
{
<recaptcha-div-v2 callback="(function(){$('#@RecaptchaValidatorBase.RecaptchaResponseKey').val(token)})" />
}
<div class="d-grid gap-2">
<abp-button button-type="Primary" type="submit" class="mt-2 mb-3">@L["Register"]</abp-button>
</div>
</form>
</div>
}
Sure, here it is.
If you see here in the following form, you'd see 2 input fields i.e., Name and Gender, the details of which are being fetched from the database from an API endpoint. And those fields will be generated dynamically on run time.
Now, once I put all the details, and click on Register, those 2 fields will be disappeared.
Here is my code as well :
Register.cshtml.cs file :
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using G1.health.ClinicService.ClinicSetup;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Owl.reCAPTCHA;
using survey.FormsService.Forms;
using Volo.Abp;
using Volo.Abp.Account;
using Volo.Abp.Account.Public.Web.Pages.Account;
using Volo.Abp.Account.Public.Web.Security.Recaptcha;
using Volo.Abp.Account.Settings;
using Volo.Abp.AspNetCore.Mvc.MultiTenancy;
using Volo.Abp.Auditing;
using Volo.Abp.Data;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.Identity;
using Volo.Abp.Identity.EntityFrameworkCore;
using Volo.Abp.Identity.Settings;
using Volo.Abp.Security.Claims;
using Volo.Abp.Settings;
using Volo.Abp.Uow;
using Volo.Abp.Validation;
using Volo.Forms;
using Volo.Forms.Questions;
using IdentityUser = Volo.Abp.Identity.IdentityUser;
namespace G1.health.AuthServer.Pages.Account;
public class RegisterModel : Web.Pages.Account.AccountPageModel
{
public IAbpTenantAppService AbpTenantAppService { get; set; }
public IFormsAppService FormsAppService { get; set; }
public IUsersAppService UsersAppService { get; set; }
private readonly IUnitOfWorkManager UnitOfWorkManager;
public RegisterModel(IAbpTenantAppService abpTenantAppService, IFormsAppService formsAppService, IUsersAppService usersAppService, IUnitOfWorkManager unitOfWorkManager)
{
AbpTenantAppService = abpTenantAppService;
FormsAppService = formsAppService;
UsersAppService = usersAppService;
UnitOfWorkManager = unitOfWorkManager;
}
[BindProperty(SupportsGet = true)]
public string? ReturnUrl { get; set; }
[BindProperty(SupportsGet = true)]
public string? ReturnUrlHash { get; set; }
[BindProperty(SupportsGet = true)]
public Guid TenantId { get; set; }
[BindProperty]
public PostInput Input { get; set; }
[BindProperty]
public List<Field> Fields { get; set; }
[BindProperty]
public List<QuestionDto> QuestionDtos { get; set; }
private async Task CreateExtraFields()
{
var tenant = await AbpTenantAppService.FindTenantByIdAsync(TenantId);
if (tenant.TenantId != null)
{
var form = await FormsAppService.GetRegistrationFormAsync(TenantId);
QuestionDtos = form.Questions;
}
Fields = new List<Field>();
foreach (var item in QuestionDtos)
{
var choices = new List<SelectListItem>();
foreach (var choice in item.Choices)
{
choices.Add(new SelectListItem()
{
Text = choice.Value,
Value = choice.Value
});
}
Field field = new Field
{
Index = item.Index,
Title = item.Title,
IsRequired = item.IsRequired,
HasOtherOption = item.HasOtherOption,
QuestionType = item.QuestionType,
Answer = null,
Choices = choices,
Answers = null
};
Fields.Add(field);
}
}
[BindProperty(SupportsGet = true)]
public bool IsExternalLogin { get; set; }
public bool LocalLoginDisabled { get; set; }
public bool UseCaptcha { get; set; }
public virtual async Task<IActionResult> OnGetAsync()
{
//await ValidateForm();
var localLoginResult = await CheckLocalLoginAsync();
if (localLoginResult != null)
{
LocalLoginDisabled = true;
return localLoginResult;
}
await CheckSelfRegistrationAsync();
await TrySetEmailAsync();
await SetUseCaptchaAsync();
await CreateExtraFields();
return Page();
}
[UnitOfWork] //TODO: Will be removed when we implement action filter
public virtual async Task<IActionResult> OnPostAsync()
{
try
{
await CheckSelfRegistrationAsync();
await SetUseCaptchaAsync();
IdentityUser user;
if (IsExternalLogin)
{
var externalLoginInfo = await SignInManager.GetExternalLoginInfoAsync();
if (externalLoginInfo == null)
{
Logger.LogWarning("External login info is not available");
return RedirectToPage("./Login");
}
user = await RegisterExternalUserAsync(externalLoginInfo, Input.EmailAddress);
}
else
{
var localLoginResult = await CheckLocalLoginAsync();
if (localLoginResult != null)
{
LocalLoginDisabled = true;
return localLoginResult;
}
user = await RegisterLocalUserAsync();
}
if (await SettingProvider.IsTrueAsync(IdentitySettingNames.SignIn.RequireConfirmedEmail) && !user.EmailConfirmed ||
await SettingProvider.IsTrueAsync(IdentitySettingNames.SignIn.RequireConfirmedPhoneNumber) && !user.PhoneNumberConfirmed)
{
await StoreConfirmUser(user);
return RedirectToPage("./ConfirmUser", new
{
returnUrl = ReturnUrl,
returnUrlHash = ReturnUrlHash
});
}
await SignInManager.SignInAsync(user, isPersistent: true);
return Redirect(ReturnUrl ?? "/"); //TODO: How to ensure safety? IdentityServer requires it however it should be checked somehow!
}
catch (BusinessException e)
{
Alerts.Danger(GetLocalizeExceptionMessage(e));
return Page();
}
}
[UnitOfWork]
protected virtual async Task<IdentityUser> RegisterLocalUserAsync()
{
using (var unitOfWork = UnitOfWorkManager.Begin())
{
ValidateModel();
var captchaResponse = string.Empty;
if (UseCaptcha)
{
captchaResponse = HttpContext.Request.Form[RecaptchaValidatorBase.RecaptchaResponseKey];
}
RegisterDto registerDto = new RegisterDto()
{
AppName = "MVC",
EmailAddress = Input.EmailAddress,
Password = Input.Password,
UserName = Input.UserName,
ReturnUrl = ReturnUrl,
ReturnUrlHash = ReturnUrlHash,
CaptchaResponse = captchaResponse
};
foreach (var item in Fields)
{
if (item.Answers != null)
{
string result = "{";
foreach (var answer in item.Answers)
{
result += '"' + answer + '"';
if (!(answer == item.Answers.LastOrDefault()))
{
result += ",";
}
}
result += "}";
item.Answer = result;
}
registerDto.SetProperty(item.Title, item.Answer);
}
var userDto = await AccountAppService.RegisterAsync(registerDto);
RegisterUser registerUser = new RegisterUser(userDto.Id, Input.UserName, Input.EmailAddress, TenantId, userDto.CreationTime, "Patient", Input.Password);
foreach (var key in Fields)
{
switch (key.Title)
{
case "Name":
registerUser.first_name = key.Answer;
break;
case "Surname":
registerUser.last_name = key.Answer;
break;
case "DOB":
registerUser.dob = DateTime.Parse(key.Answer);
break;
case "Gender":
registerUser.gender = key.Answer;
break;
default:
break;
}
}
await UsersAppService.RegisterUser(registerUser);
return await UserManager.GetByIdAsync(userDto.Id);
}
}
private async Task<bool> ValidateForm()
{
return true;
}
protected virtual async Task<IdentityUser> RegisterExternalUserAsync(ExternalLoginInfo externalLoginInfo, string emailAddress)
{
await IdentityOptions.SetAsync();
var user = new IdentityUser(GuidGenerator.Create(), emailAddress, emailAddress, CurrentTenant.Id);
(await UserManager.CreateAsync(user)).CheckErrors();
(await UserManager.AddDefaultRolesAsync(user)).CheckErrors();
if (!user.EmailConfirmed)
{
await AccountAppService.SendEmailConfirmationTokenAsync(
new SendEmailConfirmationTokenDto
{
AppName = "MVC",
UserId = user.Id,
ReturnUrl = ReturnUrl,
ReturnUrlHash = ReturnUrlHash
}
);
}
var userLoginAlreadyExists = user.Logins.Any(x =>
x.TenantId == user.TenantId &&
x.LoginProvider == externalLoginInfo.LoginProvider &&
x.ProviderKey == externalLoginInfo.ProviderKey);
if (!userLoginAlreadyExists)
{
user.AddLogin(new UserLoginInfo(
externalLoginInfo.LoginProvider,
externalLoginInfo.ProviderKey,
externalLoginInfo.ProviderDisplayName
)
);
(await UserManager.UpdateAsync(user)).CheckErrors();
}
return user;
}
protected virtual async Task CheckSelfRegistrationAsync()
{
if (!await SettingProvider.IsTrueAsync(AccountSettingNames.IsSelfRegistrationEnabled) ||
!await SettingProvider.IsTrueAsync(AccountSettingNames.EnableLocalLogin))
{
throw new UserFriendlyException(L["SelfRegistrationDisabledMessage"]);
}
}
protected virtual async Task SetUseCaptchaAsync()
{
UseCaptcha = !IsExternalLogin && await SettingProvider.IsTrueAsync(AccountSettingNames.Captcha.UseCaptchaOnRegistration);
if (UseCaptcha)
{
var reCaptchaVersion = await SettingProvider.GetAsync<int>(AccountSettingNames.Captcha.Version);
await ReCaptchaOptions.SetAsync(reCaptchaVersion == 3 ? reCAPTCHAConsts.V3 : reCAPTCHAConsts.V2);
}
}
protected virtual async Task StoreConfirmUser(IdentityUser user)
{
var identity = new ClaimsIdentity(ConfirmUserModel.ConfirmUserScheme);
identity.AddClaim(new Claim(AbpClaimTypes.UserId, user.Id.ToString()));
if (user.TenantId.HasValue)
{
identity.AddClaim(new Claim(AbpClaimTypes.TenantId, user.TenantId.ToString()));
}
await HttpContext.SignInAsync(ConfirmUserModel.ConfirmUserScheme, new ClaimsPrincipal(identity));
}
private async Task TrySetEmailAsync()
{
if (IsExternalLogin)
{
var externalLoginInfo = await SignInManager.GetExternalLoginInfoAsync();
if (externalLoginInfo == null)
{
return;
}
if (!externalLoginInfo.Principal.Identities.Any())
{
return;
}
var identity = externalLoginInfo.Principal.Identities.First();
var emailClaim = identity.FindFirst(ClaimTypes.Email);
if (emailClaim == null)
{
return;
}
Input = new PostInput { EmailAddress = emailClaim.Value };
}
}
public class PostInput
{
[Required]
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxUserNameLength))]
public string UserName { get; set; }
[Required]
[EmailAddress]
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxEmailLength))]
public string EmailAddress { get; set; }
[Required]
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPasswordLength))]
[DataType(DataType.Password)]
[DisableAuditing]
public string Password { get; set; }
}
public class Field
{
public int Index { get; set; }
public string? Title { get; set; }
public bool IsRequired { get; set; }
public bool HasOtherOption { get; set; }
public QuestionTypes QuestionType { get; set; }
public string? Answer { get; set; }
public List<string>? Answers { get; set; }
public List<SelectListItem>? Choices { get; set; }
}
}
Here the variable "Fields" holds the details of those extra input fields which I am fetching from the database. As you can see those fields would not appear after clicking on Register, can you suggest what can I do so that the fields won't disappear.
FYI, I have added all these files manually and not using ABP Suite.