4

I have a client which consumes my API. I have an endpoint on the API like so:

[Route("api/account")]
[ApiController]
public class AccountController : BaseController
{
    private readonly IIdentityProvider _identityProvider;

    private string UserId 
    { 
        get
        {
           return User?.FindFirst("AspNetUserId")?.Value;
        }
    }

    public AccountController(IIdentityProvider identityProvider)
    {
        _identityProvider = identityProvider;
    }

    [HttpGet]
    public IActionResult GetAccount()
    {
       var user = _identityProvider.GetByAspNetUserId(UserId);
       var account = new Account
       {
          UserName = user.UserName,
          Email = user.Email,
          EmailConfirmed = user.EmailConfirmed,
          PhoneNumber = user.PhoneNumber,
          PhoneNumberConfirmed = user.PhoneNumberConfirmed,
          TwoFactorEnabled = user.TwoFactorEnabled,
          SmsTwoFactorEnabled = user.SmsTwoFactorEnabled
       };

       return Ok(account);
     }
}

My client Vue.js application calls this action like so:

async getAccount(): Promise<Account> {
  const result = await this.axios.get(`${this.apiUrl}/account`);
  return this.jsonConvert.deserializeObject(result.data, Account);
}

Whenever this is called I get a 500 error. I've put a breakpoint on the start line of the action, but it doesn't get called. But when I call this same action through Postman, it works. There is nothing different in the 2 requests.

However if I change the [HttpGet] to [HttpGet("test") and change the vue.js client to await this.axios.get(${this.apiUrl}/account/test); it works and hits the breakpoint.

Can anyone explain what I'm doing wrong here?

EDIT:

The following is what is in the inspector of my browser, as you can see there is no useful information here. It just says 500 internal error with no response:

enter image description here

Here is the configure method of my Startup.cs file:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
   app.UseRouting();
   app.UseCors("AllowAll");
   app.UseAuthentication();
   app.UseAuthorization();
   app.UseEndpoints(endpoints =>
   {
       endpoints.MapControllers();
       endpoints.MapDefaultControllerRoute();
   });
}
dunu008
  • 105
  • 1
  • 15
KTOV
  • 559
  • 3
  • 14
  • 39
  • 500 means the server is crashing on something. Have you had a look in your browser developer console to see what is happening on the network request? They server should be sending back the info of the crash if it is local development – Keith Nicholas Nov 10 '20 at 00:49
  • best guess is your vue app is not authenticated, but you've set up authentication in postman.... – Keith Nicholas Nov 10 '20 at 00:52
  • @KeithNicholas I've had a look at the console and their is no errors. I also forgot to mention their is no authentication middleware or some sort. For testing purposes, I can call this without requiring auth – KTOV Nov 10 '20 at 13:32
  • not the console, the network – Keith Nicholas Nov 10 '20 at 20:56
  • Does the browser inspector "Response" tab contain any useful info? Usually if you're in debug, it will show the exception for 500s. – Bryan Lewis Nov 11 '20 at 17:59
  • It just says "Failed to load response data" @BryanLewis – KTOV Nov 11 '20 at 19:58
  • To help determine if this is some sort of weird routing issue or a code bug, can you change the GetAccount() method to just return ok and nothing else ("return Ok();") and see if that works. If not, then there is some strange default route problem. – Bryan Lewis Nov 11 '20 at 20:58
  • I should've mentioned, I removed all code from the method and just return Ok() but it still throws a 500 exception with no explanation @BryanLewis – KTOV Nov 11 '20 at 21:01
  • Since you're using [ApiController], I assume you are using .Net Core. What version are you using? What does the Configure method look like in your Startup.cs? Is it using the default "app.UseEndpoints"? – Bryan Lewis Nov 11 '20 at 21:19
  • can you try async getAccount(): Promise { const result = await this.axios.get(`${this.apiUrl}/account/GetAccount `); return this.jsonConvert.deserializeObject(result.data, Account); } – Nonik Nov 11 '20 at 21:25
  • @BryanLewis I'm using .NET Core 3.1 and have added the configure method in the question. – KTOV Nov 11 '20 at 21:33
  • @Nonik This doesnt work as the api returns a 404 – KTOV Nov 11 '20 at 21:34
  • I see that you are inheriting from "BaseController" and not "ControllerBase" (which is the default base for .Net Core 3.1 API). Are you using a custom base controller? If so, perhaps you have something in there that is messing stuff up? Can you try removing that or inheriting from the default ControllerBase? Or is this an MVC project? – Bryan Lewis Nov 11 '20 at 21:42
  • Was this project migrated from an older version of .Net Core, or is it a fresh 3.1 project? – Bryan Lewis Nov 11 '20 at 21:47
  • What's the full request URL you're using in Postman? – jslowik Nov 11 '20 at 21:50
  • @BryanLewis The BaseController inherits from ControllerBase, the BaseController just has useful methods which I can use across all controllers that inherit from it – KTOV Nov 12 '20 at 00:42
  • @BryanLewis The project was created using .NET Core 3.1 and not migrated from an older version – KTOV Nov 12 '20 at 00:43
  • @jslowik The request URL is http://localhost:65384/api/account in Postman. What I have noticed is, if I click the request on the console network tab to resend it works and it hits the breakpoints in the action but doesn't work through the client when the client makes the request – KTOV Nov 12 '20 at 00:44
  • Try one call and hardcode 'https://localhost:65384/api/account' instead of $this.apiUrl just to make sure there is no bug in the code. I would also test once commenting out the code in the constructor and descending directly from the Controller class. – Ross Bush Nov 13 '20 at 16:06
  • How are you going to get an answer with incomplete code? Where is the definition for `UserId` in your controller? I would bet the house that this line `var user = _identityProvider.GetByAspNetUserId(UserId);` is throwing the exception, but alas, you didn't post the code to that method as well. That's where your problem is. If only you knew how to set breakpoints and debug your backend, you'd have fixed this without a bounty. – Andy Nov 13 '20 at 16:18
  • If that line was throwing the exception then why hasn't it hit the breakpoint before that line just when the action is called? I would bet if UserId didn't exist then there'd be some compiler errors. But go ahead, you seem to know what the problem is so I'll post the UserId property :) @Andy – KTOV Nov 13 '20 at 16:40
  • If it helps you to understand the problem more.. I've removed all code from the action and JUST return Ok(); as previously mentioned in these comments and it still doesn't work. Now.. can we get round the transferring ownership of this house you wanted to bet on @Andy – KTOV Nov 13 '20 at 16:49
  • @RossBush I've tried this by removing the apiUrl and that doesn't work which I'm not surprised about because the value was always rendered out – KTOV Nov 13 '20 at 16:53
  • But what about server logs? That's your own api, error 500 should produce an exception or something in server logs – Evk Nov 15 '20 at 20:09
  • If you can debug the application I would suggest enabling All CLR exceptions (https://learn.microsoft.com/en-us/visualstudio/debugger/managing-exceptions-with-the-debugger?view=vs-2019#tell-the-debugger-to-break-when-an-exception-is-thrown), also take a look at the Output window for detailed logs on all threads. What difference do you see in request headers for both the requests? My hunch says it has to do with Authentication header in the request. – mbshambharkar Nov 17 '20 at 14:23
  • @KTOV I just spun up a WebApi app in VS, using your `AccountController` class and `Configure` method, but I cannot reproduce the issue you mentioned (i.e., it returns the correct result, and no errors occur). Can you provide a GitHub link to a project that reproduces the problem? – tony19 Nov 18 '20 at 04:42

4 Answers4

3

It's not failing the GET request but the OPTIONS request. Vue.js does a preflight for requests. So if your endpoint has a GET verb, when called with the OPTIONS one it will throw an error.

You have 2 solutions:

  1. Simple solution, allow OPTIONS verb for your endpoint, check for it, and return a 200.
  2. You can check the answer to this question and make a similar solution: cors issue with vue and dotnet and implement an auto-response for all OPTIONS requests.

For a reference, you can read preflight-requests

Anatoly
  • 20,799
  • 3
  • 28
  • 42
Michaelsoft
  • 777
  • 5
  • 18
  • 1
    It shows in the question that it's not a CORS error but an HTTP 500 error. – Andy Nov 13 '20 at 16:36
  • His request is doing an OPTIONS to a GET endpoint. That's a 500. – Michaelsoft Nov 13 '20 at 16:38
  • If the options failed, then why is there a 2nd request? It would have stopped after the first, correct? – Andy Nov 13 '20 at 16:40
  • The image shows a 500 error to the options request. Under the tooltip there is the "OPTIONS" label. The strange thing is that it seems that the options request is the second one... – Michaelsoft Nov 13 '20 at 16:42
  • 1
    You might be along the right lines here, I've just compared the request method against other requests in the client.. another get request in the client application actually shows the request being made as GET whilst the account request is OPTIONS – KTOV Nov 13 '20 at 16:52
  • meh -- if it turns out to be a CORS thing, then all this really was a waste of time since Chrome is very verbal about those errors. – Andy Nov 13 '20 at 16:52
  • @Andy I already faced this kind of errors, Chrome never helped more than that 500 on options... Usually Chrome complains about origins or other header error. I used various FE framework and that failed options is usually due to the server complaining about the verb. – Michaelsoft Nov 13 '20 at 16:55
  • What I don't understand is why does changing the action to `[HttpGet("test")]` and calling the endpoint accordingly work? Why doesn't it work without a route on the action? – KTOV Nov 13 '20 at 17:01
  • @KTOV I don't think that the problem is the route, but the fact that you force axios to do directly a GET request. As you can also read on MS doc you can make an options endpoint to match every request by removing the route. – Michaelsoft Nov 13 '20 at 17:09
  • But I've forced axios to do a GET request in other requests and it still works @Michael – KTOV Nov 13 '20 at 17:16
  • @KTOV, ok I misunderstood, well... in effect this is a bit weird, can you post some other working request and its controller without the route? – Michaelsoft Nov 13 '20 at 17:28
  • @KTOV oh... is the AccountController you posted the complete controller? ...or are there any other endpoints inside it? – Michaelsoft Nov 13 '20 at 17:30
  • @Michael Yes there are more endpoints inside. There's alot of actions in the controller so I wouldn't want to flood the question with things that may be irellevant but out of the 14 actions, 1 is a GET which is this one Im having problems with, 1 is a PUT and the rest are POST. None of them conflict with eachother in terms of routing – KTOV Nov 13 '20 at 17:34
  • @KTOV well, without further informations I can't say the exact reason why giving to this action a route resolves the problem with a GET request... I can give you a suggestion, import and configure swashbuckle to get swagger on development side, you can get a view on all the endpoints and try them without postman. Maybe you can receive a more detailed error. – Michaelsoft Nov 13 '20 at 17:51
1

This behavior could be caused by routing issue which can be fixed by adding an empty route on your method to allow a get request made to Route("api/account").

    [Route("")]
    [HttpGet]
    public IActionResult GetAccount()
    {
       var user = _identityProvider.GetByAspNetUserId(UserId);
       var account = new Account
       {
          UserName = user.UserName,
          Email = user.Email,
          EmailConfirmed = user.EmailConfirmed,
          PhoneNumber = user.PhoneNumber,
          PhoneNumberConfirmed = user.PhoneNumberConfirmed,
          TwoFactorEnabled = user.TwoFactorEnabled,
          SmsTwoFactorEnabled = user.SmsTwoFactorEnabled
       };

       return Ok(account);
     }

When I debug locally and attempt to get an endpoint without an empty route on a method get a 404 error without the breakpoint for the method ever hitting. Adding the empty route cause the breakpoint to hit.

As far as the 500, your application could very well have something causing a 500 instead of a 404 such as a null reference in a custom error handler, or your base controller class or some kind of error in a filter like an authorization filter.

Your debugger should give you more information about the exact reason for the 500 error. If you are hosting in IIS you can enable request tracing for error codes above 500.

Alexander Higgins
  • 6,765
  • 1
  • 23
  • 41
0

I see that you use DI, did you register the interface IdentityProvider on startup.cs too. for cors issues, you should first add the cors on ConfigureServices like this

services.AddCors(options =>
        {
            options.AddPolicy("CorsPolicy",
                builder => builder
                    .AllowAnyHeader()
                    .AllowAnyMethod()
                    .AllowAnyOrigin());
        });

in configure method add the cors with this policy like this

app.UseRouting();
app.UseCors("CorsPolicy");
KBT
  • 771
  • 5
  • 10
0

I think it's about your controller routing. Your controller route([Route("api/account")]) means when you want to call its actions, you shouldn't specify Action in your URL. AccountController can one action per each HTTP verb(you can't have more than one HttpGet in AccountController). If you have more than one HttpGet in your AccountController change its routing like this:

[Route("api/[Controller]/[Action]")]

and then add action name to URL in front-end part:

const result = await this.axios.get(`${this.apiUrl}/account/getaccount`);
Saeid Amini
  • 1,313
  • 5
  • 16
  • 26