Error: NullReferenceException: Object reference not set to an instance of an object.
Web.Controllers.ManageController.ChangeUser(BaseViewModel model) in ManageController.cs
+
user.FirstName = model.ChangeUserViewModel.FirstName;
I cannot understand why I am getting this error, could you please help me find what I am doing wrong ?
What i am trying to achieve is updating the user information trough my viewmodel.
Can you please advise if the way i am trying to do it is correct?
BaseViewModel:
public class BaseViewModel
{
public IndexViewModel IndexViewModel { get; set; }
public ChangeUserViewModel ChangeUserViewModel { get; set; }
}
ChangeUserViewModel:
public class ChangeUserViewModel
{
[Required]
[StringLength(20, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 1)]
public string FirstName { get; set; }
[Required]
[StringLength(20, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 1)]
public string LastName { get; set; }
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
[Display(Name = "Profile Picture")]
[DataType(DataType.Upload)]
[MaxFileSize(5* 1024 * 1024)]
[AllowedExtensions(new string[] { ".jpg", ".png", ".jpeg", ".gif", ".tif" })]
public IFormFile ProfilePicture { get; set; }
}
Controller:
public async Task<IActionResult> Index()
{
var user = await GetCurrentUserAsync();
var model = new BaseViewModel
{
IndexViewModel = new IndexViewModel
{
HasPassword = await _userManager.HasPasswordAsync(user),
PhoneNumber = await _userManager.GetPhoneNumberAsync(user),
TwoFactor = await _userManager.GetTwoFactorEnabledAsync(user),
Logins = await _userManager.GetLoginsAsync(user),
BrowserRemembered = await _signInManager.IsTwoFactorClientRememberedAsync(user),
AuthenticatorKey = await _userManager.GetAuthenticatorKeyAsync(user),
},
ChangeUserViewModel = new ChangeUserViewModel
{
FirstName = user.FirstName,
LastName = user.LastName,
Email = user.Email
}
};
return View(model);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ChangeUser(BaseViewModel model)
{
if (!ModelState.IsValid)
{
return RedirectToAction("Index", "Manage");
}
var user = await GetCurrentUserAsync();
if (user != null)
{
user.FirstName = model.ChangeUserViewModel.FirstName;
user.LastName = model.ChangeUserViewModel.LastName;
user.Email = model.ChangeUserViewModel.Email;
await _userManager.UpdateAsync(user);
}
return View("Index", model);
}
View:
#model BaseViewModel
#inject UserManager<ApplicationUser> UserManager
#{
ViewData["Title"] = "Manage your account";
}
<h2 class="content-heading pt-0">
<i class="fa fa-fw fa-user-circle text-muted mr-1"></i> User Profile
</h2>
<form asp-controller="Manage" asp-action="ChangeUser" method="post" class="form-horizontal" role="form" enctype="multipart/form-data">
<div class="row push">
<div class="col-lg-4">
<p class="text-muted">
Your account’s vital info.
</p>
</div>
<div asp-validation-summary="All" class="text-danger"></div>
<div class="col-lg-8 col-xl-5">
<div class="form-group">
<label for="dm-profile-edit-firstname">Firstname</label>
<input asp-for="ChangeViewModel.FirstName" type="text" class="form-control" id="dm-profile-edit-firstname" name="dm-profile-edit-firstname" >
</div>
<div class="form-group">
<label for="dm-profile-edit-lastname">Lastname</label>
<input asp-for="ChangeViewModel.LastName" type="text" class="form-control" id="dm-profile-edit-lastname" name="dm-profile-edit-lastname">
</div>
<div class="form-group">
<label for="dm-profile-edit-email">Email Address</label>
<input asp-for="ChangeViewModel.Email" type="email" class="form-control" id="dm-profile-edit-email" name="dm-profile-edit-email">
</div>
<div class="form-group">
<label>Your Avatar</label>
<div class="push">
<img class="img-avatar" src="#Url.Action("ProfilePicture", "Account" )" alt="">
</div>
<div class="custom-file">
<input asp-for="ChangeViewModel.ProfilePicture" type="file" class="custom-file-input js-custom-file-input-enabled" data-toggle="custom-file-input" id="ProfilePicture" name="ProfilePicture">
<label class="custom-file-label" for="ProfilePicture">Choose a new avatar</label>
</div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-alt-primary">
<i class="fa fa-check-circle mr-1"></i> Update Profile
</button>
</div>
</div>
</div>
</form>
It seems the controller didn't recieve the BaseViewModel when do post request from view. I suggest you could use Newtonsoft’s Json.NET instead of System.Text.Json.
Step1. Install the following Nuget package
Install-Package Microsoft.AspNetCore.Mvc.NewtonsoftJson
Step2.
If you are migrating from an existing project you’ll have a call to “AddMvc()” which you can then tack onto it like so :
services.AddMvc().AddNewtonsoftJson();
However in new .NET Core 3+ projects, you have a different set of calls replace MVC. So you’ll probably have one of the following :
services.AddControllers().AddNewtonsoftJson();
services.AddControllersWithViews().AddNewtonsoftJson();
services.AddRazorPages().AddNewtonsoftJson();
Then, place your breakpoint in your controller code to check the value of BaseViewModel model.
Related
I am learning razor pages and followed several decent examples on Microsoft to create a very rudimentary application. I was wondering if anyone had any resources on how I could add a profile image to razor pages. I am scaffolding identity in Visual studio but nowhere can I find any tutorial on how to integrate an image.
I do have a simple model and controller that will save a file path to the DB but fail to integrate this to the razor templates.
If anyone has any resources for what I am looking to learn I would greatly appreciate it. Apologies if this is very basic, I'm probably not using the correct terminology to search in Google.
identity user in the Db.
I do have a simple model and controller that will save a file path to
the DB but fail to integrate this to the razor templates.
Model:
Let's assume you have Identity base Model as below:
public class IdentityUser
{
[Key]
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
Now you would like to add profile picture functionality there:
So you could extent your class as below:
public class IdentityExtendedModel : IdentityUser
{
public string ImageName { get; set; }
public string ProfilePictureLocation { get; set; }
}
Identity View Model:
We don't want to manipulate our identity Model directly while submitting our form inputso I will use viewModel
public class IdentityViewModel
{
[Display(Name = "First Name")]
public string FirstName { get; set; }
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Display(Name = "Username")]
public string Username { get; set; }
[Phone]
[Display(Name = "Phone number")]
public string PhoneNumber { get; set; }
[Display(Name = "Profile Picture")]
public byte[] ProfilePicture { get; set; }
}
View Upload Profile Picture:
#model DotNet6MVCWebApp.Models.IdentityViewModel
<form asp-action="CreateIdentityProfilePic" method="post" enctype="multipart/form-data">
<div class="row">
<div class="col-md-6">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="FirstName"></label>
<input asp-for="FirstName" class="form-control" />
<span asp-validation-for="FirstName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="LastName"></label>
<input asp-for="LastName" class="form-control" />
<span asp-validation-for="LastName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Username"></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="PhoneNumber"></label>
<input asp-for="PhoneNumber" class="form-control" />
<span asp-validation-for="PhoneNumber" class="text-danger"></span>
</div>
<button id="update-profile-button" type="submit" class="btn btn-primary">Save</button>
</div>
<div class="col-md-6">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="ProfilePicture" style="width: 100%;"></label>
#if (Model.ProfilePicture != null)
{
<img id="profilePicture" style="width:350px;height:350px; object-fit:cover" src="data:image/*;base64,#(Convert.ToBase64String(Model.ProfilePicture))">
}
else
{
<img id="profilePicture" style="width:350px;height:350px; object-fit:cover" src="">
}
<input type="file"
accept=".png,.jpg,.jpeg,.gif,.tif"
asp-for="ProfilePicture"
class="form-control"
style="border:0px!important;padding: 0px;padding-top: 10px;padding-bottom: 30px;"
onchange="document.getElementById('profilePicture').src = window.URL.createObjectURL(this.files[0])" />
<span asp-validation-for="ProfilePicture" class="text-danger"></span>
</div>
</div>
</div>
</form>
Controller:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> CreateIdentityProfilePic(InputModel model, IFormFile profilePicture)
{
if (profilePicture == null || profilePicture.Length == 0)
{
return Content("File not selected");
}
var path = Path.Combine(_environment.WebRootPath, "ImageName/Cover", profilePicture.FileName);
using (FileStream stream = new FileStream(path, FileMode.Create))
{
await profilePicture.CopyToAsync(stream);
stream.Close();
}
if (model == null)
{
return Content("Invalid Submission!");
}
var identityModel = new IdentityModel
{
FirstName = model.FirstName,
LastName = model.LastName,
Username = model.Username,
PhoneNumber = model.PhoneNumber,
ImageName = profilePicture.FileName,
ProfilePictureLocation = path,
};
_context.Add(identityModel);
await _context.SaveChangesAsync();
return RedirectToAction("IdentityProfilePicList");
}
Note: When you would submit Upload profile Picture view this controller should receive the request.
Controller Profile Picture List:
public ActionResult IdentityProfilePicList()
{
var memberList = _context.identityModels.ToList();
return View(memberList);
}
View Profile Picure List:
#model IEnumerable<DotNet6MVCWebApp.Models.IdentityExtendedModel>
#{
ViewData["Title"] = "Index";
}
<div class="form-row">
<table>
<tr>
<td>
<a asp-action="Index" class="btn btn-success">Create New</a>
</td>
<form method="get" action="#">
<td style="padding-right:760px">
</td>
<td>
<input class="form-control" type="text" placeholder="Search for..." name="SearchString" value="#ViewData["CurrentFilter"]" aria-label="Search" aria-describedby="btnNavbarSearch" />
</td>
<td>
<input type="submit" value="Search" class="btn btn-primary" />
</td>
</form>
</tr>
</table>
</div>
<table class="table">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.FirstName)
</th>
<th>
#Html.DisplayNameFor(model => model.LastName)
</th>
<th>
#Html.DisplayNameFor(model => model.Username)
</th>
<th>
#Html.DisplayNameFor(model => model.ProfilePictureLocation)
</th>
<th>
Member Details
</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.FirstName)
</td>
<td>
#Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
#Html.DisplayFor(modelItem => item.Username)
</td>
<td>
<img src="~/ImageName/Cover/#item.ImageName"
class="rounded-square"
height="50" width="75"
style="border:1px"
asp-append-version="true" accept="image/*" />
</td>
<td>
<a asp-action="EditMember" class="btn btn-primary" asp-route-memberId="#item.Id">Details</a> | <a asp-action="EditMember" class="btn btn-warning" asp-route-memberId="#item.Id">Edit</a>
</td>
</tr>
}
</tbody>
</table>
Output:
Database:
Note: If you need any further assistance on this please check this GitHub repository.
Thanks for all the help it helped me massively to resolve the issue. At the end of registering the image in the scaffolded razor pages Identity I had to use the code behind file register.cshtl.cs and add it to the Input module there and be consumed by the register.cshtml which now works perfectly.
I hope this helps someone else.
First I extended the ApplicationUser and added the migration.
public class ApplicationUser : IdentityUser
{
[Required]
public string Name { get; set; }
public string? StreetAddress { get; set; }
public string? City { get; set; }
public string? PostalCode { get; set; }
[ValidateNever]
public string? ImageUrl { get; set; }
public int? CompanyId { get; set; }
[ForeignKey("CompanyId")]
[ValidateNever]
public Company Company { get; set; }
}
Then added to the Input model in the Register.cshtml.cs
public class InputModel
{
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[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; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
//Added custom fields
[Required]
public string Name { get; set; }
public string? StreetAddress { get; set; }
public string? City { get; set; }
public string? PostalCode { get; set; }
public string? PhoneNumber { get; set; }
[ValidateNever]
public string? ImageUrl { get; set; }
public string? Role { get; set; }
public int? CompanyId { get; set; }
[ValidateNever]
public IEnumerable<SelectListItem> RoleList { get; set; }
[ValidateNever]
public IEnumerable<SelectListItem> CompanyList { get; set; }
}`
Then in the on Post method add the file path to the database.
public async Task<IActionResult> OnPostAsync(IFormFile file, string returnUrl = null)
{
returnUrl ??= Url.Content("~/");
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
if (ModelState.IsValid)
{
var user = CreateUser();
string wwwRootPath = _hostEnvironment.WebRootPath;
if (file != null)
{
string fileName = Guid.NewGuid().ToString();
var uploads = Path.Combine(wwwRootPath, #"images\companies");
var extension = Path.GetExtension(file.FileName);
if (Input.ImageUrl != null)
{
var oldImagePath = Path.Combine(wwwRootPath, Input.ImageUrl.TrimStart('\\'));
}
using (var fileStreams = new FileStream(Path.Combine(uploads, fileName + extension), FileMode.Create))
{
file.CopyTo(fileStreams);
}
Input.ImageUrl = #"\images\companies\" + fileName + extension;
}
else
{
Input.ImageUrl = #"\images\companies\QPQ-logo.jpg";
}
await _userStore.SetUserNameAsync(user, Input.Email, CancellationToken.None);
await _emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None);
user.Name = Input.Name;
user.StreetAddress = Input.StreetAddress;
user.City = Input.City;
user.PostalCode = Input.PostalCode;
user.PhoneNumber = Input.PhoneNumber;
user.ImageUrl = Input.ImageUrl;
var result = await _userManager.CreateAsync(user, Input.Password);
if (result.Succeeded)
{
_logger.LogInformation("User created a new account with password.");
if (Input.Role == null)
{
await _userManager.AddToRoleAsync(user, SD.Role_User_Indi);
}
else
{
await _userManager.AddToRoleAsync(user, Input.Role);
}
var userId = await _userManager.GetUserIdAsync(user);
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 = userId, 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();
}`
One of the first mistakes I made was not declaring the front-end form as a multipart form in the Register.cshtml.
<h1>#ViewData["Title"]</h1><div class="row pt-4">
<div class="col-md-8">
<form id="registerForm" class="row" asp-route-returnUrl="#Model.ReturnUrl" method="post" enctype="multipart/form-data">
<h2>Create a new account.</h2>
<hr />
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-floating py-2 col-12">
<input asp-for="Input.Email" class="form-control" aria-required="true" />
<label asp-for="Input.Email"></label>
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<div class="form-floating py-2 col-6">
<input asp-for="Input.Name" class="form-control" aria-required="true" />
<label asp-for="Input.Name"></label>
<span asp-validation-for="Input.Name" class="text-danger"></span>
</div>
<div class="form-floating py-2 col-6">
<input asp-for="Input.StreetAddress" class="form-control" />
<label asp-for="Input.StreetAddress"></label>
<span asp-validation-for="Input.StreetAddress" class="text-danger"></span>
</div>
<div class="form-floating py-2 col-6">
<input asp-for="Input.City" class="form-control" />
<label asp-for="Input.City"></label>
<span asp-validation-for="Input.City" class="text-danger"></span>
</div>
<div class="form-floating py-2 col-6">
<input asp-for="Input.PostalCode" class="form-control" />
<label asp-for="Input.PostalCode"></label>
<span asp-validation-for="Input.PostalCode" class="text-danger"></span>
</div>
<div class="form-floating py-2 col-6">
<input asp-for="Input.Password" class="form-control" aria-required="true" />
<label asp-for="Input.Password"></label>
<span asp-validation-for="Input.Password" class="text-danger"></span>
</div>
<div class="form-floating py-2 col-6">
<input asp-for="Input.ConfirmPassword" class="form-control" aria-required="true" />
<label asp-for="Input.ConfirmPassword"></label>
<span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
</div>
<div class="form-floating py-2 col-6">
<select asp-for="Input.Role" asp-items="#Model.Input.RoleList" class=form-select>
<option disabled selected>-Select Role-</option>
</select>
</div>
<div class="form-floating py-2 col-6">
<select asp-for="Input.CompanyId" asp-items="#Model.Input.CompanyList" class=form-select>
<option disabled selected>-Select Company-</option>
</select>
</div>
<div class="form-floating py-2 col-12">
<label asp-for="Input.ImageUrl"></label>
<input asp-for="Input.ImageUrl" type="file" id="uploadBox" name="file" class="form-control" />
</div>
<button id="registerSubmit" type="submit" class="w-100 btn btn-lg btn-primary">Register</button>
</form>
</div>
<div class="col-md-4">
<section>
<h3>Use another service to register.</h3>
<hr />
#{
if ((Model.ExternalLogins?.Count ?? 0) == 0)
{
<div>
<p>
There are no external authentication services configured. See this <a href="https://go.microsoft.com/fwlink/?LinkID=532715">article
about setting up this ASP.NET application to support logging in via external services</a>.
</p>
</div>
}
else
{
<form id="external-account" asp-page="./ExternalLogin" asp-route-returnUrl="#Model.ReturnUrl" method="post" class="form-horizontal">
<div>
<p>
#foreach (var provider in Model.ExternalLogins)
{
<button type="submit" class="btn btn-primary" name="provider" value="#provider.Name" title="Log in using your #provider.DisplayName account">#provider.DisplayName</button>
}
</p>
</div>
</form>
}
}
</section>
</div>
I've a Page entity dependent on Investor entity as investor creates a page:
public class Page
{
[Required, DatabaseGenerated(DatabaseGeneratedOption.Identity), Key]
public Guid Id { get; set; }
[Required(ErrorMessage = "Describe the idea of your blog")]
public string Description { get; set; }
[Display(Name = "Тags")]
[Required(ErrorMessage = "Let people know what content you're going to share")]
public ICollection<Investor> Subscribers { get; set; }
public Guid AuthorId { get; set; }
public Investor Author { get; set; }
}
That's what in my dbContext:
modelBuilder.Entity<Page>()
.HasOne(b => b.Author)
.WithOne(u=> u.Page)
.HasForeignKey<Page>(b => b.AuthorId)
.OnDelete(DeleteBehavior.Restrict);
Here's the controller for creating a Page:
public class PageController : Controller
{
private readonly DataManager _dataManager;
private readonly UserManager<ApplicationUser> _userManager;
public PageController(UserManager<ApplicationUser> userManager, DataManager dataManager)
{
_userManager = userManager;
_dataManager = dataManager;
}
public IActionResult Create(Guid Id)
{
var investor = _dataManager.Investors.GetInvestorById(Id);
if (investor.Page != null)
{
return RedirectToPage("/Account/ProfileCheck", new { Id = investor.User.Id, area = "Identity"});
}
var page = new Page();
return View(page);
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Create(Page page)
{
if (ModelState["Description"].Errors.Count() == 0)
{
var currentUserId = _userManager.GetUserId(HttpContext.User);
var investor = _dataManager.Investors.GetInvestorByUserId(currentUserId);
page.Author = investor;
_dataManager.Pages.SavePage(page);
return RedirectToPage("/Account/ProfileCheck", new { Id = investor.User.Id, area = "Identity" });
}
return View(page);
}
}
However, In HttpPost Action that page has an Id and this Id equals to author's Id who created it.
This's the view for Create action
#model FinicWebApp.Domain.Entitites.Page
<form method="post" asp-action="Create">
<div class="border p-3">
<div asp-validation-summary="ModelOnly" class="text-danger" ></div>
<div class="form-group row">
<h2 class="text-black-50 pl-3">Create Page</h2>
</div>
<div class="row">
<div class="col-12">
<div class="form-group row">
<div class="col-6">
<label asp-for="Description"></label>
</div>
</div>
<div class="form-group row">
<div class="col-4">
<input asp-for="Description" class="form-control" />
<span asp-validation-for="Description" class="text-danger"></span>
</div>
</div>
<div class="form-group row">
<div class="col-8 text-center row">
<div class="col">
<input type="submit" class="btn btn-info w-75" value="Create"/>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
In my db all the pages' ids equal to investors' ids who created them. But I need pages' ids to be different. How can I get it?
I'm trying to create a comment and like box like the one shown in this tutorial what are the main changes I need to make in order to make it work with ASP.NET Core 3.1
https://www.c-sharpcorner.com/blogs/how-to-create-a-comment-box-session-as-like-facebook-comment-in-asp-net
To create a Facebook-like comment box in ASP.NET Core, I do a demo to make it, referring to official documentation to configure database:https://learn.microsoft.com/en-us/aspnet/core/tutorials/first-mvc-app/adding-model?view=aspnetcore-5.0&tabs=visual-studio
1.To Create the comment box, we need to create User, Reply, Comment models to add some tables ,and add some ViewModels.
User.cs:
public class User
{
[Key]
public int Id { get; set; }
[Required]
public string UserName { get; set; }
[Required]
public string Password { get; set; }
[Required]
public string Email { get; set; }
public string ImageUrl { get; set; }
public DateTime? CreateOn { get; set; }
}
Reply.cs:
public class Reply
{
[Key]
public int Id { get; set; }
[Required]
public string Text { get; set; }
public DateTime? CreateOn { get; set; }
public int UserId { get; set; }
[ForeignKey("UserId")]
public virtual User User { get; set; }
public int CommentId { get; set; }
[ForeignKey("CommentId")]
public virtual Comment Comment{get;set;}
}
Comment.cs:
public class Comment
{
[Key]
public int Id { get; set; }
[Required]
public string Text { get; set; }
public DateTime? CreateOn { get; set; }
public int UserId { get; set; }
[ForeignKey("UserId")]
public virtual User User { get; set; }
public ICollection<Reply> Replies { get; set; }
public ICollection<User> Users { get; set; }
}
LoginVM.cs:
public class LoginVM
{
[Required]
[Key]
public string UserName { get; set; }
[Required]
public string Password { get; set; }
}
RegisterVM.cs:
public class RegisterVM
{
[Required]
public string UserName { get; set; }
[Required]
public string Password { get; set; }
[Compare("Password")]
public string ConfirmPassword { get; set; }
[Required]
public string Email { get; set; }
}
ReplyVM.cs:
public class ReplyVM
{
public string Reply { get; set; }
public int CID { get; set; }
}
Installs the EF Core package
From the Tools menu, select NuGet Package Manager > Manage NuGet Packages for solution.
[Note]My core3.1 installed 3.1.21 version
Microsoft.EntityFrameworkCore
Microsoft.EntityFrameworkCore.Tools
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.SqlServer.Design
Microsoft.EntityFrameworkCore.Proxies
Microsoft.Extensions.Configuration.JSON
creates a database context class: Models/ApplicationDbContext.cs
public class ApplicationDbContext: DbContext
{
public ApplicationDbContext(DbContextOptions options) :base(options)
{
}
public DbSet<User> Users { get; set; }
public DbSet<Comment> Comments { get; set; }
public DbSet<Reply> Replies { get; set; }
public DbSet<commentbox.ViewModels.LoginVM> LoginVM { get; set; }
}
4.Add codes in Startup.ConfigureServices:
public void ConfigureServices(IServiceCollection services)
{
services.AddSession();
services.AddControllersWithViews();
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("ApplicationDbContext")));
}
5.Add app.UseSession(); in Startup.Configure
6.Add a connection string to the appsettings.json file:
"ConnectionStrings": {
"ApplicationDbContext": "Server=(localdb)\\mssqllocaldb;Database=ApplicationDbContext-1;Trusted_Connection=True;MultipleActiveResultSets=true"}
7.In the Package Manager Console (PMC), enter the following commands separately:
Add-Migration InitialCreate
Update-Database
8.Add HomeController to set userId=0 when people first Login without register, add AccountController to manage user login or not, add ChatRoomController to manage user post comments or reply.
HomeController.cs:
public class HomeController : Controller
{ ....
public IActionResult Index()
{
ISession session = HttpContext.Session;
session.SetInt32("UserId", 0);
return View();
}
...
}
AccountController.cs:
public class AccountController : Controller
{
private readonly ApplicationDbContext db;
public AccountController(ApplicationDbContext context)
{
db = context;
}
//GET:Account/Register
[HttpGet]
public IActionResult Register()
{
return View();
}
//GET:Account/Register
[HttpPost]
public IActionResult Register(RegisterVM obj)
{
bool UserExistis=db.Users.Any(x=>x.UserName==obj.UserName);
if(UserExistis)
{
ViewBag.UserNameMessage = "This UserName is already in use, try another";
return View();
}
bool EmailExistis = db.Users.Any(y => y.Email == obj.Email);
if (EmailExistis)
{
ViewBag.EmailMessage = "This Email is already in use, try another";
return View();
}
//if username and email is unique, then we save or register the user
User u = new User();
u.UserName = obj.UserName;
u.Password = obj.Password;
u.Email = obj.Email;
u.ImageUrl = "";
u.CreateOn = DateTime.Now;
db.Users.Add(u);
db.SaveChanges();
return RedirectToAction("Index","ChatRoom");
}
//GET:Account/Login
[HttpGet]
public IActionResult Login()
{
return View();
}
//GET:Account/Login
[HttpPost]
public IActionResult Login(LoginVM obj)
{
bool existis = db.Users.Any(u => u.UserName == obj.UserName&&u.Password==obj.Password);
if(existis)
{
ISession session = HttpContext.Session;
session.SetInt32("UserId", db.Users.Single(x => x.UserName == obj.UserName).Id);
return RedirectToAction("Index","ChatRoom");
}
//if invalid credentials
ViewBag.Message = "Invalid Credentials!";
return View();
}
}
ChatRoomController.cs:
public class ChatRoomController: Controller
{
private readonly ApplicationDbContext db;
public ChatRoomController(ApplicationDbContext context)
{
db = context;
}
public IActionResult Index()
{
var comments = db.Comments.Include(x => x.Replies).ThenInclude(x=>x.User).OrderByDescending(x => x.CreateOn)
.ToList();
return View(comments);
}
//Post:ChatRoom/PostReply
[HttpPost]
public ActionResult PostReply(ReplyVM obj)
{
ISession session = HttpContext.Session;
int userId =(int)session.GetInt32("UserId");
if (userId==0)
{
return RedirectToAction("Login", "Account");
}
Reply r = new Reply();
r.Text = obj.Reply;
r.CommentId = obj.CID;
r.UserId =userId;
r.CreateOn = DateTime.Now;
db.Replies.Add(r);
db.SaveChanges();
return RedirectToAction("Index");
}
//Post:ChatRoom/PostComment
[HttpPost]
public ActionResult PostComment(string CommentText)
{
ISession session = HttpContext.Session;
int userId = (int)session.GetInt32("UserId");
if (userId == 0)
{
return RedirectToAction("Login", "Account");
}
Comment c = new Comment();
c.Text = CommentText;
c.CreateOn = DateTime.Now;
c.UserId = userId;
db.Comments.Add(c);
db.SaveChanges();
return RedirectToAction("Index");
}
}
9.Add views: Login.cshtml(for Login action in AccountController), Register.cshtml(for Register action in AccountController), index.cshtml(for Index action in ChatRoomController).
Login.cshtml:
#model commentbox.ViewModels.LoginVM
#{
ViewData["Title"] = "Login";
}
<h1>Login</h1>
#Html.AntiForgeryToken()
<div class="row">
<div class="col-md-4">
<form asp-action="Login">
<h4 class="text-danger">#ViewBag.Message</h4>
<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>
</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">
<input type="submit" value="Create" class="btn btn-success" />
</div>
</form>
</div>
</div>
Register.cshtml:
#model commentbox.ViewModels.RegisterVM
#{
ViewData["Title"] = "Register";
}
<h1 class="text-success">Register User</h1>
<div class="row">
<div class="col-md-4">
<form asp-action="Register">
<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>
<p class="text-danger">#ViewBag.UserNameMessage </p>
</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">
<label asp-for="ConfirmPassword" class="control-label"></label>
<input asp-for="ConfirmPassword" class="form-control" />
<span asp-validation-for="ConfirmPassword" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Email" class="control-label"></label>
<input asp-for="Email" class="form-control" />
<span asp-validation-for="Email" class="text-danger"></span>
<p class="text-danger">#ViewBag.EmailMessage </p>
</div>
<div class="form-group">
<input type="submit" value="Register" class="btn btn-primary" style="border-radius:20px"/>
</div>
</form>
</div>
</div>
Index.cshtml:
#model IEnumerable<commentbox.Models.Comment>
#{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<h2 class="text-success text-center"> someone's post or some other things</h2>
#using (Html.BeginForm("PostComment", "ChatRoom", FormMethod.Post))
{
<input type="text" name="CommentText" placeholder="Type new comment..."
style="width:700px; height:60px; font-size:20px; margin-top:10px" />
<br />
<input type="submit" value="Post Comment" class="btn btn-success "
style="margin-top: 10px;margin-bottom: 10px " />
}
<table class="table text-center">
<tbody>
#foreach (var comment in Model)
{
<tr style="border:1px solid black;">
<td>
<span style="margin-right:15px;font-size:16px;color:green">
#Html.DisplayFor(modelItem => comment.User.UserName)
</span>
<span style="font-size:20px">
#Html.DisplayFor(modelItem => comment.Text)
</span>
<span style="margin-left:10px">
#Html.DisplayFor(modelItem => comment.CreateOn)
</span>
#foreach (var reply in comment.Replies)
{
<br />
<span style="margin-right:15px;font-size:16px;color:blue">
#Html.DisplayFor(modelItem => reply.User.UserName)
</span>
<span style="font-size:19px">
#reply.Text
</span>
<span style="margin-left:10px">
#reply.CreateOn
</span>
}
<br />
#using (Html.BeginForm("PostReply", "ChatRoom", FormMethod.Post))
{<input type="text" name="Reply" placeholder="Type reply..."
style="width:100%; height:60px; font-size:20px; margin-top:10px" />
<br />
<input type="hidden" name="CID" value="#comment.Id" />
<input type="submit" value="Post Reply" class="btn btn-success" style="margin-top :10px" />
}
</td>
</tr>
}
</tbody>
</table>
10.Add link in _Layout.cshtml.
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Account" asp-action="Register">Register</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Account" asp-action="Login">Login</a>
</li>
I am struggling to find a solution on how to add user input like first name, last name, zipcode.... to the database. The email is already stored in database during signUp.
I tried looking at some resource but they seem complicated.
HomeController
public async Task<IActionResult> ProfileView(ApplicationUser users)
{
return View();
}
Application user model:
public class ApplicationUser: IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public int ZipCode { get; set; }
}
Profile view:
<form>
<div class="form-row">
<div class="form-row col-md-6">
<div class="col">
<label for="firstName">First Name:</label>
<input type="text" class="form-control" placeholder="First name">
</div>
<div class="col col-md-6">
<label for="lastName">Last Name:</label>
<input type="text" class="form-control" placeholder="Last name">
</div>
</div>
<div class="form-group col-md-6">
<label for="inputEmail4">Email</label>
<input type="email" class="form-control" id="inputEmail4" placeholder="Email">
</div>
</div>
Database context:
public class DataBaseContext: IdentityDbContext<ApplicationUser>
{
public DataBaseContext(DbContextOptions<DataBaseContext> options): base(options)
{
}
public new DbSet<SignUp> Users { get; set; }
public DbSet<Products> Products { get; set; }
}
When the user fills out the information in the view it should store it in an existing database.
Here is a working example on modifying user's profile after logining :
Profile View :
#model MVC2_2Project.Models.ApplicationUser
<div class="row">
<div class="col-md-6">
<form id="profile-form" method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="FirstName"></label>
<input asp-for="FirstName" class="form-control" placeholder="First name"/>
<span asp-validation-for="LastName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="LastName"></label>
<input asp-for="LastName" class="form-control" placeholder="Last name"/>
<span asp-validation-for="LastName" class="text-danger"></span>
</div>
<button id="update-profile-button" type="submit" class="btn btn-primary">Save</button>
</form>
</div>
</div>
#section Scripts {
<partial name="_ValidationScriptsPartial" />
}
HomeController:
public class HomeController : Controller
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly ApplicationDbContext _context;
public HomeController(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
ApplicationDbContext context)
{
_userManager = userManager;
_signInManager = signInManager;
_context = context;
}
public async Task<IActionResult> ProfileView()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
return View(user);
}
[HttpPost]
public async Task<IActionResult> ProfileView(ApplicationUser users)
{
if (!ModelState.IsValid)
{
return View();
}
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
user.FirstName = users.FirstName;
user.LastName = users.LastName;
_context.Users.Update(user);
await _context.SaveChangesAsync();
await _signInManager.RefreshSignInAsync(user);
return RedirectToAction();
}
}
This question already has answers here:
The model item passed into the dictionary is of type .. but this dictionary requires a model item of type
(7 answers)
Closed 4 years ago.
I am having a trouble fixing this error, I am trying to edit an information of a specific user but when I click the "Edit" button I am getting this error in my web browser saying:
InvalidOperationException: The model item passed into the ViewDataDictionary is of type 'SanMarinoClassicWebsite.Auth.ApplicationUser', but this ViewDataDictionary instance requires a model item of type 'SanMarinoClassicWebsite.ViewModels.EditUserViewModel'.
what could I've done wrong?
Here is the action of my Admin Controller for editing the information
[Authorize(Roles ="Administrator")]
public class AdminController : Controller
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly RoleManager<IdentityRole> _roleManager;
public AdminController(UserManager<ApplicationUser> userManager,
RoleManager<IdentityRole> roleManager)
{
_userManager = userManager;
_roleManager = roleManager;
}
public IActionResult Index()
{
return View();
}
public IActionResult UserManagement()
{
var users = _userManager.Users;
return View(users);
}
public IActionResult AddUser()
{
return View();
}
[HttpPost]
public async Task<IActionResult> AddUser(AddUserViewModel
addUserViewModel)
{
if (!ModelState.IsValid) return View(addUserViewModel);
var user = new ApplicationUser()
{
UserName = addUserViewModel.UserName,
Email = addUserViewModel.Email,
City = addUserViewModel.City,
Birthdate = addUserViewModel.Birthdate,
Country = addUserViewModel.Country
};
IdentityResult result = await _userManager.CreateAsync(user,
addUserViewModel.Password);
if (result.Succeeded)
{
return RedirectToAction("UserManagement", _userManager.Users);
}
foreach (IdentityError error in result.Errors)
{
ModelState.AddModelError("", error.Description);
}
return View(addUserViewModel);
}
public async Task<IActionResult> EditUser(string id)
{
var user = await _userManager.FindByIdAsync(id);
if (user == null)
return RedirectToAction("UserManagement", _userManager.Users);
return View(user);
}
[HttpPost]
public async Task<IActionResult> EditUser(EditUserViewModel editUserViewModel)
{
var user = await _userManager.FindByIdAsync(editUserViewModel.Id);
if (user != null)
{
user.Email = editUserViewModel.Email;
user.UserName = editUserViewModel.UserName;
user.Birthdate = editUserViewModel.Birthdate;
user.City = editUserViewModel.City;
user.Country = editUserViewModel.Country;
var result = await _userManager.UpdateAsync(user);
if (result.Succeeded)
return RedirectToAction("UserManagement",
_userManager.Users);
ModelState.AddModelError("", "User not updated, something went
wrong.");
return View(editUserViewModel);
}
return RedirectToAction("UserManagement", _userManager.Users);
}
[HttpPost]
public async Task<IActionResult> DeleteUser(string userId)
{
ApplicationUser user = await _userManager.FindByIdAsync(userId);
if (user != null)
{
IdentityResult result = await _userManager.DeleteAsync(user);
if (result.Succeeded)
return RedirectToAction("UserManagement");
else
ModelState.AddModelError("", "Something went wrong while deleting this user.");
}
else
{
ModelState.AddModelError("", "This user can't be found");
}
return View("UserManagement", _userManager.Users);
}
EditUserViewModel.cs
public class EditUserViewModel
{
public string Id { get; set; }
[Required(ErrorMessage = "Please enter the user name")]
[Display(Name = "User name")]
public string UserName { get; set; }
[Required(ErrorMessage = "Please enter the user email")]
public string Email { get; set; }
public List<string> UserClaims { get; set; }
[Required(ErrorMessage = "Please enter the birth date")]
[Display(Name = "Birth date")]
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}",
ApplyFormatInEditMode = true)]
public DateTime Birthdate { get; set; }
public string City { get; set; }
public string Country { get; set; }
}
EditUser.cshtml
#model EditUserViewModel
<h2>Edit user</h2>
<form asp-controller="Admin" asp-action="EditUser" method="post"
class="form-horizontal" role="form">
<h4>You can change the user details below</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Id" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Id" class="form-control" />
<span asp-validation-for="Id" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="UserName" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="UserName" class="form-control" />
<span asp-validation-for="UserName" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="Email" class="col-md-2 control-label"></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">
<label asp-for="Birthdate" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Birthdate" class="form-control" />
<span asp-validation-for="Birthdate" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="City" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="City" class="form-control" />
<span asp-validation-for="City" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="Country" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Country" class="form-control" />
<span asp-validation-for="Country" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" class="btn btn-primary" value="Save user" />
<a asp-action="Index" class="btn btn-primary">Cancel</a>
</div>
</div>
</form>
You have a problem with you generic type.
Look at you controller.
public class AdminController : Controller
{
// Here is the problem
private readonly UserManager<ApplicationUser> _userManager;
private readonly RoleManager<IdentityRole> _roleManager;
public AdminController(UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager)
{
_userManager = userManager;
_roleManager = roleManager;
}
// ...
You're using ApplicationUser to create an instance of UserManager. However, are using EditUserViewModel in your view.
You should change the type of your instance _userManager or use another type in your view.
Examples:
public class AdminController : Controller
{
// Here is the problem
private readonly UserManager<EditUserViewModel> _userManager;
private readonly RoleManager<IdentityRole> _roleManager;
public AdminController(UserManager<EditUserViewModel> userManager, RoleManager<IdentityRole> roleManager)
{
_userManager = userManager;
_roleManager = roleManager;
}
// ...
Or
#model EditUserViewModel
<h2>Edit user</h2>
I hope I helped you.