EFCore Identity: Admin Razor Page : create user with roles - identity

I'm trying to create a Razor Page for an admin to add a user with checkboxes to select the roles for the user. But i'm not getting the desired result.
Pagemodel:
[BindProperty(SupportsGet = true)]
public InputModel Input { get; set; }
public class InputModel
{
public string Email {get; set;}
...
public list<rol> rollen {get;set;}
}
public class rol
{
public string name {get;set}
public bool selected {get;set;}
}
In the OnGet():
Input.rollen = (from rol in _roleManager.Roles
select new rol
{
name = rol.Name,
selected = false
}).ToList();
In the Page:
#foreach (var rol in Model.Input.rollen.OrderBy(x=>x.name))
{
<div class="form-check">
<input class="form-check-input" type="checkbox" id="#rol.name" name="#rol.name">
<label class="form-check-label" for="#rol.name">
#rol.name
</label>
</div>
}
Visually it gives the correct result. But off course when posting the form, it doesn't match the model. What is the correct way of doing this? I could just add the checkboxes manually, one by one but that's not really the way of doing things

I found the answer using thins page: https://www.learnrazorpages.com/razor-pages/forms/checkboxes.
I added
[BindProperty]
public InputModel Input { get; set; }
[BindProperty]
public List<string> roles { get; set; }
Onget:
public void OnGet()
{
var rollen = _roleManager.Roles.OrderBy(x => x.Name).Select(x => x.Name).ToList();
ViewData["roles"] = roles;
}
On page:
<div class="form-row">
<label class="col-md-3 col-form-label">Rollen</label>
<div class="form-group col-md-9">
#foreach(var role in ViewData["roles"] as List<string>)
{
<div class="form-check">
<input class="form-check-input" type="checkbox" name="roles" value="#role">
<label class="form-check-label">
#role
</label>
</div>
}
</div>
</div>
On Post you get the List which contains all the checked values. It's important that all checkboxes have the same name-attribute
I found that page before but i just didn't get it to work. But that was because i added the List as part of the InputModel. I had to add it as Bindproperty separatly.

Related

Custom data isn't saved in database

I am trying to make a login/signup system with ASP.NET. I saw a tutorial on how to make it and I started following him. But it was sometimes messy and I couldn't understand him. (For the tutorial).
I used the 'new scaffolded item' to create a new 'identity' for login & registration.
I added custom data of my own (at this moment it's only FirstName&LastName) and when I create A user everything looks excellent, in debug mode I could see the data saved in my class but when it transfers to the database it holds as "NULL".
I am still new to this technique so I am not sure if that's all the code that's needed for you to help spot the problem. If the's anything to add, comment down, please.
here's the HTML for the registration (at the moment the login isn't complete) [Directory: Areas/Identity/Pages/Register.cshtml]
#page
#model RegisterModel
#{ ViewData["Title"] = "Register"; }
#{ Layout = "~/Areas/Identity/Pages/_AuthLayout.cshtml"; }
<form asp-route-returnUrl="#Model.ReturnUrl" method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label asp-for="Input.FirstName"></label>
<input asp-for="Input.FirstName" class="form-control" />
<span asp-validation-for="Input.FirstName" class="text-danger"></span>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label asp-for="Input.LastName"></label>
<input asp-for="Input.LastName" class="form-control" />
<span asp-validation-for="Input.LastName" class="text-danger"></span>
</div>
</div>
</div>
<div class="form-group">
<label asp-for="Input.Email"></label>
<input asp-for="Input.Email" class="form-control" />
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label asp-for="Input.Password"></label>
<input asp-for="Input.Password" class="form-control" />
<span asp-validation-for="Input.Password" class="text-danger"></span>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label asp-for="Input.ConfirmPassword"></label>
<input asp-for="Input.ConfirmPassword" class="form-control" />
<span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary">Register</button>
</form>
#section Scripts {
<partial name="_ValidationScriptsPartial"></partial>
}
here's the C# (the backend) [Directory: Areas/Identity/Pages/Register.cshtml.cs]
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging;
using SvivaTeam.Areas.Identity.Data;
namespace SvivaTeam.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class RegisterModel : PageModel
{
private readonly SignInManager<IdentityUser> _signInManager;
private readonly UserManager<IdentityUser> _userManager;
private readonly ILogger<RegisterModel> _logger;
private readonly IEmailSender _emailSender;
public RegisterModel(
UserManager<IdentityUser> userManager,
SignInManager<IdentityUser> signInManager,
ILogger<RegisterModel> logger,
IEmailSender emailSender)
{
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
_emailSender = emailSender;
}
[BindProperty]
public InputModel Input { get; set; }
public string ReturnUrl { get; set; }
public IList<AuthenticationScheme> ExternalLogins { get; set; }
public class InputModel
{
[Required]
[DataType(DataType.Text)]
[Display(Name = "First Name")]
public string FirstName { get; set; }
[Required]
[DataType(DataType.Text)]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
public async Task OnGetAsync(string returnUrl = null)
{
if (User.Identity.IsAuthenticated)
Response.Redirect("/");
ReturnUrl = returnUrl;
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
}
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl ??= Url.Content("~/");
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
if (ModelState.IsValid)
{
var user = new ApplicationUser
{
UserName = Input.Email,
Email = Input.Email,
FirstName = Input.FirstName, //Custom Code
LastName = Input.LastName //Cusom Code
};
var result = await _userManager.CreateAsync(user, Input.Password);
if (result.Succeeded)
{
_logger.LogInformation("User created a new account with password.");
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { area = "Identity", userId = user.Id, code = code, returnUrl = returnUrl },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
if (_userManager.Options.SignIn.RequireConfirmedAccount)
{
return RedirectToPage("RegisterConfirmation", new { email = Input.Email, returnUrl = returnUrl });
}
else
{
await _signInManager.SignInAsync(user, isPersistent: false);
return LocalRedirect(returnUrl);
}
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
// If we got this far, something failed, redisplay form
return Page();
}
}
}
Here's more C# code that holds the custom info (In the video he said to make it). [Directory: Areas/Identity/Data/ApplicationUser.cshtml]
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
namespace SvivaTeam.Areas.Identity.Data
{
public class ApplicationUser : IdentityUser
{
[PersonalData]
[Column(TypeName = "nvarchar(100)")]
public string FirstName { get; set; }
[PersonalData]
[Column(TypeName = "nvarchar(100)")]
public string LastName { get; set; }
}
}

ASP.NET Core Identity use different user table

I'm trying to do a web App with ASP.NET Core 3.1. I have an existing SQL Database with full of users. I have only read access to this database, so I can't change nothing.
My question is, can I somehow use it to authentication to Login instead of AspNetUsers table?
(I don't need registration, nor forget password, etc., just a safety login)
My User database has these columns: Id(varchar),Name(varchar),Rank(int),Password(varchar),Email(varchar),Phone(varchar)
use it to authentication to Login instead of AspNetUsers table
Below is a demo, you can refer to it.
LoginModel
public class LoginModel
{
[Required]
[Display(Name ="Username")]
public string UserName { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
public bool RememberLogin { get; set; }
public string ReturnUrl { get; set; }
}
UserModel
public class UserModel
{
public int UserId { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string Role { get; set; }
}
AccountController
[Note] I set fake data, you can get it directly from the database.
public class AccountController : Controller
{
//Sample Users Data, it can be fetched with the use of any ORM
public List<UserModel> users = null;
public AccountController()
{
users = new List<UserModel>();
users.Add(new UserModel() { UserId = 1, Username = "Anoop", Password = "123", Role = "Admin" });
users.Add(new UserModel() { UserId = 2, Username = "Other", Password = "123", Role = "User" });
}
public IActionResult Login(string ReturnUrl = "/")
{
LoginModel objLoginModel = new LoginModel();
objLoginModel.ReturnUrl = ReturnUrl;
return View(objLoginModel);
}
[HttpPost]
public async Task<IActionResult> Login(LoginModel objLoginModel)
{
if (ModelState.IsValid)
{
var user = users.Where(x => x.Username == objLoginModel.UserName && x.Password == objLoginModel.Password).FirstOrDefault();
if (user == null)
{
//Add logic here to display some message to user
ViewBag.Message = "Invalid Credential";
return View(objLoginModel);
}
else
{
//A claim is a statement about a subject by an issuer and
//represent attributes of the subject that are useful in the context of authentication and authorization operations.
var claims = new List<Claim>() {
new Claim(ClaimTypes.NameIdentifier,Convert.ToString(user.UserId)),
new Claim(ClaimTypes.Name,user.Username),
new Claim(ClaimTypes.Role,user.Role),
new Claim("FavoriteDrink","Tea")
};
//Initialize a new instance of the ClaimsIdentity with the claims and authentication scheme
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
//Initialize a new instance of the ClaimsPrincipal with ClaimsIdentity
var principal = new ClaimsPrincipal(identity);
//SignInAsync is a Extension method for Sign in a principal for the specified scheme.
//await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
// principal, new AuthenticationProperties() { IsPersistent = objLoginModel.RememberLogin });
await HttpContext.SignInAsync(
principal, new AuthenticationProperties() { IsPersistent = objLoginModel.RememberLogin });
return LocalRedirect(objLoginModel.ReturnUrl);
}
}
return View(objLoginModel);
}
public async Task<IActionResult> LogOut() {
//SignOutAsync is Extension method for SignOut
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
//Redirect to home page
return LocalRedirect("/");
}
}
In HomeController, use [Authorize] on ConfidentialData() method
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
[Authorize]
public IActionResult ConfidentialData()
{
return View();
}
}
ConfidentialData view
#{
ViewData["Title"] = "Confidential Data";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Confidential Data</h2>
#if (User.Identity.IsAuthenticated)
{
<table class="table table-bordered">
#foreach (var claim in User.Claims) {
<tr><td>#claim.Type</td><td>#claim.Value</td></tr>
}
</table>
}
Register Authentication in startup
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(x => x.LoginPath = "/account/login");
...
app.UseAuthentication();
app.UseAuthorization();
Result:
Read Use cookie authentication without ASP.NET Core Identity to know more.
Login form
#model LoginModel
#{
ViewData["Title"] = "Login";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Login</h2>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Login">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
#if (!string.IsNullOrEmpty(ViewBag.Message))
{
<span class="text-danger">
#ViewBag.Message
</span>
}
#Html.HiddenFor(x => x.ReturnUrl)
<div class="form-group">
<label asp-for="UserName" class="control-label"></label>
<input asp-for="UserName" class="form-control" />
<span asp-validation-for="UserName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Password" class="control-label"></label>
<input asp-for="Password" class="form-control" />
<span asp-validation-for="Password" class="text-danger"></span>
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input asp-for="RememberLogin" /> #Html.DisplayNameFor(model => model.RememberLogin)
</label>
</div>
</div>
<div class="form-group">
<input type="submit" value="Login" class="btn btn-default" />
</div>
</form>
</div>
</div>

how to use timespan in the component of blazor server app and convert to 12 hour format

My requirements are to add timeslot settings , edit and show that timeslots
fields from the database are :
1.ID(short)
2.FromTime (TimeSpan)
3.ToTime(TimeSpan)
4.DisplayTime(string)
Model CLass:
public short TimeSlotID { get; set; }
public TimeSpan FromTime { get; set; }
public TimeSpan ToTime { get; set; }
Database Read:
yield return new TimeSlotModel
{
TimeSlotID = reader.GetInt16("TimeSlotID"),
FromTime = (TimeSpan)reader["FromTime"],
ToTime = (TimeSpan)reader["ToTime"],
DisplayText = reader.SafeGetString("DisplayText"),
};
Razor page
<div class="row">
<div class="col">
<label for="TimeSlotID">TimeSlotID</label>
</div>
</div>
<div class="row">
<div class="col-4">
<label for="FromTime">FromTime</label>
</div>
<div class="col">
<label for="ToTime">ToTime</label>
</div>
</div>
<div class="form-group">
<div class="col">
<label for="DisplayText">DisplayText</label>
#item.DisplayText
</div>
</div>
I found it difficult to use any components to read and add or edit coz its Timespan .
i used datetimepicker but i dont know how to convert that to timespan while saving that to the database.Can anyone help solve this one.
thank you!
This solution binds to a DateTimeOffset property that sets your models timespans.
Index.razor
#page "/"
<input type="time" #bind-value="FromTime" />
<input type="time" #bind-value="ToTime" />
<hr />
#ConvertTo12HourFormat(SomeModel.FromTime) - #ConvertTo12HourFormat(SomeModel.ToTime)
Index.razor.cs
using System;
using Microsoft.AspNetCore.Components;
public partial class Index : ComponentBase
{
readonly DateTimeOffset aDate = new DateTime(2020, 01, 01);
public SomeModel SomeModel { get; set; } = new SomeModel();
public DateTimeOffset FromTime
{
get { return aDate.Add(SomeModel.FromTime); }
set { SomeModel.FromTime = value.Subtract(aDate); }
}
public DateTimeOffset ToTime
{
get { return aDate.Add(SomeModel.ToTime); }
set { SomeModel.ToTime = value.Subtract(aDate); }
}
public string ConvertTo12HourFormat(TimeSpan time) => aDate.Add(time).ToString("hh:mm:ss tt");
}
SomeModel.cs
public class SomeModel
{
public short TimeSlotID { get; set; }
public TimeSpan FromTime { get; set; }
public TimeSpan ToTime { get; set; }
}

Passing list of models from one page to controller

I've tried to pass a list of models to controller but with no luck. I have a problem with generating empty view and then pass values from filled forms to controller. What I have:
Models
public class PostsModel
{
[Required]
[DataType(DataType.DateTime)]
public DateTime PublishDate { get; set; }
[Required]
public List<PostModel> Posts { get; set; }
}
public class PostModel
{
public string Language {get;set;}
public string X {get;set;}
public string Y {get;set;}
// and other properties
}
Controller
public IActionResult New()
{
ViewData["ButtonName"] = "Add";
return View(new PostsModel { PublishDate = DateTime.UtcNow });
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> New(PostsModel model)
{
if (ModelState.IsValid)
{
// some code
// never reaches this point
}
return View(model);
}
Form:
<form method="post">
<h4>XYZ</h4>
<hr />
#{
Model.Posts = new List<PostModel>(2);
for (var i = 0; i < 2; i++)
{
Model.Posts.Add(new PostModel());
}
foreach (var test in Model.Posts)
{
<h4>xyz</h4>
<div class="form-group">
<label asp-for="#test.Subject">Temat</label>
<input asp-for="#test.Subject" class="form-control" />
<span asp-validation-for="#test.Subject" class="text-danger"></span>
</div>
}
}
<button type="submit" class="btn btn-primary">#ViewData["ButtonName"]</button>
</form>
Of course model is never valid. I don't have an idea how to do such functionality.
As #StephenMuecke said, using for-loop is working approach. In this particular problem that I had it's enough to do:
Controller:
public IActionResult New()
{
ViewData["ButtonName"] = "Add";
// example:
return View(new PostsModel { PublishDate = DateTime.UtcNow, Posts = new List<PostModel>(2) { new PostModel(), new PostModel() } });
}
View:
for (var i = 0; i < Model.Posts.Count; i++)
{
<h4>xyz</h4>
<div class="form-group">
<label asp-for="#Model.Posts[i].Subject">Temat</label>
<input asp-for="#Model.Posts[i].Subject" class="form-control" />
<span asp-validation-for="#Model.Posts[i].Subject" class="text-danger"></span>
</div>
#*and so one*#
}
I also want to thank M. Kostrzewski and M. Kaletka who also helped me on some .net group

button group into razor syntax

I would like to know if I can convert my div button group (btn-group) to razor syntax in my asp.net mvc app? I want the razor syntax so I can preselect and pre activate a button label when entering the page. If razor isn't needed, then can someone please show me how to make a button active and selected on the page being entered from my view model data? It seems that without razor I would have to pass my viewmodel data to javascript to perform, but that doesn't seem right. Here is my html
<div class="form-group">
#Html.LabelFor(model => model.Listing.SpaceType, htmlAttributes: new { #class = "control-label" })
<div class="btn-group form-control" data-toggle="buttons" id="SpaceType">
<label id="SpaceTypeLabel0" class="btn btn-primary">
<input type="radio" name="typeoptions" autocomplete="off" id="0"> House
</label>
<label id="SpaceTypeLabel1" class="btn btn-primary">
<input type="radio" name="typeoptions" autocomplete="off" id="1"> Apartment
</label>
<label id="SpaceTypeLabel2" class="btn btn-primary">
<input type="radio" name="typeoptions" autocomplete="off" id="2"> Studio
</label>
<label id="SpaceTypeLabel3" class="btn btn-primary">
<input type="radio" name="typeoptions" autocomplete="off" id="3"> Other
</label>
</div>
</div>
Here is my model
public class Space
{
public int SpaceId { get; set; }
public virtual SpaceOverview Overview { get; set; }
public virtual SpaceDetails Details { get; set; }
public virtual SpaceListing Listing { get; set; }
public virtual SpaceAddress Address { get; set; }
[Required]
public DateTime DateCreated { get; set; }
}
and spacelisting is
public class SpaceListing
{
[Key, ForeignKey("SpaceOf")]
public int SpaceListingId { get; set; }
public SpaceType SpaceType { get; set; }
public SpaceLocation SpaceLocation { get; set; }
public SpaceAccommodation Accommodation { get; set; }
public Space SpaceOf { get; set; } // one-to-one
}
and spacetype is
public enum SpaceType
{
Home,
Apartment,
Studio,
Park,
Beach,
Field,
Backyoard,
FrontYard,
Other
}
Currently you creating a group of radio buttons with name="typeoptions" which have no relationship to the model, and your not even giving the radio buttons a value attribute so nothing would post back anyway.
The syntax should be
#Html.RadioButtonFor(m => m.Listing.SpaceType, "House", new { id = "House" })
#Html.Label("House")
#Html.RadioButtonFor(m => m.Listing.SpaceType, "Apartment", new { id = "Apartment" })
#Html.Label("Apartment")
...
To make this easier, you can create an extension method
public static class RadioButtonHelper
{
public static MvcHtmlString EnumRadioButtonListFor<TModel, TValue>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TValue>> expression)
{
ModelMetadata metaData = ModelMetadata.FromLambdaExpression(expression, helper.ViewData);
string name = ExpressionHelper.GetExpressionText(expression);
Type type = Nullable.GetUnderlyingType(metaData.ModelType);
if (type == null || !type.IsEnum)
{
throw new ArgumentException(string.Format("The property {0} is not an enum", name));
}
StringBuilder html = new StringBuilder();
foreach (Enum item in Enum.GetValues(type))
{
string id = string.Format("{0}_{1}", metaData.PropertyName, item);
StringBuilder innerHtml = new StringBuilder();
innerHtml.Append(helper.RadioButtonFor(expression, item, new { id = id }));
innerHtml.Append(helper.Label(id, item.ToDescription()));
TagBuilder div = new TagBuilder("div");
div.AddCssClass("radiobutton");
div.InnerHtml = innerHtml.ToString();
html.Append(div.ToString());
}
TagBuilder container = new TagBuilder("div");
container.AddCssClass("radiobutton-container");
container.InnerHtml = html.ToString();
return MvcHtmlString.Create(container.ToString());
}
}
Note, this uses the following extension method
public static string ToDescription(this Enum value)
{
FieldInfo field = value.GetType().GetField(value.ToString());
DescriptionAttribute[] attributes = (DescriptionAttribute[])field.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes.Length > 0)
{
return attributes[0].Description;
}
return value.ToString();
}
which allows you to decorate the enum values with a 'friendly' name
public enum SpaceType
{
Home,
[Description("2 bed apartment")]
Apartment,
....
}
and in the view
#Html.EnumRadioButtonListFor(m => m.Listing.SpaceType)