I set up my own home lab to learn development in the hopes of proving my competence. I am using VSCode to write the code with help from AI. I use Github as my repository: https://github.com/JonathanHoward86/BackendDevProject I use Azure Devops for my CI/CD I use Azure Web Services for the Web App: learnhowtodev.azurewebsites.net and SQL database.
For some reason, in the last couple of days my database has stopped writing user registrations or logins. This post is specifically asking about the Register function to keep it simple.
The views all load, the url's redirect correcty, but the Login and Register buttons are not redirecting and the database is not storing any of the login info provided by the user when clicking Register and this was working just last week.
I am not receiving any error in the Web App Log Stream, no error in the Dev Console... Just a 200 Post and then the page refreshes which means it's failing but I have no idea why...
I've gone over my commits to see if there was something I changed that would cause this but I can't identify anything.
Any advice?
This is my Account Controller:
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using MyEcommerceBackend.Models;
using SendGrid;
using SendGrid.Helpers.Mail;
namespace MyEcommerceBackend.Controllers
{
[Route("[controller]")]
public class AccountController : Controller
{
private readonly UserManager<IdentityUser> _userManager;
private readonly SignInManager<IdentityUser> _signInManager;
private readonly IConfiguration _configuration;
public AccountController(UserManager<IdentityUser> userManager, SignInManager<IdentityUser> signInManager, IConfiguration configuration)
{
_userManager = userManager;
_signInManager = signInManager;
_configuration = configuration;
}
[HttpPost("Register")]
public async Task<IActionResult> Register(RegisterModel model)
{
if (ModelState.IsValid)
{
if (model.Email != null && model.Password != null)
{
var user = new IdentityUser { UserName = model.Email, Email = model.Email };
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, false);
return RedirectToAction("RegisterSuccess", "View");
}
foreach (var error in result.Errors)
{
ModelState.AddModelError("", error.Description);
}
}
else
{
ModelState.AddModelError("", "Email and Password must not be null");
}
}
return View("Register", model);
}
This is my Register Model:
using System.ComponentModel.DataAnnotations;
namespace MyEcommerceBackend.Models
{
public class RegisterModel // Model for user registration.
{
[Required]
[EmailAddress]
public string? Email { get; set; } // Required email property.
[Required]
[MinLength(6)] // Minimum length for a password, change as needed.
public string? Password { get; set; } // Required password property.
[Compare("Password")] // Ensures this matches the password field.
public string? ConfirmPassword { get; set; } // Required confirm password property.
}
}
This is my View Controller:
using Microsoft.AspNetCore.Mvc;
using MyEcommerceBackend.Models;
public class ViewController : Controller // Controller for handling user-facing views like Register, Login, etc.
{
[HttpGet]
public IActionResult Register()
{
return View(); // Returns the Register view.
}
[HttpPost]
public IActionResult Register(RegisterModel model)
{
return View(model); // Returns the Register view along with the model data.
}
public IActionResult RegisterSuccess()
{
return View(); // Returns the RegisterSuccess view.
}
This is my main program.cs:
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using MyEcommerceBackend.Models;
namespace MyEcommerceBackend
{
public class Authentication
{
// Main method - entry point of the application
public static void Main(string[] args)
{
// Creates and runs the web host
CreateHostBuilder(args).Build().Run();
}
// Defines the host builder
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
// Configures the web host to use this class (Authentication) as the startup class
webBuilder.UseStartup<Authentication>();
});
// Constructor that accepts configuration settings
public Authentication(IConfiguration configuration)
{
Configuration = configuration;
}
// Configuration property
public IConfiguration Configuration { get; }
// ConfigureServices method - used to register application services
public void ConfigureServices(IServiceCollection services)
{
// Read the environment variables from Azure
string server = Configuration["DBServer"] ?? throw new Exception("Server environment variable is not set.");
string database = Configuration["DB"] ?? throw new Exception("Database environment variable is not set.");
string username = Configuration["DBLogin"] ?? throw new Exception("Username environment variable is not set.");
string password = Configuration["DBPW"] ?? throw new Exception("Password environment variable is not set.");
// Construct the connection string
string connectionString = $"Server=tcp:{server},1433;Initial Catalog={database};Persist Security Info=False;User ID={username};Password={password};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;";
// Add the DbContext using the connection string
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
// Registers controller services
services.AddControllers();
// Add MVC support with Views and Controllers
services.AddControllersWithViews();
// Adds Identity services for authentication and authorization
services.AddIdentity<IdentityUser, IdentityRole>(options =>
{
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// Configures API explorer and Swagger for API documentation
services.AddEndpointsApiExplorer();
services.AddSwaggerGen();
}
// Configure method - used to configure the HTTP request pipeline
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// Enables Swagger in the development environment
if (env.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI(); // Enables Swagger UI for API documentation browsing
}
// Adds routing middleware
app.UseRouting();
// Adds authentication and authorization middleware
app.UseAuthentication();
app.UseAuthorization();
app.UseStaticFiles(); // Serve static files like CSS, JavaScript, etc.
// Enable endpoint routing and configure the default route
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=View}/{action=Login}/{id?}"); // Defines the default route
endpoints.MapControllers();
});
}
}
}
This is the Register View:
<!DOCTYPE html>
<html lang="en">
@{
ViewData["Title"] = "Please enter your Email and Password then click Register.";
}
<h2>@ViewData["Title"]</h2>
<form method="post" asp-controller="Account" asp-action="Register">
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text" id="email-label">Email:</span>
</div>
<input type="text" class="form-control" id="Email" name="Email" aria-describedby="email-label" required autocomplete="email">
</div>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text" id="password-label">Password:</span>
</div>
<input type="password" class="form-control" id="Password" name="Password" aria-describedby="password-label" required autocomplete="new-password">
</div>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text" id="confirm-password-label">Confirm Password:</span>
</div>
<input type="password" class="form-control" id="ConfirmPassword" name="ConfirmPassword" aria-describedby="confirm-password-label" required autocomplete="new-password">
</div>
<button type="submit" class="btn btn-primary mb-3">Register</button>
</form>
</html>
This is a screenshot of the dev console log when I tried to Register: Network Dev Tools, no errors or warnings or issues, 200POST
Reproduction Steps: Open my website in chrome browser. Click Register button. Fill in an email, password, and password confirmation. Click Register
Expected Result: Redirected to success page and information stored in the database.
Actual Result: Page refreshes, a 200 Post shown in the Dev Tool, no errors of any kind.
Turned out that logging was indeed the issue, not sure if it was what was suggest, but I did remove the logging I was using and switched to NLog and magically the redirects began to work. I had to setup a local environment and DB / SMTP to test it all, but it does now work correctly.
Hopefully this helps someone somewhere. :)
Updated Registration Controller:
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using LoginService.Models;
using NLog;
namespace LoginService.Controllers
{
public class RegistrationController : Controller
{
private readonly UserManager<IdentityUser> _userManager;
private readonly SignInManager<IdentityUser> _signInManager;
private readonly IConfiguration _configuration;
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
public RegistrationController(UserManager<IdentityUser> userManager, SignInManager<IdentityUser> signInManager, IConfiguration configuration, ILogger<RegistrationController> logger)
{
_userManager = userManager;
_signInManager = signInManager;
_configuration = configuration;
}
[HttpPost]
public async Task<IActionResult> Register(RegisterModel model)
{
if (ModelState.IsValid)
{
Logger.Info("Entered Register POST method");
if (model.Email != null && model.Password != null)
{
var user = new IdentityUser { UserName = model.Email, Email = model.Email };
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
Logger.Info("User registration succeeded");
await _signInManager.SignInAsync(user, false);
return RedirectToAction("RegisterSuccess");
}
foreach (var error in result.Errors)
{
Logger.Info("User registration failed");
ModelState.AddModelError("", error.Description);
}
}
else
{
Logger.Info("Model state is invalid");
ModelState.AddModelError("", "Email and Password must not be null");
}
}
Logger.Info("Exiting Login POST method");
return View("~/Views/Registration/Register.cshtml", model);
}
[HttpGet]
public IActionResult Register()
{
Logger.Info("Entered Register GET method");
var model = new RegisterModel();
return View("~/Views/Registration/Register.cshtml", model);
}
Updated Register Model:
using System.ComponentModel.DataAnnotations;
namespace LoginService.Models
{
public class RegisterModel // Model for user registration.
{
[Required]
[EmailAddress]
public string? Email { get; set; } // Required email property.
[Required]
[MinLength(6)] // Minimum length for a password, change as needed.
public string? Password { get; set; } // Required password property.
[Compare("Password")] // Ensures this matches the password field.
public string? ConfirmPassword { get; set; } // Required confirm password property.
}
}
Updated Register View:
@{
Layout = "_Layout";
ViewData["Title"] = "Please enter your Email and Password then click Register.";
}
<div asp-validation-summary="All" class="text-danger"></div>
<h2>@ViewData["Title"]</h2>
<form method="post" asp-controller="Registration" asp-action="Register">
<div class="input-group mb-3">
<label id="email-label" for="Email">Email:</label>
<input type="text" class="form-control" id="Email" name="Email" aria-labelledby="email-label" required autocomplete="email">
</div>
<div class="input-group mb-3">
<label id="password-label" for="Password">Password:</label>
<input type="password" class="form-control" id="Password" name="Password" aria-labelledby="password-label" required autocomplete="new-password">
</div>
<div class="input-group mb-3">
<label id="confirm-password-label" for="ConfirmPassword">Confirm Password:</label>
<input type="password" class="form-control" id="ConfirmPassword" name="ConfirmPassword" aria-labelledby="confirm-password-label" required autocomplete="new-password">
</div>
<button type="submit" class="btn btn-primary mb-3">Register</button>
</form>
Updated Program and Startup:
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using LoginService.Models;
namespace LoginService
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddControllersWithViews();
services.AddTransient<IEmailService, EmailService>();
services.AddIdentity<IdentityUser, IdentityRole>(options => { })
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Login}/{action=Login}/{id?}");
endpoints.MapControllers();
});
}
}
}
using NLog.Web;
namespace LoginService
{
public class Program
{
public static void Main(string[] args)
{
// Configure NLog using the new recommended approach
NLog.LogManager.Setup().LoadConfigurationFromAppSettings();
var logger = NLog.LogManager.GetCurrentClassLogger();
try
{
logger.Info("Application starting up.");
CreateHostBuilder(args).Build().Run();
}
catch (Exception ex)
{
logger.Error(ex, "Application stopped because of exception.");
throw;
}
finally
{
NLog.LogManager.Shutdown();
}
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.SetMinimumLevel(LogLevel.Trace);
})
.UseNLog() // This should work after installing NLog.Web.AspNetCore
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}