Mvc: Razor Pages responding to all HTTP verbs

Created on 5 Mar 2018  路  18Comments  路  Source: aspnet/Mvc

I've been noticing some exceptions being logged on a Razor Pages application. The exceptions looked to be random, and were logged on pages that are incredibly simple and always worked perfectly when tested in a browser.

Eventually I tracked the errors down to when pages are requested with HEAD or OPTIONS HTTP verbs. The exceptions are thrown as the view is executed, because the page's OnGet() has not prepared the view model and it is in an uninitialised state.

Some quick testing on an MVC project shows that things just seem to work as expected out of the box. If the action is decorated with any standard HTTP verbs ([HttpGet], [HttpPost]) then a HEAD or OPTIONS request returns a 404 status response, which makes good sense. If there are no HTTP verb attributes present then a HEAD or OPTIONS request returns an empty 200 status response, which again seems fine.

Is there a suggested best practice for dealing with the above with Razor Pages?

3 - Done 2 - Preferred bug S

Most helpful comment

Yep. Self-assigning myself.

This is working as we intended it to, but we still got it wrong in this case. What really changed my mind is seeing a bunch of failures in production logs for innocent little HEAD requests. That's something that needs to 'just work'

All 18 comments

Hi. Thanks for contacting us. @NTaylorMullen, any thoughts regarding this one?

@pranavkm is better suited to answer this one 馃槃

If the action is decorated with any standard HTTP verbs ([HttpGet], [HttpPost])

Could you share a snippet of what you're doing to make this happen? Handlers on Razor Pages do not support method constraints the same way controller actions do, so I'm not entirely sure what your set up looks like.

@pranavkm That paragraph is talking about me testing using a traditional MVC controller and action. I've never seen exceptions being logged for HEAD or OPTIONS in other MVC applications I'm running, so I wanted to compare how things were handled there. It seems like MVC just does the Right Thing out of the box, whereas Razor Pages is always going to break with verbs other than GET and POST with a complex view model, as far as I can see.

The behavior you're seeing is by design. Razor Pages do a best effort to find a handler and fall back to rendering the page sans handler when it cannot find a match. There's a couple of recommendations here to organize your code

  • Write your code defensively assuming no handler was selected
  • Initialize things you always expect to be present in the model's OnPageHandlerExecuting method. This should run for all methods.

Another alternative might be to use a filter to 404 requests if no handler is selected. You can register a global filter along the lines of:

```C#
private class NoHandlerPageFilter : IPageFilter
{
public void OnPageHandlerExecuted(PageHandlerExecutedContext context)
{
}

public void OnPageHandlerExecuting(PageHandlerExecutingContext context)
{
    if (context.HandlerMethod == null && context.ActionDescriptor.HandlerMethod.Count > 0)
    {
        context.Result = new NotFoundResult();
    }
}

public void OnPageHandlerSelected(PageHandlerSelectedContext context)
{
}

}
```

Yet another option might be add a page route model convention that adds a method based action constraint to your page selectors.

Closing the issue as it's by design. If you think there is something we haven't answered or considered, feel free to reopen it and leave your comments.

Thanks for the responses. I noticed that @shanselman bumped in to this same issue and documents it in a blog post (with further examples of people bumping into the same issue in the comments, and only noticing due to pen testing):

https://www.hanselman.com/blog/SettingUpApplicationInsightsTook10MinutesItCreatedTwoDaysOfWorkForMe.aspx

THOUGHT: I think this behavior is sub-optimal. While GET and POST are distinct and it makes sense to require an OnGet() and OnPost(), I think that HEAD is special. It's basically a GET with a "don't return the body" flag set. So why not have Razor Pages automatically delegate OnHead to OnGet, unless there's an explicit OnHead() declared? I'll file an issue on GitHub because I don't like this behavior and I find it counter-intuitive. I could also register a global IPageFilter to make this work for all my site's pages.

I'd agree with the above. It still needs to be possible for developers to override it with a specific handler, but it seems like the out of the box experience could be improved - in reality I would expect that a very high percentage of Razor Pages applications will break on HEAD requests, and that it won't be noticed unless the site is being tested or monitored thoroughly.

@svallis I think @rynowak is working on adding support for HEAD and OPTIONS requests mapping to GET as part of preview2. He's promised to file an issue for this soon.

@pranavkm @rynowak That sounds great, providing you can still override the handler on a per-page basis should the need ever arise. It would be fantastic to see it make the release of v2.1.0 - thanks for all the hard work!

Yep. Self-assigning myself.

This is working as we intended it to, but we still got it wrong in this case. What really changed my mind is seeing a bunch of failures in production logs for innocent little HEAD requests. That's something that needs to 'just work'

Fixed by 1442972

@rynowak I'm still seeing exceptions for OPTIONS requests after upgrading to 2.1.0. Did this fix make it into the release?

This is attached to a compatibility switch. Add AddMvc().SetCompatibilityVersion(Microsoft.AspNetCore.Mvc.CompatibilityVersion.Version_2_1); to startup

Like: https://github.com/aspnet/Mvc/commit/14429721d933104e17bd5cae0b7f2399ca55ab2d#diff-85ae0edbe5e347e453bfdfc8467ce8f5R28

Let us know if you're still having problems after doing this

@rynowak We're experiencing this using CompatibilityVersion.Latest, I'm assuming that would currently equate to CompatibilityVersion.Version_2_1?

I'm reopening this for consideration. We didn't do anything about OPTIONS

@svallis - who's sending you OPTIONS requests and what for? is it a crawler?

@rynowak The frequency of logged exceptions is massively down now HEAD isn't causing them, but I still see a couple a week for OPTIONS requests. I'm not logging user agent with the exception currently, but my assumption would be that it's a crawler of some sort. It's possibly there's some weird browser/client out there that does it I suppose, too. I see the exceptions on different, random content pages on the site in question.

Sorry I don't have any more definite information; if it's important I can look into logging additional relevant data for you.

Yeah, this is kinda in no man's land. Traditionally in MVC OPTIONS would be handled by any method-neutral action - usually the same one that handles GET. However this isn't really correct, since you're not supposed to just treat OPTIONS the same ways.

We didn't want to double-down on that incorrectness, so we didn't lump it in with the fix for head. We might just want to automatically handle OPTIONS for pages (obviously allowing CORS to still work).

@mkArtakMSFT - clearing fields so this can be reconsidered

Was this page helpful?
0 / 5 - 0 ratings