- ABP Framework version: v7.3.2
- UI Type: Angular / MVC
- Database System: EF Core (MySQL)
- Tiered (for MVC) or Auth Server Separated (for Angular): yes/no
- Exception message and full stack trace:
- Steps to reproduce the issue:
I am working on the Auth Server project (independent). I have a requirement where I want to put some extra input fields on the Registration page of auth server. I'd collect the values of those extra input fields and will store those values in my other database table (separate from the ones provided by ABP). I am even able to carry out the whole process and am successfully able to store the data in the database without altering the existing configuration. I am fetching the details of those input fields from my database and storing them in a variable in the Register.cshtml.cs file. And I am using that variable to display those input fields on UI.
Now, here the problem is, that if I have any kind of a validation error (username already taken for example), and if I try to submit at this point, the application will pop out the validation error, saying this username is already taken, which is fine. But all my input fields get disappeared as soon as there is some validation error. I think this is happening because the class is losing all of it's values as soon as there is any validation error and hence it resets all the variables of the class and hence my variable (which stores the input field details), also gets reset and therefore loses all the details and it doesn't appear on the UI.
Is there a way to keep the value of that variable held during the entire time? Any kind of annotation or something? Please suggest.
8 Answer(s)
-
0
-
0
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.
-
0
Hi,
Could you also share the cshtml code?
-
0
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>
}
-
0
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" />
-
0
Hi, You can try update the
CreateExtraFields
methodprivate async Task CreateExtraFields() { .... 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 = Fields.FirstOrDefault(x => x.Title == item.Title) ?? new Field(); field.Index = item.Index; field.Index = item.Index; field.Title = item.Title; field.IsRequired = item.IsRequired; field.HasOtherOption = item.HasOtherOption; field.QuestionType = item.QuestionType; field.Choices = choices; Fields.AddIfNotContains(x => x.Title == field.Title ,() =>field); } }
And
OnPostAsync
methodpublic virtual async Task<IActionResult> OnPostAsync() { try { await CheckSelfRegistrationAsync(); await SetUseCaptchaAsync(); await CreateExtraFields(); add this line .... } .... }
The cshtml
... case QuestionTypes.ShortText: <div class="form-floating mb-2"> <input type="hidden" asp-for="@Model.Fields[i].Title" /> // keep this line <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].Title" /> // keep this line <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; ....
-
0
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.
-
0
Hi,
Something which can hold the values of the variable till the page is refreshed or the instance of the class is destroyed.
The server is stateless, you must re-call the app service or temporarily store the value. you can consider using Session to store values.
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/app-state?view=aspnetcore-8.0 https://learn.microsoft.com/en-us/aspnet/core/fundamentals/app-state?view=aspnetcore-8.0#set-and-get-session-values