How to do exception handling in asp.net core? - c#

I have to do the exception handling in asp.net core I have read so many articles and I have implemented it on my startup.cs file here is the code
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IServiceProvider svp)
{
app.UseExceptionHandler(errorApp =>
{
errorApp.Run(async context =>
{
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; ; // or another Status accordingly to Exception Type
context.Response.ContentType = "application/json";
var error = context.Features.Get<IExceptionHandlerFeature>();
if (error != null)
{
var ex = error.Error;
await context.Response.WriteAsync(new ErrorDto()
{
Code = 1,
Message = ex.Message // or your custom message
// other custom data
}.ToString(), Encoding.UTF8);
}
});
app.UseMvc();
I am having a problem that how to call this code when there is exception occur in my controller.
I will be very thankfullk to you.
Here is the controller code-:
[HttpPost]
[AllowAnonymous]
public async Task<JsonResult> Register([FromBody] RegisterViewModel model)
{
int count = 1;
int output = count / 0;
var user = new ApplicationUser { UserName = model.Email, Email = model.Email, FirstName = model.FirstName, LastName = model.LastName, UserType = model.UserType };
user.FirstName = user.UserType.Equals(Models.Entity.Constant.RECOVERY_CENTER) ? model.Name : model.FirstName;
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: <a href='{callbackUrl}'>link</a>");
await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation(3, "User created a new account with password.");
user = await _userManager.FindByEmailAsync(user.Email);
var InsertR = await RecoveryGuidance.Models.Entity.CenterGateWay.AddNewRecoveryCenter(new Models.Entity.Center { Rec_Email = user.Email, Rec_Name = user.FirstName, Rec_UserId = user.Id });
}
AddErrors(result);
return Json(result);
}

You don't need to call it. UseExceptionHandler is an extension method which uses ExceptionHandlerMiddleware. See middleware source code:
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);// action execution occurs in try block
}
catch (Exception ex)
{
// if any middleware has an exception(includes mvc action) handle it
}
}

Related

Azure AD authentication to existing ASP.NET Core Identity application

I currently have and application that is using Identity to authorize users. I need to change it to use Azure AD to login. After being authenticated through azure I need to use the information of the logged in user that we have in the identity database. After the user is authenticated I get a
NullReferenceException: Object reference not set to an instance of an object.
and fails at this point:
ApplicationUser user = await manager.FindByNameAsync(context.Principal.Identity.Name);
```
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication().AddOpenIdConnect(c =>
{
c.Authority = "https://login.microsoftonline.com/common";
c.ClientId = "<insert-registered-guid>";
c.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false
};
c.Events.OnTokenValidated = async context =>
{
UserManager<ApplicationUser> manager = context.HttpContext.RequestServices.GetService<UserManager<ApplicationUser>>();
SignInManager<ApplicationUser> signIn = context.HttpContext.RequestServices.GetService<SignInManager<ApplicationUser>>();
ApplicationUser user = await manager.FindByNameAsync(context.Principal.Identity.Name);
if (user != null)
{
await signIn.SignInAsync(user, false);
}
};
});
}
// HomeController.cs
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
public class HomeController : Controller
{
[AllowAnonymous]
public IActionResult LoginWithAzure()
{
string redirectUrl = Url.Content("~/");
return Challenge(new AuthenticationProperties { RedirectUri = redirectUrl }, OpenIdConnectDefaults.AuthenticationScheme);
}
}
```
UPDATE:
I was able to get past the error because I was missing
services.AddIdentity
Now the issue is that it gets stuck in a loop inside the OnTokenValidated.
UserManager<ApplicationUser> manager = context.HttpContext.RequestServices.GetService<UserManager<ApplicationUser>>();
SignInManager<ApplicationUser> signIn = context.HttpContext.RequestServices.GetService<SignInManager<ApplicationUser>>();
ApplicationUser user = await manager.FindByNameAsync(context.Principal.Identity.Name);
if (user != null)
{
await signIn.SignInAsync(user, false);
}
after the if statement it goes back to the manager line.
The above solution was not working so I changed it.
Startup.cs was changed to the following:
// Add Azure AD authentication
services.AddAuthentication(defaultScheme: AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => Configuration.Bind("AzureAd", options));
AccountController.cs was changed to this:
[AllowAnonymous]
[HttpGet]
public ChallengeResult InternalSignIn(string returnUrl = "/")
{
var redirectUrl = Url.Action(nameof(ExternalLoginCallback));
var properties = signInManager.ConfigureExternalAuthenticationProperties(AzureADDefaults.AuthenticationScheme, redirectUrl);
return new ChallengeResult(AzureADDefaults.AuthenticationScheme, properties);
}
[HttpGet]
public async Task<IActionResult> ExternalLoginCallback()
{
var info = await signInManager.GetExternalLoginInfoAsync();
if (info is null)
{
return BadRequest();
}
var signInResult = await signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: false);
var email = info.Principal.FindFirstValue(ClaimTypes.Name);
var user = await userManager.FindByEmailAsync(email);
IdentityResult result;
if (user != null)
{
var logins = await userManager.GetLoginsAsync(user);
if (!logins.Any())
{
result = await userManager.AddLoginAsync(user, info);
if (!result.Succeeded)
{
return View();
}
}
await signInManager.SignInAsync(user, isPersistent: false);
return RedirectToAction(nameof(HomeController.Index),"Home");
}
return StatusCode(500, "Internal server error");
}

How To Pass ErrorMessage From API to MVC In Asp.Net Core

I've created an API. When you face an error, It shows you the type of error with it's message. But When I try to use that API in my MVC project, It just shows the type of error. I want to see the message in Modelstate.AddModelError
Here is API controller for Login
[HttpPost("login")]
public async Task<IActionResult> LoginUser([FromBody] UserDtoLogin user)
{
var userToRetrieve = await _applicationDbContext.Users.FirstOrDefaultAsync(u => u.UserName == user.UserName);
if (userToRetrieve == null)
{
ModelState.AddModelError("username", "Such a user doesn't exists! Enter the correct username please");
return NotFound(ModelState);
}
if (!_userRepository.VerifyPasswordHash(user.Password, userToRetrieve.PasswordHash, userToRetrieve.PasswordSalt))
{
ModelState.AddModelError("password", "Wrong Password!");
return BadRequest(ModelState);
}
await _userRepository.Login(userToRetrieve);
return Ok(user);
}
Here is MVC Controller for Login
[HttpPost]
public async Task<IActionResult> Login(User user)
{
var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost:42045/api/user/login");
if (user != null)
{
request.Content = new StringContent(JsonConvert.SerializeObject(user),
System.Text.Encoding.UTF8, "application/json");
}
var client = _clientFactory.CreateClient();
HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
ViewBag.StatusCode = System.Net.HttpStatusCode.OK;
var apiString = await response.Content.ReadAsStringAsync();
user = JsonConvert.DeserializeObject<User>(apiString);
}
else
{
ViewBag.StatusCode = response.StatusCode;
}
return View(user);
}
I write a simple demo to show how to pass ErrorMessage From API to MVC In Asp.Net Core, you can reference to it.
API
[Route("api/[controller]")]
[ApiController]
public class LoginController : ControllerBase
{
//For testing convenience, I use hard code here
List<UserDtoLogin> context = new List<UserDtoLogin>
{
new UserDtoLogin
{
UserName = "Mike"
},
new UserDtoLogin
{
UserName = "Jack"
},
new UserDtoLogin
{
UserName = "Lily"
}
};
[HttpPost("login")]
public IActionResult LoginUser([FromBody] UserDtoLogin user)
{
var userToRetrieve = context.FirstOrDefault(u => u.UserName == user.UserName);
if (userToRetrieve == null)
{
return BadRequest("Such a user doesn't exists! Enter the correct username please");
}
//your logic.....
return Ok();
}
}
MVC/Controller
[HttpPost]
public async Task<IActionResult> Privacy(UserDtoLogin todoItem)
{
var todoItemJson = new StringContent(JsonSerializer.Serialize(todoItem),Encoding.UTF8,Application.Json);
using var httpResponseMessage = await _httpClient.PostAsync("your api url", todoItemJson);
var errormessage = httpResponseMessage.Content.ReadAsStringAsync().Result;
return View(todoItem);
}
Then you can see it can receive the errormessage successfully.

I want to register my first Admin to start working on the rest of the roles and I don't know what service is missing in my IServicecollection

I am working with .NET 5 to build an online shop. My project is divided into 4 projects , {project'the main which includes the controllers', project.DataAccess , project.Models , project.Utility} I am using RazorPages and Identity core package for the registeration and handling roles part. In the utility project I have class "SD" storing roles contains the following code,
public static class SD
{
public const string Role_User_Indi = "Individual Customer";
public const string Role_User_Designer = "Designer Customer";
public const string Role_Admin = "Admin";
public const string Role_Moderator = "Moderator";
}
//now this is inside register.cshtml.cs in the identity folder inside the areas
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl ??= Url.Content("~/");
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
if (ModelState.IsValid)
{
var user = new ApplicationUser
{
UserName = Input.UserName,
Email = Input.Email,
DesignerShopId = Input.DesignerShopId,
Role = Input.Role
};
var result = await _userManager.CreateAsync(user, Input.Password);
if (result.Succeeded)
{
_logger.LogInformation("User created a new account with password.");
//first check if the role is in the database
if(!await _roleManager.RoleExistsAsync(SD.Role_Admin))
{
//if it doesn't exist it will create it
await _roleManager.CreateAsync(new IdentityRole(SD.Role_Admin));
}
if (!await _roleManager.RoleExistsAsync(SD.Role_Moderator))
{
//if it doesn't exist it will create it
await _roleManager.CreateAsync(new IdentityRole(SD.Role_Moderator));
}
if (!await _roleManager.RoleExistsAsync(SD.Role_User_Indi))
{
//if it doesn't exist it will create it
await _roleManager.CreateAsync(new IdentityRole(SD.Role_User_Indi));
}
if (!await _roleManager.RoleExistsAsync(SD.Role_User_Designer))
{
//if it doesn't exist it will create it
await _roleManager.CreateAsync(new IdentityRole(SD.Role_User_Designer));
}
//for now we will make anyone registers as an admin as default
await _userManager.AddToRoleAsync(user, SD.Role_Admin);
/** 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 = user.Id, 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();
I wanted to register my first admin through running the project and register on the website that's why I commented some of the PostOnSync to make anybody register for that moment an admin as You can see in the code and I changed in the start up class to inject Identity role with the default tokens As the following
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
//services.AddDatabaseDeveloperPageExceptionFilter();
services.AddIdentity<IdentityUser,IdentityRole>().AddDefaultTokenProviders()
.AddEntityFrameworkStores<ApplicationDbContext>();
//services.AddIdentity<IdentityUser, IdentityRole>(options => options.SignIn.RequireConfirmedAccount = true)
// .AddEntityFrameworkStores<ApplicationDbContext>();
services.AddSingleton<IEmailSender, EmailSender>();
services.AddScoped<IUnitOfWork, UnitOfWork>();
services.AddControllersWithViews().AddRazorRuntimeCompilation();
services.AddControllersWithViews();
services.AddHealthChecks();
}
It throughs a runtime exception that
Unable to find the required services. Please add all the required services by calling 'IServiceCollection.AddRazorPages' inside the call to 'ConfigureServices(...)
What can I do to resolve this exception , Is there an other way to register my first admin? should I add it manually to the database table in sql mangement studio? any better way to customize the package to handle roles as I want ?
did you try to add the following line to you ConfigureServices() method ? :
services.AddRazorPages();
As you uses Razor for pages and rendering, you has to declare it in the service.
Hope it may fix your problem

How can I fix email confirmation - in .NET Core, it doesn't work

I already have a register action
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
var useremail = _userManager.Users.FirstOrDefault(u => u.Email.ToLower() == Input.Email.ToLower());
if (useremail == null)
{
returnUrl = returnUrl ?? Url.Content("~/");
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
if (ModelState.IsValid)
{
var user = new IdentityUser { UserName = Input.UserName, Email = Input.Email };
var result = await _userManager.CreateAsync(user, Input.Password);
if (result.Succeeded)
{
_logger.LogInformation("User created a new account with password.");
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 = 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>.");
if (_userManager.Options.SignIn.RequireConfirmedAccount)
{
return RedirectToPage("RegisterConfirmation", new { email = Input.Email });
}
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
ViewData["EmailExists"] = "Try another email that one is used";
return Page();
}
Then I created the sendgrid user and key and registered them by CMD, then I created the action of send email
public class EmailSender : IEmailSender
{
public EmailSender(IOptions<AuthMessageSenderOptions>optionsAccessor)
{
Options = optionsAccessor.Value;
}
public AuthMessageSenderOptions Options { get; }
public Task SendEmailAsync (string email , string subject , string message)
{
return Excute(Options.SendGridKey,subject,message,email);
}
private Task Excute(string apiKey, string subject, string message, string email)
{
var client = new SendGridClient(apiKey);
var msg = new SendGridMessage()
{
From = new EmailAddress("darydress#yahoo.com", "dary dress"),
Subject = subject,
PlainTextContent = message,
HtmlContent = message
};
msg.AddTo(new EmailAddress(email));
msg.SetClickTracking(false, false);
return client.SendEmailAsync(msg);
}
}
Then in startup.cs
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<IdentityUser, IdentityRole>( options => options.SignIn.RequireConfirmedAccount = true)
.AddDefaultUI()
.AddDefaultTokenProviders()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddControllersWithViews();
services.AddRazorPages();
services.AddMvc();
services.AddTransient<IEmailSender, EmailSender>();
services.Configure<AuthMessageSenderOptions>(Configuration);
services.AddPaging();
services.ConfigureApplicationCookie(o => {
o.ExpireTimeSpan = TimeSpan.FromDays(5);
o.SlidingExpiration = true;
});
services.AddMvc(options =>
{
options.Filters.Add(new RequireHttpsAttribute());
});
services.ConfigureApplicationCookie(options =>
{
options.AccessDeniedPath = new Microsoft.AspNetCore.Http.PathString("/Main/AccessDenied");
});
}
but sending an e-mail doesn't work after registration gives me some words that i need confirm my email and gives me link to confirm my email but doesn't send it to gmail
Does anyone have an idea?
I followed this documentation from microsoft
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/accconfirm?view=aspnetcore-3.1&tabs=visual-studio
When I create a new web application with individual user accounts, this works perfectly, but I noticed that when you scaffold identity and override all pages to have control into an existing app, the behavior you are experiencing is the usual.
Here is how I fixed it:
If you open the file Areas/Identity/Pages/Account/RegisterConfirmation.cshtml.cs look for the comment Once you add a real email sender, you should remove this code that lets you confirm the account, comment everything below that line before the return Page() statement and that should do the job.
"Solved" I asked Sendgrid, and I was told that I cannot use my yahoo email (or gmail,...) as the sender email; this is part of the answer: "Yahoo observes an email security standard called DMARC. DMARC instructs email providers to reject messages where the From domain is a Yahoo domain, but the message originates from a non-approved domain server/service." So I need to use my own mail domain;

Implement the SendEmailAsync interface and send Email

I don't know how to invoke the SendEmailAsync function
Register Post page
public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
var user = new ApplicationUser { 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: <a href='{callbackUrl}'>link</a>");
await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation(3, "User created a new account with password.");
return RedirectToLocal(returnUrl);
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
Email configuration Page
// this code was already there
public interface IEmailSender
{
Task SendEmailAsync(string email, string subject, string message);
}
//I got this code from some other site. I don't know how to use this together.
public class sendMail : IEmailSender // this line is written by me
{
public async Task SendEmailAsync(string email, string subject, string message)
{
var emailMessage = new MimeMessage();
emailMessage.From.Add(new MailboxAddress("Joe Bloggs", "jbloggs#example.com"));
emailMessage.To.Add(new MailboxAddress("", email));
emailMessage.Subject = subject;
emailMessage.Body = new TextPart("plain") { Text = message };
using (var client = new SmtpClient())
{
client.LocalDomain = "some.domain.com";
await client.ConnectAsync("smtp.relay.uri", 25, SecureSocketOptions.None).ConfigureAwait(false);
await client.SendAsync(emailMessage).ConfigureAwait(false);
await client.DisconnectAsync(true).ConfigureAwait(false);
}
}
}
how the write the class definition to send email.
Break point does not reach here, I think there is some problem with my implementation.
in Startup make sure you change this:
services.AddTransient<IEmailSender, AuthMessageSender>();
to
services.AddTransient<IEmailSender, sendMail>();
so that your implementaiton gets injected

Categories