Display user data in ASP.NET core MVC - c#

Trying to display authenticated user data in an MVC view.
Using ASP.NET Core 2.1
The following error occours:
An unhandled exception occurred while processing the request.
NullReferenceException: Object reference not set to an instance of an object.
AspNetCore.Views_Home_Index.ExecuteAsync() in Index.cshtml, line 6
There seems to be a problem with using #Model.id. What is the correct way of accessing properties of an authenticated user from within the view?
Models/LoginModel.cs
using Microsoft.AspNetCore.Identity;
namespace MyProject.Models
{
public class LoginModel
{
[Required]
[UIHint("email")]
public string Email { get; set; }
[Required]
[UIHint("password")]
public string Password { get; set; }
}
}
Views/Account/Login.cshtml
#model LoginModel
<h1>Login</h1>
<div class="text-danger" asp-validation-summary="All"></div>
<form asp-controller="Account" asp-action="Login" method="post">
<input type="hidden" name="returnUrl" value="#ViewBag.returnUrl" />
<div class="form-group">
<label asp-for="Email"></label>
<input asp-for="Email" class="form-control" />
</div>
<div class="form-group">
<label asp-for="Password"></label>
<input asp-for="Password" class="form-control" />
</div>
<button class="btn btn-primary" type="submit">Login</button>
</form>
Controllers/AccountController.cs
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginModel details, string returnUrl)
{
ApplicationUser user = new ApplicationUser();
if (ModelState.IsValid)
{
user = await userManager.FindByEmailAsync(details.Email);
if (user != null)
{
await signInManager.SignOutAsync();
Microsoft.AspNetCore.Identity.SignInResult result =
await signInManager.PasswordSignInAsync(
user, details.Password, false, false);
if (result.Succeeded)
{
return Redirect(returnUrl ?? "/");
}
}
ModelState.AddModelError(nameof(LoginModel.Email),
"Invalid user or password");
}
return View(details);
}
Views/Home/Index.cshtml
#model ApplicationUser
#if (User.Identity.IsAuthenticated)
{
#Model.Id
}

You can inject the UserManager into the view, and achieve the same result without having the pass a model into the view:
#using Microsoft.AspNetCore.Identity
#inject UserManager<ApplicationUser> UserManager
And then doing:
#await UserManager.GetUserIdAsync(User)

Related

Can't get values ​entered in ASP.NET MVC Form

First of all my codes:
ManageClass.cshtml:
#{
ViewData["Title"] = "Class' Management";
}
<br />
<h2>Add Class</h2>
<form method="post" asp-controller="AddClassDB" asp-action="">
<div class="container">
<div class="form-group">
</div> </div>
<label for="formGroupExampleInput2">Class Name</label>
<input type="text" class="form-control" id="classNameInput">
<br/>
<div class="float-right">
<button type="submit" class="btn btn-success">Add</button>
</div>
<br />
<h2>Manage Class</h2>
</form>
HomeController.cs:
using Microsoft.AspNetCore.Mvc;
using StudentWeb.Models;
using System.Diagnostics;
namespace StudentWeb.Controllers
{
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}
public IActionResult Index()
{
return View();
}
public ActionResult ManageClass()
{
return View();
}
public ActionResult AddClassDB(ClassTable _table)
{
Console.WriteLine(_table);
return View();
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
}
I will take the value of classNameInput in ManageClass.cshtml and save it to SQL. I will do the saving in the Controller, but I have not yet received the value entered by the user.
But after I enter the value in the input and press the submit button, I get the following result:
(page not found)
You are using the wrong value for asp-controller and asp-action for the form. Hence it generates the wrong action path for the form.
It should be:
<form method="post" asp-controller="Home" asp-action="AddClassDB">
...
</form>
By default, all the action methods in the controller are GET (method). You need to apply [HttpPost] attribute so that the AddClassDB is recognized as POST (method).
[HttpPost("AddClassDB")]
public ActionResult AddClassDB(ClassTable _table)
{
Console.WriteLine(_table);
// TO-DO Redirect to view that is existed
return View();
}

ReturnUrl is always null on POST using ASP.NET Core 6 MVC

I'm not able to get ReturnUrl to work on HttpPost using ASP.NET Core 6 MVC.
When adding a breakpoint to the POST method, returnurl is always null. But with .NET 5, it works with the same code setup except that with .NET 6, I need to make the returnurl parameter nullable so that I won't get an error "returnurl field is required".
This is the code I'm using - any help would be much appreciated.
Thanks.
Model:
namespace IdentityManagerDotNet6.Models
{
public class LoginViewModel
{
[Required]
[EmailAddress]
public string Email { get; set; } = string.Empty;
[Required]
[DataType(DataType.Password)]
public string Password { get; set; } = string.Empty;
[Display(Name = "Remember me?")]
public bool RememberMe { get; set; }
}
}
Controller
[HttpGet]
public IActionResult Login(string? returnurl)
{
ViewData["ReturnUrl"] = returnurl;
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginViewModel loginViewModel, string? returnurl)
{
ViewData["ReturnUrl"] = returnurl;
returnurl = returnurl ?? Url.Content("~/");
if (ModelState.IsValid)
{
var result = await _signInManager.PasswordSignInAsync(loginViewModel.Email, loginViewModel.Password, loginViewModel.RememberMe, lockoutOnFailure: true);
if (result.Succeeded)
{
return LocalRedirect(returnurl);
}
if (result.IsLockedOut)
{
return View("Lockout");
}
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return View(loginViewModel);
}
}
return View(loginViewModel);
}
View:
#model LoginViewModel
<h1 class="text-info">Log in</h1>
<div class="row">
<div class="col-md-8">
<form asp-controller="Account" asp-action="Login" asp-route-returnurl="#ViewData["ReturnUrl"]" method="post" role="form">
<h4>Use a local account to log in</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Email" class="col-md-2"></label>
<div class="col-md-10">
<input asp-for="Email" class="form-control" />
<span asp-validation-for="Email" class="text-danger"></span>
</div>
</div>
<div class="form-group mt-3">
<label asp-for="Password" class="col-md-2"></label>
<div class="col-md-10">
<input asp-for="Password" class="form-control" />
<span asp-validation-for="Password" class="text-danger"></span>
</div>
</div>
<div class="form-check mt-3">
<input class="form-check-input" asp-for="RememberMe" type="checkbox" value="" id="flexCheckChecked">
<label class="form-check-label" asp-for="RememberMe" for="flexCheckChecked">
Remember me?
</label>
</div>
<div class="form-group">
<div class=" col-1 my-3">
<button type="submit" asp-controller="Account" asp-action="Login" class="btn btn-success form-control">Login</button>
</div>
</div>
<p>
<a asp-action="Register">Register as a new user?</a>
</p>
<p>
<a asp-action="ForgotPassword">Forgot your passord?</a>
</p>
</form>
</div>
</div>
"I'm not able to get ReturnUrl to work on HttpPost using ASP.NET Core 6 MVC.":
I have checked your code between the line. It doesn't has anything
wrong with [FromQuery] So you don't need to do anything on [FromQuery] as other answer I've seen, may be deleted now.
Issue Replication:
I have reproduced your issue successfully as you can see below:
What Causing the Issue:
If you investigate your code again you would noticed that you are
using asp-controller="Login" asp-action="Login" twice on your
Login.cshtml at the begining of the form and at the point of submit button this causing the data loss while you are submitting the form.
At the starting on form:
<form asp-controller="Login" asp-action="Login" asp-route-returnurl="#ViewData["ReturnUrl"]" method="post" role="form">
At your button submit::
<button type="submit" asp-controller="Login" asp-action="Login" class="btn btn-success form-control">Login</button>
Solution:
The easiest solution is just modify your submit button code like below which will resolve your issue:
<div class="form-group">
<div class=" col-1 my-3">
<button type="submit" class="btn btn-success form-control">Login</button>
</div>
</div>
Output:
Hope it will resolve your returnurl null issue completely.
Controller
[HttpGet]
public IActionResult Login(string? returnurl) <-- you don't need it
{
ViewData["ReturnUrl"] = returnurl; <-- you don't need it
return View();
}
View
<form method="post" role="form"> <-- you can do that and the returnUrl
will be posted to you anyway
Or
<form method="post" role="form" new { returnUrl = Context.Request.QueryString["ReturnUrl"]} >
it will work either way

Model conflict accurs as it says the ViewDataDictionary is passed a model #model1 but requires the model of type #model2 asp.net Identity

There is a conflict occurring regarding the DataViewDictionary which requires a model of different type while a different type model is being passed.
I tried to avoid this conflict by using ViewBags to get data from controllers but as a matter of fact the trick didn't work while passing data back to the controller using HttpPost requests. I have also tried using Common Model approach to resolve the issue but to no avail
[AllowAnonymous]
public IActionResult Login(string returnUrl)
{
Login login = new Login();
login.ReturnUrl = returnUrl;
return View(login);
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(Login login )
{
if (ModelState.IsValid)
{
ApplicationUser appUser = await userManager.FindByEmailAsync(login.Email);
if (appUser != null)
{
await signInManager.SignOutAsync();
Microsoft.AspNetCore.Identity.SignInResult result = await signInManager.PasswordSignInAsync(appUser, login.Password, false, false);
if (result.Succeeded)
{
if(User.IsInRole("Administrator") || User.IsInRole("Editor"))
{
return RedirectToAction("Dashboard", "Admin");
}
else
{
return RedirectToAction("Index", "Home");
}
}
}
ModelState.AddModelError(nameof(login.Email), "Login Failed: Invalid Email or password");
}
return View(login);
}
Startup.cs code is as follows:
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.ConfigureApplicationCookie(options =>
{
options.AccessDeniedPath = "/Account/AccessDenied";
options.Cookie.Name = "Cookie";
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(720);
options.LoginPath = "/Home/";
options.ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter;
options.SlidingExpiration = true;
});
The expected result should be to login the user successfully and same for register user. But the following exception is thrown as I click the login button.
InvalidOperationException: The model item passed into the ViewDataDictionary is of type 'PixageStudioWeb.Models.Login', but this ViewDataDictionary instance requires a model item of type 'PixageStudioWeb.Models.Register'.
I have several partial views of which some are _LoginPartial and _RegisterPartial in the Index file.I have used common model named Multimodel of which the code is as follows.
#model MultiModel
<div id="form" style="min-width:375px;">
<div class="text-danger" asp-validation-summary="All"></div>
<form asp-action="Login" asp-controller="Account" method="post"
style="margin:5px 5px 5px 5px">
<input type="hidden" asp-for="login.ReturnUrl" />
<ul>
<li>
<label asp-for="login.Email"></label>
<input asp-for="login.Email" class="col-md-6" />
</li>
<li>
<label asp-for="login.Password"></label>
<input type="password" asp-for="login.Password" class="col-md-6" />
</li>
<li>
<button type="submit">Login</button>
<label>
<input type="checkbox" checked="checked" name="remember">
Remember me
</label>
</li>
<li>
<button type="button"
onclick="document.getElementById('id01').style.display='none'"
class="cancelbtn">Cancel</button>
<span class="psw">Forgot password?</span>
</li>
</ul>
</form>
</div>
The Multimodel class
public partial class MultiModel
{
public IEnumerable<Category> categories { get; set; }
public IEnumerable<ImagePool> images { get; set; }
public Login login { get; set; }
public Register register { get; set; }
}

Razor Pages keep data between class methods

The problem that I have faced lately when trying to write a site on razor pages - when I use variables in a class method, it doesn't keep that data inside. In example: I have a method which creates data when the page is created. And when I press submit button: The data isn't remembered inside the class and thus it returns null.
I've tried to work with Data binding, Temp data, Private classes. Neither of them kept data for a future use inside one class. The current code is:
`
namespace TestSite.Pages.Shared
{
public class Registration_2Model : PageModel
{
private readonly TestSite.Data.ApplicationDbContext _context;
public UserManager<IdentityUser> _user;
public string _ID { get; set; }
public string _Code { get; set; }
public bool _Validated;
public Registration_2Model(UserManager<IdentityUser> UserManager, ApplicationDbContext context)
{
_context = context;
_user = UserManager;
}
public IActionResult OnGet()
{
var CurrentUser = _context.Simple_User.FirstOrDefault(m => m.ID == Guid.Parse(_user.GetUserId(User)));
if (CurrentUser == null)
{
_ID = _user.GetUserId(User);
_Code = GenerateCode();
_Validated = false;
TempData["ID"] = _ID;
TempData["Code"] = _Code;
return Page();
} else { return Redirect("/Index"); }
}
[BindProperty]
public Simple_User Simple_User { get; set; }
public async Task<IActionResult> OnPostAsync()
{
Simple_User.ID = Guid.Parse((string)TempData["ID"]);
Simple_User.Code = (string)TempData["Code"];
Simple_User.Validated = false;
if (!ModelState.IsValid)
{
return Page();
}
_context.Simple_User.Add(Simple_User);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
private string GenerateCode()
{
Random _random = new Random();
return $"{_random.Next(1000, 9999).ToString()}-{DateTime.Now.Year}";
}
}
}
`
and HTML:
`
#{
ViewData["Title"] = "Second registration";
}
<h2>Second registration</h2>
<h4>One step left. After your initial registration, you must fill in some blanks, after which, our moderator will check and add you to our list.</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label class="control-label">ID</label>
<input class="form-control" type="text" placeholder="#Model._ID" readonly />
</div>
<div class="form-group">
<label asp-for="Simple_User.Name" class="control-label"></label>
<input asp-for="Simple_User.Name" class="form-control" />
<span asp-validation-for="Simple_User.Name" class="text-danger"></span>
<span asp-validation-for="Simple_User.Code" class="text-danger"></span>
</div>
<div class="form-group">
<label class="control-label">Code</label>
<input class="form-control" type="text" placeholder="#Model._Code" readonly />
</div>
<div class="form-group">
<label asp-for="Simple_User.Address" class="control-label"></label>
<input asp-for="Simple_User.Address" class="form-control" />
<span asp-validation-for="Simple_User.Address" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</form>
</div>
</div>
<div>
<a asp-page="Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}`
Basically, site displays some value in an inaccessible field and should use it when it creates a Simple_User in database. But so far I got only nulls
HTTP is stateless™. If you want to set properties on objects during the execution of one request, and have those values available for a subsequent request, you need to implement your own state management strategy.
There are a lot of options available to you, depending on what you wan to do. If security is important, you should look at session variables. In the meantime, here are all of the options available: https://www.learnrazorpages.com/razor-pages/state-management
Try scaffolding the pages. You can throw out the Create/Details/Delete pages and keep only the Edit page if that is what your looking for. You can do this from the command line or in Visual Studio when creating a new page. Something like the following from the CLI (see here for more details)
dotnet aspnet-codegenerator razorpage -m SimpleUser -dc ApplicationDbContext
Also if you want your _ID or _Code properties to be sent in the post add the [BindProperty] attribute to them. See here for more info about Model Binding.

ASP.NET Core 2.0 Identity - password error message not showing in view

I'm having a problem using the default ASP.NET Identity service. Everything is working but when trying to register for an account, if the password complexity is not met, no error messages are displayed.
I ran the debugger, and below is the error that is thrown:
result {Failed : PasswordTooShort,PasswordRequiresDigit,PasswordRequiresUpper,PasswordRequiresUniqueChars} Microsoft.AspNetCore.Identity.IdentityResult
The error message is not passed into the view to be displayed on the Password field. How do you pass the errors into the view? I can manually create a modelstate error but would like the pre-configured messages to be passed in instead.
**Update, I am able to pull in the error messages but trying to solve how to pass all messages into the view. Right now, the view only displays the first error message. Do I need to loop through all the error messages in the view to do that? If so, what would be the best approach?
I have configured the services as below in my startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<AppDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<AppDbContext>();
services.Configure<IdentityOptions>(options =>
{
// Password settings
options.Password.RequireDigit = true;
options.Password.RequiredLength = 8;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = true;
options.Password.RequireLowercase = false;
options.Password.RequiredUniqueChars = 6;
});
services.AddTransient<IFeedbackRepository, FeedbackRepository>();
services.AddTransient<IPieRepository, PieRepository>();
services.AddMvc();
}
My Account controller is configured as below:
public class AccountController : Controller
{
private readonly SignInManager<IdentityUser> _signInManager;
private readonly UserManager<IdentityUser> _userManager;
public AccountController(SignInManager<IdentityUser> signInManager, UserManager<IdentityUser> userManager)
{
_signInManager = signInManager;
_userManager = userManager;
}
public IActionResult Register()
{
return View(new LoginViewModel());
}
[HttpPost]
public async Task<IActionResult> Register(LoginViewModel loginViewModel)
{
if (ModelState.IsValid)
{
var user = new IdentityUser() { UserName = loginViewModel.UserName };
var result = await _userManager.CreateAsync(user, loginViewModel.Password);
if (result.Succeeded)
{
return RedirectToAction("Index", "Home");
}
else
{
var errList = "";
var error = result.Errors.ToList(); //convert to list
foreach (var err in error) //iterate through individual error
{
this.ModelState.AddModelError("Password", err.Description); //add error to modelstate
//errList += string.Join(", ", err.Description);
}
//this.ModelState.AddModelError("Password", errList);
}
}
return View(loginViewModel);
}
The loginviewmodel is configured as:
public class LoginViewModel
{
[Key]
[Required]
[Display(Name ="User Name")]
public string UserName { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
}
}
The View for register is configured as:
#model LoginViewModel
<h2>Register</h2>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Register" asp-controller="Account" method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<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><br />
<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">
<input type="submit" value="Register" class="btn btn-default" />
</div>
</form>
</div>
</div>
I can't validate this easily (no compiler/vs at hand) but you could try to add the model error in an explicit way before you render the view again
if (result.Succeeded)
{
return RedirectToAction("Index", "Home");
}
else
{
var error = string.Join( ", ", result.Errors );
this.ModelState.AddModelError( "Password", error );
}

Categories