Identity Core 2 issue with registration - c#

I am trying to use a model to create a user using asp.net core 2 sql server and identity core. But I am having an issue registering the user the following is my code and the errors it generates. I hope someone can help me.
This is not just a null issue its an issue with the posting of the form not getting the correct model so the persons interpetation is incorrect.
Model:
public class Register
{
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} 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; }
}
To Which I have a register action marked with the post command
[HttpGet]
[AllowAnonymous]
public IActionResult Register(string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
return View();
}
//
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Register(AppUser model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
var user = new AppUser { UserName = model.Email, Email = model.Email };
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
// For more information on how to enable account confirmation and password reset please visit http://go.microsoft.com/fwlink/?LinkID=532713
// Send an email with this link
//var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
//var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme);
//await _emailSender.SendEmailAsync(model.Email, "Confirm your account",
// "Please confirm your account by clicking this link: link");
await _signInManager.SignInAsync(user, isPersistent: false);
return RedirectToLocal(returnUrl);
}
// If we got this far, something failed, redisplay form
return View(model);
}
This is my html for the form
#model solitude.models.Register
#{
ViewData["Title"] = "Register";
Layout = "~/Views/Shared/_LoginAdminLte.cshtml";
}
<body class="hold-transition register-page">
<div class="register-box">
<div class="register-logo">
<b>Register</b>
</div>
<div class="register-box-body">
<p class="login-box-msg">Register a new membership</p>
<form asp-controller="Account" asp-action="Register" asp-route-returnurl="#ViewData["ReturnUrl"]" method="post" class="form-horizontal" role="form">
<div class="form-group has-feedback">
<input type="text" class="form-control" placeholder="Full name">
<span class="glyphicon glyphicon-user form-control-feedback"></span>
</div>
<div class="form-group has-feedback">
<input asp-for="Email" class="form-control" placeholder="Email">
<span class="glyphicon glyphicon-envelope form-control-feedback"></span>
</div>
<div class="form-group has-feedback">
<input asp-for="Password" class="form-control" />
<span class="glyphicon glyphicon-lock form-control-feedback"></span>
</div>
<div class="form-group has-feedback">
<input asp-for="ConfirmPassword" class="form-control" />
<span class="glyphicon glyphicon-log-in form-control-feedback"></span>
</div>
<div class="row">
<div class="col-xs-8">
<div class="checkbox icheck">
<label>
<input type="checkbox"> I agree to the terms
</label>
</div>
</div>
<!-- /.col -->
<div class="col-xs-4">
<button type="submit" class="btn btn-primary btn-block btn-flat">Register</button>
</div>
<!-- /.col -->
</div>
</form>
But I am having the following error problem
System.NullReferenceException: Object reference not set to an instance
of an object. at
solitude.admin.core.Controllers.AccountController.d__6.MoveNext()
in
C:\Projects\solitudeec2core\solitude.admin.core\solitude.admin.core\Controllers\AccountController.cs:line
88
--- End of stack trace from previous location where exception was thrown --- at
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task
task) at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.d__12.MoveNext()
--- End of stack trace from previous location where exception was thrown --- at
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at
When I look at line 88 it shows this but I do not see why I am having this problem
var result = await _userManager.CreateAsync(user, model.Password);
My Startup.cs
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
var connection = #"Server=----SEVER NAME HIDDEN---;Database=solitude;Trusted_Connection=True;";
services.AddDbContext<SolitudeDBContext>(options => options.UseSqlServer(connection));
services.AddIdentity<AppUser, IdentityRole>()
.AddEntityFrameworkStores<SolitudeDBContext>()
.AddDefaultTokenProviders();
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
Does any body have an idea what could be wrong.

The error is on this line:
public async Task<IActionResult> Register(AppUser model, string returnUrl = null)
You should provide Register type as an model input parameter, and not AppUser:
public async Task<IActionResult> Register(Register model, string returnUrl = null)

Related

ASP.NET Core 6 : permit Azure AD authentication and local authentication

I am working on a program which will allow a user to authenticate in two ways. They can create and use a local (homebrew) account with any email; or they can use Azure AD OAuth for our organization only. No matter what method of authentication is used the user should be treated the same and return true on context.User.Identity.IsAuthenticated.
I have been running into issues where only the Azure AD method worked, to fix this I used authentication policies inspired by this article. However, after following it both authentication methods seem to not be working :(
Here is the services code in startup.cs:
services.AddJwtAuthorization();
services.AddAuthentication(o =>
{
o.DefaultScheme = "MultiAuthSchemes";
o.DefaultChallengeScheme = "MultiAuthSchemes";
})
.AddCookie(o =>
{
o.LoginPath = "/login";
})
.AddJwtBearer("HomebrewScheme", _ => { })
.AddPolicyScheme("MultiAuthSchemes", JwtBearerDefaults.AuthenticationScheme, options =>
{
options.ForwardDefaultSelector = context =>
{
string authorization = context.Request.Headers[HeaderNames.Authorization];
if (!string.IsNullOrEmpty(authorization) && authorization.Contains("Bearer "))
{
var token = authorization["Bearer ".Length..].Trim();
var jwtHandler = new JwtSecurityTokenHandler();
return jwtHandler.CanReadToken(token)
? "HomebrewScheme" : "AdScheme";
}
return CookieAuthenticationDefaults.AuthenticationScheme;
};
})
.AddMicrosoftIdentityWebApi(Config, "AzureAd", "AdScheme");
services.AddAuthorization(o =>
{
var defaultAuthorizationPolicyBuilder = new AuthorizationPolicyBuilder(
JwtBearerDefaults.AuthenticationScheme,
CookieAuthenticationDefaults.AuthenticationScheme,
"HomebrewScheme", "AdScheme");
defaultAuthorizationPolicyBuilder =
defaultAuthorizationPolicyBuilder.RequireAuthenticatedUser();
o.DefaultPolicy = defaultAuthorizationPolicyBuilder.Build();
var onlySecondJwtSchemePolicyBuilder = new AuthorizationPolicyBuilder("HomebrewScheme");
o.AddPolicy("OnlyHomebrewScheme", onlySecondJwtSchemePolicyBuilder
.RequireAuthenticatedUser()
.AddAuthenticationSchemes()
.Build());
var onlyCookieSchemePolicyBuilder = new AuthorizationPolicyBuilder("AdScheme");
o.AddPolicy("OnlyAdScheme", onlyCookieSchemePolicyBuilder
.RequireAuthenticatedUser()
.Build());
});
Here is the app code in startup.cs:
app.UseAuthentication()
app.UseGraphQLPlayground(new PlaygroundOptions
{
GraphQLEndPoint = GraphQLApiEndpoint
});
app.UseWebSockets();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(x => x.MapGraphQL(path: GraphQLApiEndpoint));
Here is code in startup.cs which I am using to test authentication:
app.Use((context, next) =>
{
//Grab the first identity because the authentication type does not matter
if (context.User.Identity?.IsAuthenticated == true)
{
PermissionLevel = Permissions.Authorized;
}
});
From what I can tell from debugging, the system never detects that I obtain authentication now. I know the front end works because if I use only services.AddMicrosoftIdentityWebApiAuthentication(Config, "AzureAd", "AdScheme"); no matter if it is the default scheme or not, everything works.
Thank you for your help, stuck on this one 😊
Firstly, I followed this blog to add Cookie authentication in my code. It provides a login page to let us sign in. And in the AccountController, it provides several mock user account so that we can use them to sign in for test.
Then I added code in the login page so that it provides the option to sign in with AAD.
Changing the Program.cs file to add multiple authentication scheme.
Here's my code, Program.cs
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.UI;
var builder = WebApplication.CreateBuilder(args);
//set CookieAuthenticationDefaults.AuthenticationScheme as the default authentication scheme
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(x => x.LoginPath = "/account/login");
builder.Services.AddAuthentication()
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"), OpenIdConnectDefaults.AuthenticationScheme, "ADCookies");
// Add microsoft sign in page
builder.Services.AddControllersWithViews().AddMicrosoftIdentityUI();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
My HomeController
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;
using WebAppMvcCookieAuthAad.Models;
namespace WebAppMvcCookieAuthAad.Controllers
{
[AllowAnonymous]
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
[Authorize]
public async Task<IActionResult> ConfidentialDataAsync()
{
return View();
}
}
}
My AccountController:
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
using WebAppMvcCookieAuthAad.Models;
namespace WebAppMvcCookieAuthAad.Controllers
{
public class AccountController : Controller
{
public List<UserModel> users = null;
public AccountController()
{
users = new List<UserModel>();
users.Add(new UserModel()
{
UserId = 1,
Username = "Tiny",
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)
{
ViewBag.Message = "Invalid Credential";
return View(objLoginModel);
}
else
{
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")
};
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
var principal = new ClaimsPrincipal(identity);
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal, new AuthenticationProperties()
{
IsPersistent = objLoginModel.RememberLogin
});
return LocalRedirect(objLoginModel.ReturnUrl);
}
}
return View(objLoginModel);
}
public async Task<IActionResult> LogOut()
{
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
return LocalRedirect("/");
}
}
}
My LoginModel and UserModel
using System.ComponentModel.DataAnnotations;
namespace WebAppMvcCookieAuthAad.Models
{
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;}
}
}
namespace WebAppMvcCookieAuthAad.Models
{
public class UserModel
{
public int UserId { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string Role { get; set; }
}
}
View -> Account -> Login.cshtml:
#model WebAppMvcCookieAuthAad.Models.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" />
</div>
</form>
</div>
</div>
<div>
<label>sign in with aad</label>
<a asp-area="MicrosoftIdentity" asp-controller="Account" asp-action="SignIn">Sign in with aad</a>
</div>
View -> Home -> ConfidentialData.cshtml
#if(User.Identity.IsAuthenticated){
<table>
#foreach (var item in User.Claims)
{
<tr><td>#item.Type</td><td>#item.Value</td></tr>
}
</table>
}
View -> Shared -> _LoginPartial.cshtml, pls don't forget add this partial view to layout.
#using System.Security.Principal
<ul class="navbar-nav">
#if (User.Identity.IsAuthenticated)
{
<li class="nav-item">
<span class="navbar-text text-dark">Hello #User.Identity.Name!</span>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-controller="Account" asp-action="LogOut">log out</a>
</li>
#*asp - area = "MicrosoftIdentity"*#
}
else
{
<li class="nav-item">
<a class="nav-link text-dark" asp-controller="Account" asp-action="Login">log in</a>
</li>
}
</ul>
appsetting.json:
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "tenant_name",
"TenantId": "tenant_id",
"ClientId": "azure_ad_app_id",
"ClientSecret": "azure_ad_client_secret",
"CallbackPath": "/home", //don't forget to set redirect url in azure portal
"SignedOutCallbackPath ": "/signout-callback-oidc"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
This code worked well in my side and I can sign in with cookie auth and aad. I noticed that after signing with aad, #User.Identity.Name won't show user name. But actually the sign in flow succeed.

How do I upload file to file system and save path to database AFTER user clicks save button in Edit view?

I want to upload my files to the filesystem after I have pressed the "save" button in the Edit view. To do that I am trying to call the UploadToFileSystem method inside the Edit (POST) action.
I learned how to do that here but this tutorial shows you how to do it in the Index action grid view. I have been trying to reverse engineer that logic so as to do it in the edit action.
This is what it looked like before I call UploadToFileSystem in a button that uses only that method and it worked with saving the path to the file system but not the database.
I would give you a Github link to the app but it has lots of dependencies that you would need to install and gain access to.
BEFORE:
Debugging in VS Code showing UploadToFileSystem being called by itself.
AFTER: (Where it didn't work.)
Debugging in VS Code showing UploadToFileSystem being called by itself.
So after that error in the second image showing there were no files being retrieved I have tried to use a view model called ProblemInputModel but I get a null reference exception once the file gets passed to the view.
ProblemsController.cs
// POST: Problems/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("ID,ProblemTitle,ProblemDescription,ProblemStartDate,ProblemFileAttachments,ProblemSeverity,ProblemComplete")] Problem problem, List<IFormFile> iFormFile)
{
//Used for file attachment upload.
if (id != problem.ID)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(problem);
await _context.SaveChangesAsync();
//Upload or update any attachments user inserted.
await UploadToFileSystem(iFormFile ,problem.ID, problem.ProblemTitle, problem.ProblemDescription,
problem.ProblemStartDate, problem.ProblemSeverity);
}
catch (DbUpdateConcurrencyException)
{
if (!ProblemExists(problem.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(problem);
}
[HttpPost]
public async Task<IActionResult> UploadToFileSystem(List<IFormFile> files, int? id, string title,
string description, DateTime dateTime, int severity)
{
foreach (var file in files)
{
//Get the base Path, i.e, The Current Directory of the application + /Files/.
var basePath = Path.Combine(Directory.GetCurrentDirectory() + "\\Files\\");
//Checks if the base path directory exists, else creates it.
bool basePathExists = System.IO.Directory.Exists(basePath);
if (!basePathExists) Directory.CreateDirectory(basePath);
//Gets the file name without the extension.
var fileName = Path.GetFileNameWithoutExtension(file.FileName);
//Combines the base path with the file name.
var filePath = Path.Combine(basePath, file.FileName);
//If the file doesnt exist in the generated path, we use a filestream object, and create a new file, and then copy the contents to it.
var extension = Path.GetExtension(file.FileName);
if (!System.IO.File.Exists(filePath))
{
using (var stream = new FileStream(filePath, FileMode.Create))
{
await file.CopyToAsync(stream);
}
//Create a new Problem object with required values.
var problem = await _context.Problems.FindAsync(id);
problem = new Problem
{
ProblemFileAttachments = filePath,
ProblemTitle = title,
ProblemDescription = description,
ProblemStartDate = dateTime,
ProblemSeverity = severity
};
//Inserts this model to the db via the context instance of EF Core.
_context.Problems.Add(problem);
_context.SaveChanges();
}
}
//Loads all the File data to an object and sets a message in the TempData.
TempData["Message"] = "File successfully uploaded to File System.";
return RedirectToAction("Index");
}
Edit.cshtml
#model Pitcher.Models.Problem
#{
ViewData["Title"] = "Edit";
}
<h1>Edit</h1>
<h4>Problem</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Edit" enctype="multipart/form-data" method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="ID" class="control-label" ></label>
<input asp-for="ID" readonly="true" disabled="true" class="form-control"/>
</div>
<div class="form-group">
<label asp-for="ProblemTitle" class="control-label"></label>
<input asp-for="ProblemTitle" class="form-control" />
<span asp-validation-for="ProblemTitle" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ProblemDescription" class="control-label"></label>
<textarea asp-for="ProblemDescription" class="form-control" rows="10" cols="50"></textarea>
<span asp-validation-for="ProblemDescription" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ProblemStartDate" class="control-label"></label>
<input asp-for="ProblemStartDate" class="form-control" />
<span asp-validation-for="ProblemStartDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ProblemFileAttachments" class="control-label"></label>
<input asp-for="ProblemFileAttachments" type="file" name="files"/>
#* <button type="submit" class="btn btn-primary" asp-controller="Problems" asp-action="UploadToFileSystem">Upload to File System</button> *#
</div>
<div class="form-group">
<label asp-for="ProblemSeverity" class="control-label"></label>
<select asp-for="ProblemSeverity" class="form-control">
<option value="">Choose severity level</option>
<option value="1">Very Low</option>
<option value="2">Low</option>
<option value="3">Medium</option>
<option value="4">High</option>
<option value="5">Very High</option>
</select>
<span asp-validation-for="ProblemSeverity" class="text-danger"></span>
</div>
<div class="form-group form-check">
<label class="form-check-label">
<input class="form-check-input" asp-for="ProblemComplete" /> #Html.DisplayNameFor(model => model.ProblemComplete)
</label>
</div>
<div class="form-group" method="post">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Problem.cs
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations;
namespace Pitcher.Models
{
public class Problem
{
public int ID {get;set;}
[Required]
[StringLength(180, MinimumLength = 2, ErrorMessage = "Problem Title must be bettween 2 to 20 characters.")]
[DataType(DataType.Text)]
[Display(Name = "Problem Title")]
[Column("ProblemTitle")]
public string ProblemTitle {get;set;}
[Required]
[StringLength(int.MaxValue, MinimumLength = 5, ErrorMessage = "Problem Title must be at least 5 characters.")]
[DataType(DataType.Text)]
[Display(Name = "Problem Description")]
[Column("ProblemDescription")]
public string ProblemDescription {get;set;}
[Required]
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = " Problem Start Date")]
[Column("ProblemStartDate")]
public DateTime ProblemStartDate {get;set;}
[DataType(DataType.Upload)]
[Display(Name = " Upload file")]
[Column("ProblemFileAttachments")]
public string ProblemFileAttachments {get;set;}
[Required]
[Display(Name = "Problem Severity")]
[Range(1,5, ErrorMessage
= "Problem Severity value for {0} must be between {1} and {2}.")]
[Column("ProblemSeverity")]
public int ProblemSeverity {get;set;}
[Display(Name = "Problem Complete")]
[Column("ProblemComplete")]
public bool ProblemComplete {get;set;}
public ICollection<Result> Result {get;set;}
public ICollection<Chat> Chat {get;set;}
//public List<Problem> ProblemFileModel {get; set;}
}
}
ProblemInputModel
//This is a view model only.
public class ProblemInputModel
{
[DataType(DataType.Upload)]
[Display(Name = " Upload file")]
[Column("ProblemFileAttachments")]
public List<IFormFile> ProblemFileAttachments {get;set;}
}
For the two situations, both of them get null value are caused by model binding failure.
You need know two things below:
ASP.NET Core Tag Helper asp-for will generate the id and name attribute.
Model Binding system looks through the sources for the name pattern prefix.property_name. If nothing is found, it looks for just property_name without the prefix.
Model binding failure here is because the name attribute does not match the parameter name.
For the first situation to directly call UploadToFileSystem method, you get the null parameters:
[HttpPost]
public IActionResult UploadToFileSystem(List<IFormFile> files, int? ID, string ProblemTitle,
string ProblemDescription, DateTime ProblemStartDate, int ProblemSeverity)
{
//.......
return RedirectToAction("Index");
}
For the second situation to call the UploadToFileSystem method inside the Edit action:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Edit(int id, [Bind("ID,ProblemTitle,ProblemDescription,ProblemStartDate,ProblemFileAttachments,ProblemSeverity,ProblemComplete")] Problem problem,
List<IFormFile> files) //change here...
{
//.......
return View(problem);
}
If you want to use ProblemInputModel model, you need firstly change the view code to remove the name="files", then asp-for will generate the name="ProblemFileAttachments" which matches the ProblemFileAttachments property in ProblemInputModel model:
<input asp-for="ProblemFileAttachments" type="file" #*name="files"*#/>
Then remember to add the ProblemInputModel model as a parameter:
public IActionResult Edit(int id, [Bind("ID,ProblemTitle,ProblemDescription,ProblemStartDate,ProblemFileAttachments,ProblemSeverity,ProblemComplete")] Problem problem,
ProblemInputModel model)

How to show a temporally Success Message without using query parameters?

I want to show a success message in a view SingIn.cshtml only one time after user registration. In order to do it, i was thinking into sending a boolean parameter to the SingIn Action method so then the View SingIn.cshtml will get it, and then choose if it should show it or not, but since the parameter will appear in the query string (app.com/Account/SignIn?parameter=true), the user can refresh the page and see it again and again or maybe he can type the url and saw it again.So How can i complete my approach without showing the parameter on a query string (app.com/Account/SignIn) ?
This is my Controller:
public class AccountController : Controller
{
private readonly SignInManager<Client> _signInManager;
private readonly UserManager<Client> _userManager;
public AccountController(UserManager<Client> userManager, SignInManager<Client> signInManager)
{
_userManager = userManager;
_signInManager = signInManager;
}
[HttpGet]
public IActionResult SignUp()
{
return View(new SignUpViewModel());
}
[HttpPost]
public async Task<IActionResult> SignUp(SignUpViewModel viewModel)
{
if (ModelState.IsValid)
{
var client = new Client
{
UserName = viewModel.Id, FullName = viewModel.FullName, BirthDate = viewModel.BirthDate.Value,
Email = viewModel.Email
};
var result = await _userManager.CreateAsync(client, viewModel.Password);
if (result.Succeeded)
return RedirectToAction("SignIn", new {DidHeJustSignUp = true});
}
return View(viewModel);
}
[HttpGet]
public IActionResult SignIn(bool didHeJustSignUp)
{
var model = new SignInViewModel {DidHeJustSignUp = didHeJustSignUp};
return View(model);
}
}
This is my view:
#model SignInViewModel
#{
ViewBag.Title = "Sign In";
}
<form asp-controller="Account" asp-action="SignIn" method="post">
#{
if (Model.DidHeJustSignUp)
{
<div class="alert alert-success text-center">
You have been registred Successfully, please Sign In
</div>
}
}
<div class="form-group">
<label asp-for="Id"></label>
<input type="text" class="form-control" asp-for="Id">
<span class="text-danger" asp-validation-for="Id"></span>
</div>
<div class="form-group">
<label asp-for="Password"></label>
<input type="password" class="form-control" asp-for="Password"/>
<span class="text-danger" asp-validation-for="Password"></span>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" asp-for="ShouldIRememberYou">
<label class="form-check-label" asp-for="ShouldIRememberYou">Remember me</label>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
You can use TempData here, anything you put into TempData is discarded after the next request completes.
if (result.Succeeded)
{
TempData["DidHeJustSignUp"] = true;
return RedirectToAction("SignIn");
}
if (TempData["DidHeJustSignUp"] != null)
{
<div class="alert alert-success text-center">
You have been registred Successfully, please Sign In
</div>
}

ViewBag is not sending any value to the view

In ASP.NET Core MVC, I'm facing trouble creating a login panel, I'm using sessions after the user is logged into the account and I'm storing the session values inside the ViewBag. But the ViewBag does not get any value inside of it, it rather gets null value inside it.
Here's the controller
[HttpPost]
public IActionResult Login(userModel model)
{
var findValue = _context.users.Any(o => o.username == model.username);
var findValue2 = _context.users.Any(o => o.password == model.password);
if (findValue && findValue2)
{
HttpContext.Session.SetString("Username", model.username);
}
return View(model);
}
public IActionResult Index()
{
ViewBag.Username = HttpContext.Session.GetString("Username");
return View();
}
And here's the view
Index.cshtml
#model ComplaintManagement.Models.userModel
#{
ViewData["Title"] = "Portal";
}
<h1>Welcome #ViewBag.Username</h1>
Login.cshtml
#model ComplaintManagement.Models.userModel
#{
ViewData["Title"] = "Login";
}
<div class="row mb-3">
<div class="col-lg-4"></div>
<div class="col-lg-4 border login" style="background-color: #d3d1d1;">
<h4 class="mt-3 text-center">
<i class="fa fa-lg fa-user text-secondary"></i><br />
Login
</h4>
<hr />
<form method="post" asp-action="Index" asp-controller="Portal">
<div class="text-danger"></div>
<div class="text-warning">#ViewBag.Name</div>
<div class="form-group">
<label class="mt-4 asp-for=" username"">Username</label>
<input class="form-control" type="text" required="required" asp-for="username" />
<span></span>
</div>
<div class="form-group">
<label class="mt-4" asp-for="password">Password</label>
<input type="password" class="form-control" required="required" asp-for="password" />
<span></span>
</div>
<center>Don't have an account? <a asp-controller="Portal" asp-action="Register">Register here</a>.</center>
<center><button value="login" class="btn btn-primary mt-3 w-25 mb-3 align-content-center">Login</button></center>
</form>
</div>
<div class="col-lg-4"></div>
</div>
Session and state management in ASP.NET Core
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/app-state?view=aspnetcore-3.1
Here is a demo How to use Session in ASP.NET Core.
1. Codes of Startup Configurations
AddSession in ConfigureServices, UseSession in Configure.
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
...
services.AddSession();
...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseSession();
app.UseStaticFiles();
....
}
2. Codes of Controller
public class AccountController : Controller
{
public IActionResult Index()
{
return View();
}
[HttpGet]
public IActionResult Login()
{
return View();
}
[HttpPost]
public IActionResult Login(userModel model)
{
if (string.IsNullOrEmpty(model.username
) || string.IsNullOrEmpty(model.password))
{
return NotFound();
}
//var user = await _context.users.FirstOrDefaultAsync(x => x.username == model.username && x.password == model.password);
//if (user != null)
if (model.username.Equals("test") && model.password.Equals("123"))
{
HttpContext.Session.SetString("username", model.username);
}
else
ViewBag.error = "Invalid Account";
return View("Index");
}
[HttpGet]
public IActionResult Logout()
{
HttpContext.Session.Remove("username");
return RedirectToAction("Index");
}
}
3. Codes of View
<h3>Login Page</h3>
#ViewBag.error
<form method="post" asp-controller="account" asp-action="login">
<table border="0" cellpadding="2" cellspacing="2">
<tr>
<td>Username</td>
<td><input type="text" name="username"></td>
</tr>
<tr>
<td>Password</td>
<td><input type="password" name="password"></td>
</tr>
<tr>
<td> </td>
<td><input type="submit" value="Login"></td>
</tr>
</table>
</form>
4. Codes of returning View when success
#using Microsoft.AspNetCore.Http;
<h3>Success Page</h3>
Welcome #Context.Session.GetString("username")
<br>
<a asp-controller="account" asp-action="logout">Logout</a>
Test result

Why don't values persist on Postback?

My home page has a list of product options each giving the user a different account level on sign up. Each option presents a "Sign Up" button which takes the user to the /Identity/Account/Register page to sign up.
I need to communicate to the Register page which option the user selected.
I can't use Sessions because that's apparently been taken away
I can't use Cookies because that's apparently been taken away
ViewData values don't persist when I submit the form
Querystring values don't persist when I submit the form
Global variable values don't persist when I submit the form
I can't set properties of the viewmodel when the page is loaded initially (NullReferenceException)
When the Register page loads, the value is there, but when I submit the form, it disappears.
I'm at a loss. By what mechanism am I meant to get this required information across?
For the most part my code is pretty much just standard boilerplate stuff:
[AllowAnonymous]
public class RegisterModel : PageModel
{
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly UserManager<ApplicationUser> _userManager;
private readonly ILogger<RegisterModel> _logger;
private readonly IEmailSender _emailSender;
public RegisterModel(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
ILogger<RegisterModel> logger,
IEmailSender emailSender)
{
_userManager = userManager;
_signInManager = signInManager ?? throw new ArgumentNullException(nameof(signInManager));
_logger = logger;
_emailSender = emailSender;
}
[BindProperty]
public InputModel Input { get; set; }
[BindProperty]
public int AccountLevel { get; set; }
public string ReturnUrl { get; set; }
public void OnGet(string returnUrl = null, int acclevel = 1)
{
AccountLevel = acclevel;
ReturnUrl = returnUrl;
}
public async Task<IActionResult> OnPostAsync(string returnUrl = null, int acclevel = 0)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (ModelState.IsValid)
{
if (acclevel == 0) throw new ArgumentException(nameof(acclevel));
Input.LicenseCount = acclevel * 10;
var user = new ApplicationUser { UserName = Input.Email, Email = Input.Email, Name = Input.FirstName, Surname = Input.Surname, PhoneNumber = Input.PhoneNumber, SaIdNumber = Input.IdNumber, LicensesCount = Input.LicenseCount };
var result = await _userManager.CreateAsync(user, Input.Password);
await _userManager.AddToRoleAsync(user, nameof(SystemRoles.AppUser));
if (result.Succeeded)
{
_logger.LogInformation("User created a new account with password.");
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { userId = user.Id, code = code },
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>.");
//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 the Page (this is using Pages with the PageModel for some reason, rather than Views and Controllers - it scaffolded this way when I added Identity).
#page
#model RegisterModel
#{
ViewData["Title"] = "Register";
}
<h1>#ViewData["Title"]</h1>
<div class="row">
<div class="col-md-4">
<form asp-route-returnUrl="#Model.ReturnUrl" method="post">
<h4>Create a new account.</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<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 class="form-group">
<label asp-for="Input.Surname"></label>
<input asp-for="Input.Surname" class="form-control" />
<span asp-validation-for="Input.Surname" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.PhoneNumber"></label>
<input asp-for="Input.PhoneNumber" class="form-control" />
<span asp-validation-for="Input.PhoneNumber" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.IdNumber"></label>
<input asp-for="Input.IdNumber" class="form-control" />
<span asp-validation-for="Input.IdNumber" class="text-danger"></span>
</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="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 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>
<button type="submit" class="btn btn-primary">Register</button>
</form>
</div>
</div>
#section Scripts {
<partial name="_ValidationScriptsPartial" />
}
Note: I don't work with Razor Pages, so someone with knowledge in that technology might have a better answer.
You can use a hidden field in the form to keep the value on POST-back:
<form asp-route-returnUrl="#Model.ReturnUrl" method="post">
#Html.HiddenFor(model => model.AccountLevel)
#*OR, TagHelper way*#
<input asp-for="AccountLevel" type="hidden"/>
<h4>Create a new account.</h4>
....
</form>
This is also fairly easy to do with sessions after you enable them:
public void OnGet(string returnUrl = null, int acclevel = 1)
{
HttpContext.Session.SetInt32("AccountLevel", acclevel);
ReturnUrl = returnUrl;
}
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
int accountLevel = HttpContext.Session.GetInt32("AccountLevel");
...
}
The only data that exists after a request is what's sent along with that request. Even in the case of sessions, the crucial session identifier must be transmitted by the client to the server in the request in order to restore that session.
Any data you retrieved from the database must either be posted or queried out again. Additionally, no data should be posted that the user should not be explicitly allowed to change. As a result, most data should to be queried again.
As far as which option the user selected goes, that of course should be posted. However, the actual details of the selected plan and the other plan options should be obtained from querying that database again. That will be necessary, for example, if you need to redisplay the form following the post, due to a validation error.

Categories

Resources