I have an Response object that I want to return to the user if there is an exception in the controller. However when i try to send back BadRequest i cant seem to send back my Response object. So my question is how do i edit BadRequst to contain my Response object and/or how do i send back my Response object with an error status code ?
Controller
public async Task<IActionResult> Login([FromBody] LoginViewModel model) {
Response response = new Response();
try {
if (ModelState.IsValid) {
var result = await signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
if (result.Succeeded) {
logger.LogInformation(1, "User logged in.");
return Json("User logged in.");
}
if (result.RequiresTwoFactor) {
response.id = 1 ;
throw new LoginException("Login requiest two factor", new InvalidOperationException());
}
if (result.IsLockedOut) {
response.id = 2 ;
logger.LogWarning(2, "User account locked out.");
throw new LoginException("User account locked out", new InvalidOperationException());
}
else {
response.id = 3 ;
throw new LoginException("Invalid login attempt", new InvalidOperationException());
}
}
response.id = 4 ;
var modelErrors = ModelState.Values.ToList();
throw new LoginException("Model State Error", ModelState, new InvalidOperationException());
} catch (LoginException ex){
return BadRequest(response); // response with status: 400 Bad Request for URL, no response object is in here.
}
}
Front-End
login(email:any, password:any, remember:any){
//let body:User = {email:email, password:password};
let headers = new Headers({ 'Content-Type': 'application/json' });
let body = {Email:email, Password:password,RememberMe:false };
console.log(body);
this.http.post('/api/Account/Login', body ,{headers:headers})
.map(response => response.json())
.subscribe(
response => {
console.log("Success !!!:\n"+response);
this.router.navigate(['/home']);
},
error => {
console.log("Error !!!:\n"+error);
}
);
}
It's not really clear what you're asking?
You only want to return status code 400? Use the parameterless version of return BadRequest()
Want a different status code? Use different Method. BadRequest is named by the HTTP Statuscode 400 (BadRequest). If you want to set the status code yourself, use return Status(HttpStatusCode.Unauthorized); or whatever.
Want to return a error message as json? Use error classes or annonymous class to do so:
return BadRequest(new { ErrorMessage = "Account is locked." });
But whatever you want to do, throwing exceptions in Controller is wrong. An controller action should NEVER throw an exception. Also as pointed in the comments, exceptions are to be used for exceptional cases. A wrong password, locked user or wrong authentication method (i.e. no two factor auth when its required) are expected errors and shouldn't be handled via excpetions.
Exceptions (when thrown only) are inherently expensive operations in a computer program. For that reason Microsoft implemented an IdentityResult class to return a list of expected errors, rather than throwing exceptions during the call of signInManager.PasswordSignInAsync
Instead of returning IActionResult why don't you return a HttpResponseMessage and then you can do something like:
return Request.CreateResponse(System.Net.HttpStatusCode.BadRequest)
Related
So I have button that sends a POST request along with two variables, an outbox ID called oid, and an email. The controller takes in the variables and performs a method call, the end goal would be to send a JSON response back depending on whether it fails or succeeds.
I have the following written for the controller:
public ActionResult ResendEmail(int oid, string email = null)
{
try
{
Outbox.ResendEmail(oid, email);
return Json(new { success = true, message = "Email has been queued to be resent", status = (int)HttpStatusCode.OK });
}
catch (Exception)
{
return Json(new { success = false, message = "An error occurred when attempting to resend the email", status = (int)HttpStatusCode.InternalServerError });
}
}
Problem here, is that 1. If the oid and/or email is empty, the method call for Outbox.ResendEmail(oid, email) will still be called and a success json response will be returned. I'm also unsure if this is a proper way to use try/catch.
Feels like I should re-write how i'm handling this. Maybe an if statement to check for an existing oid and email followed by a try/catch?
The try/catch is useful for handling exceptions. If Outbox.ResendEmail() throws an exception, then the try/catch will do its thing.
However, if Outbox.ResendEmail() will not throw an exception when given an incorrect oid or an incorrect, empty, or null email, then you need something else, such as an if-statement.
In addition, it probably is not appropriate to return a 500 error due to invalid parameters. It's not really a server error if the user passes a null email address.
I would recommend something like the following:
public ActionResult ResendEmail(int oid, string email = null)
{
// If you have a way to validate the oid, then use that instead of just checking for 0:
if (oid == 0)
{
return // one of the 400 level responses
}
// Do you have a way to validate the email address?
// If so, probably do that here, depending on how Outbox.ResendEmail() behaves
if (string.IsNullOrWhiteSpace(email))
{
return // one of the 400 level responses
}
try
{
Outbox.ResendEmail(oid, email);
return Json(new { success = true, message = "Email has been queued to be resent", status = (int)HttpStatusCode.OK });
}
catch (Exception)
{
return Json(new { success = false, message = "An error occurred when attempting to resend the email", status = (int)HttpStatusCode.InternalServerError });
}
}
Try Catch always work with exception. As per the below statement in your question, it seems that Outbox.ResendEmail does not throw exception
If the oid and/or email is empty, the method call for
Outbox.ResendEmail(oid, email) will still be called and a success json
response will be returned. I'm also unsure if this is a proper way to
use try/catch
instead of exception, it may be returning response with status false or error or not 200. That why, it is not going to catch and returning json response with success = false.
you can do like this
public ActionResult ResendEmail(int oid, string email = null)
{
if ( oid <=0 || string.IsNullOrWhiteSpace(email) || validation of email if any)
{
return response; // with statuscode among 400 and proper message in response
//that where is the problem in request.
}
try
{
var response= Outbox.ResendEmail(oid, email);
if (response.StatusCode == 200) // condition for check that response is false or success
{
return Json(new { success = true, message = "Email has been queued to be resent", status = (int)HttpStatusCode.OK });
}
return Json(new { success = false, message = "An error occurred when attempting to resend the email", status = (int)HttpStatusCode.InternalServerError });
}
catch (Exception)
{
return Json(new { success = false, message = "An error occurred when attempting to resend the email", status = (int)HttpStatusCode.InternalServerError });
}
}
first its something that happening in webapi 2.2 and not in the old one
https://devblogs.microsoft.com/aspnet/asp-net-core-2-1-web-apis/
I get from webapi response
{
"ProductValue": [
"The input was not valid."
]
}
how i cancel this response and just get false in
ModelState.IsValid
i need to return more fields to response
and this response is not good for me
for those who have hard trouble to understand in dubug i dont enter to
this function at all,because web api built in mechanism
return his response instend of mine
{code=9}
public MyResponse Start(Request req)
{
if (ModelState.IsValid)
{
return new MyResponse(){code=0} ;
}
return new MyResponse(){code=9} ;
}
Not absolutely sure what you are trying to achieve but if you want to send your own custom error response then you can probably do something like below (Hypothetically)
Product p = GetProduct(productvalue);
if (p == null)
{
HttpError err = new HttpError($"Product with productvalue {productvalue} not found");
return Request.CreateResponse(HttpStatusCode.NotFound, err);
}
else
{
return Request.CreateResponse(HttpStatusCode.OK, p);
}
services.AddMvc()
.ConfigureApiBehaviorOptions(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
thanks to Kirk Larkin
How do I handle a failure when my method only returns a single return type?
public async Task<User> GetUser(int userId)
{
User user = null;
HttpResponseMessage response = await client.GetAsync("/User/...");
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadAsStringAsync();
user = JsonConvert.DeserializeObject<User>(result);
}
else
{
// ????
}
return user;
}
Success:
{
"a": 1,
"b": 2,
"c": 3
}
Failure
{
"error": "something has gone wrong..."
}
In the failure case currently I am just returning null, but I want to return the error message.
The API I am using doesn't return the same JSON for both failure and success, so I'm not sure how to best handle the failure case.
#Crowcoder's comment is the proper route. However, in case you ever had a successful response with a dynamic object structure this information could be useful. You can test a property on a returned object to see if it is undefined. In your case, and don't do this because you should use the status codes, you could do something like this:
if(result.error){
//this is an error state
} else {
//this was successful
}
My API Code is given below where we create login api:
public class AccountController : ApiController
{
private static readonly ILogWrapper Log = LogManagerWrapper.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private ApplicationSignInManager _signInManager;
/// <summary>
/// Gets this instance.
/// </summary>
/// <returns>HttpResponseMessage.</returns>
///
public ApplicationSignInManager SignInManager
{
get
{
return _signInManager ?? HttpContext.Current.GetOwinContext().Get<ApplicationSignInManager>();
}
private set { _signInManager = value; }
}
/// <summary>
/// Gets this instance.
/// </summary>
/// <returns>HttpResponseMessage.</returns>
///
[HttpGet]
public async Task<HttpResponseMessage> Get(string email,string password,bool remember)
{
var result = SignInStatus.Failure;
try
{
result = await SignInManager.PasswordSignInAsync(email, password, remember, shouldLockout: false);
}
catch (Exception ex)
{
Log.Error("Error in login = " + ex.Message, ex);
}
return Request.CreateResponse(HttpStatusCode.OK, result);
}
}
And Controller code is :
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
// This doen't count login failures towards lockout only two factor authentication
// To enable password failures to trigger lockout, change to shouldLockout: true
var code = HttpStatusCode.NotFound;
try
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:52958/");
client.DefaultRequestHeaders.Accept.Clear();
var res = await client.GetAsync("api/Account/Get?email=" + model.UserName + "&password=" + model.Password + "&remember=" + model.RememberMe + "");
code = res.StatusCode;
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
switch (code)
{
case HttpStatusCode.OK:
return RedirectToLocal(returnUrl);
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
}
When I execute this API through swagger then it respose very well it always show success if authentication detail is right else it give Failure But when i call this API through controller then I get always status is OK. How I get The correct Status code?
It should be returning 200 OK. In your switch statement based on the status code returned from the Web Api request, if 200 was returned, then you do a temporary redirect. A temporary redirect is actually a 301, but the web browser responds by issuing a new request for the URL in the redirect, and the response from that request (given everything goes right) will be a 200.
In the case your Web Api returns an error status code, you're returning a view, which is going to produce a 200 (again assuming everything goes right with returning that view).
This is actually the way it should be. The fact that your Web Api failed should not propagate into your MVC response. From the user's perspective, they are either being redirected or returned to the previous view with an error message. That is ideal. You wouldn't want to return a 500 to the user because the Web Api returned a 500. Then the user is stuck at an error page. Instead, you gracefully recover, deliver a response to the user (as a 200) that includes a message saying that their request couldn't be processed, try again later, etc. In other words, you explain what went wrong, but you're still delivering a successful response to the user.
It's really two different things. Delivering a successful response to the user doesn't mean that everything happened as it should. It just means that the browser has something it can work with, something it can present to the user. In this scenario, that's what you want, because the user needs to be informed about what happened, have the ability to fix their mistakes, etc.
I have an MVC webapi site that uses OAuth/token authentication to authenticate requests. All the relevant controllers have the right attributes, and authentication is working ok.
The problem is that not all of the request can be authorised in the scope of an attribute - some authorisation checks have to be performed in code that is called by controller methods - what is the correct way to return a 401 unauthorised response in this case?
I have tried throw new HttpException(401, "Unauthorized access");, but when I do this the response status code is 500 and I get also get a stack trace. Even in our logging DelegatingHandler we can see that the response is 500, not 401.
You should be throwing a HttpResponseException from your API method, not HttpException:
throw new HttpResponseException(HttpStatusCode.Unauthorized);
Or, if you want to supply a custom message:
var msg = new HttpResponseMessage(HttpStatusCode.Unauthorized) { ReasonPhrase = "Oops!!!" };
throw new HttpResponseException(msg);
Just return the following:
return Unauthorized();
As an alternative to the other answers, you can also use this code if you want to return an IActionResult within an ASP.NET controller.
ASP.NET
return Content(HttpStatusCode.Unauthorized, "My error message");
Update: ASP.NET Core
Above code does not work in ASP.NET Core, you can use one of these instead:
return StatusCode((int)System.Net.HttpStatusCode.Unauthorized, "My error message");
return StatusCode(Microsoft.AspNetCore.Http.StatusCodes.Status401Unauthorized, "My error message");
return StatusCode(401, "My error message");
Apparently the reason phrase is pretty optional (Can an HTTP response omit the Reason-Phrase?)
You get a 500 response code because you're throwing an exception (the HttpException) which indicates some kind of server error, this is the wrong approach.
Just set the response status code .e.g
Response.StatusCode = (int)HttpStatusCode.Unauthorized;
To add to an existing answer in ASP.NET Core >= 1.0 you can
return Unauthorized();
return Unauthorized(object value);
To pass info to the client you can do a call like this:
return Unauthorized(new { Ok = false, Code = Constants.INVALID_CREDENTIALS, ...});
On the client besides the 401 response you will have the passed data too. For example on most clients you can await response.json() to get it.
In .Net Core You can use
return new ForbidResult();
instead of
return Unauthorized();
which has the advantage to redirecting to the default unauthorized page (Account/AccessDenied) rather than giving a straight 401
to change the default location modify your startup.cs
services.AddAuthentication(options =>...)
.AddOpenIdConnect(options =>...)
.AddCookie(options =>
{
options.AccessDeniedPath = "/path/unauthorized";
})
you can use follow code in asp.net core 2.0:
public IActionResult index()
{
return new ContentResult() { Content = "My error message", StatusCode = (int)HttpStatusCode.Unauthorized };
}
You also follow this code:
var response = new HttpResponseMessage(HttpStatusCode.NotFound)
{
Content = new StringContent("Users doesn't exist", System.Text.Encoding.UTF8, "text/plain"),
StatusCode = HttpStatusCode.NotFound
}
throw new HttpResponseException(response);
Make sure that the lines order in "Startup.cs" is like this, not vise versa:
app.UseAuthentication(); // the order is important
app.UseAuthorization();
That was what cased the issue in my case.
Because I found this post as best match.
For a ASP.NET Core Web Api the ReturnType ContentResult is a good choice:
[HttpPost]
[Route("api/my-controller")]
public async ContentResult Index([FromBody] MyRequestType request)
{
if (!authenticate(request.User,request.Password)
{
return new ContentResult() { StatusCode = StatusCodes.Status401Unauthorized };
}
//Process request
var myReturnObject = await processRequest(request);
string errString = System.Text.Json.JsonSerializer.Serialize(myReturnObject);
return new ContentResult()
{
Content = errString,
StatusCode = StatusCodes.Status200OK,
};
}