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.
CodePudding user response:
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 >
<div >
<div asp-validation-summary="ModelOnly" ></div>
<div >
<label asp-for="FirstName"></label>
<input asp-for="FirstName" />
<span asp-validation-for="FirstName" ></span>
</div>
<div >
<label asp-for="LastName"></label>
<input asp-for="LastName" />
<span asp-validation-for="LastName" ></span>
</div>
<div >
<label asp-for="Username"></label>
<input asp-for="Username" />
<span asp-validation-for="Username" ></span>
</div>
<div >
<label asp-for="PhoneNumber"></label>
<input asp-for="PhoneNumber" />
<span asp-validation-for="PhoneNumber" ></span>
</div>
<button id="update-profile-button" type="submit" >Save</button>
</div>
<div >
<div asp-validation-summary="ModelOnly" ></div>
<div >
<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"
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" ></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 >
<table>
<tr>
<td>
<a asp-action="Index" >Create New</a>
</td>
<form method="get" action="#">
<td style="padding-right:760px">
</td>
<td>
<input type="text" placeholder="Search for..." name="SearchString" value="@ViewData["CurrentFilter"]" aria-label="Search" aria-describedby="btnNavbarSearch" />
</td>
<td>
<input type="submit" value="Search" />
</td>
</form>
</tr>
</table>
</div>
<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"
height="50" width="75"
style="border:1px"
asp-append-version="true" accept="image/*" />
</td>
<td>
<a asp-action="EditMember" asp-route-memberId="@item.Id">Details</a> | <a asp-action="EditMember" asp-route-memberId="@item.Id">Edit</a>
</td>
</tr>
}
</tbody>
</table>
Database:
Note: If you need any further assistance on this please check this GitHub repository
.
CodePudding user response:
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 >
<div >
<form id="registerForm" asp-route-returnUrl="@Model.ReturnUrl" method="post" enctype="multipart/form-data">
<h2>Create a new account.</h2>
<hr />
<div asp-validation-summary="ModelOnly" ></div>
<div >
<input asp-for="Input.Email" aria-required="true" />
<label asp-for="Input.Email"></label>
<span asp-validation-for="Input.Email" ></span>
</div>
<div >
<input asp-for="Input.Name" aria-required="true" />
<label asp-for="Input.Name"></label>
<span asp-validation-for="Input.Name" ></span>
</div>
<div >
<input asp-for="Input.StreetAddress" />
<label asp-for="Input.StreetAddress"></label>
<span asp-validation-for="Input.StreetAddress" ></span>
</div>
<div >
<input asp-for="Input.City" />
<label asp-for="Input.City"></label>
<span asp-validation-for="Input.City" ></span>
</div>
<div >
<input asp-for="Input.PostalCode" />
<label asp-for="Input.PostalCode"></label>
<span asp-validation-for="Input.PostalCode" ></span>
</div>
<div >
<input asp-for="Input.Password" aria-required="true" />
<label asp-for="Input.Password"></label>
<span asp-validation-for="Input.Password" ></span>
</div>
<div >
<input asp-for="Input.ConfirmPassword" aria-required="true" />
<label asp-for="Input.ConfirmPassword"></label>
<span asp-validation-for="Input.ConfirmPassword" ></span>
</div>
<div >
<select asp-for="Input.Role" asp-items="@Model.Input.RoleList" class=form-select>
<option disabled selected>-Select Role-</option>
</select>
</div>
<div >
<select asp-for="Input.CompanyId" asp-items="@Model.Input.CompanyList" class=form-select>
<option disabled selected>-Select Company-</option>
</select>
</div>
<div >
<label asp-for="Input.ImageUrl"></label>
<input asp-for="Input.ImageUrl" type="file" id="uploadBox" name="file" />
</div>
<button id="registerSubmit" type="submit" >Register</button>
</form>
</div>
<div >
<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" >
<div>
<p>
@foreach (var provider in Model.ExternalLogins)
{
<button type="submit" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button>
}
</p>
</div>
</form>
}
}
</section>
</div>