8

I've got a service setup using the CorsFeature, and am using the approach that mythz suggested in other answers, collected in a function used in the appHost file:

private void ConfigureCors(Funq.Container container)
{
    Plugins.Add(new CorsFeature(allowedOrigins: "*",
                                allowedMethods: "GET, POST, PUT, DELETE, OPTIONS",
                                allowedHeaders: "Content-Type, Authorization, Accept",
                                allowCredentials: true));

    PreRequestFilters.Add((httpReq, httpRes) =>
    {
        //Handles Request and closes Responses after emitting global HTTP Headers
        if (httpReq.HttpMethod == "OPTIONS")
        {
            httpRes.EndRequest();
        }
    });
}

However, the pre-request filter is only firing on some of the service requests. One of the base entities we have in the service is a question entity, and there are custom routes defined as follows:

[Route("/question")]
[Route("/question/{ReviewQuestionId}", "GET,DELETE")]
[Route("/question/{ReviewQuestionId}/{ReviewSectionId}", "GET")]

Using POSTMAN to fire test queries (all using the OPTIONS verb), we can see that this will fire the pre-request filter:

http://localhost/myservice/api/question/

But this will not:

http://localhost/myservice/api/question/66

Presumably, this is because the second and third routes explicitly defined the verbs they accept, and OPTIONS isn't one of them.

Is it really necessary to spell out OPTIONS in every defined route that restricts the verbs supported?

gizmoboy
  • 260
  • 3
  • 12
  • 2
    I think yes, it is necessary to have OPTIONS in every Route "GET,OPTIONS" (at least, it is not necessary to create function for options or any ) my code is [this](http://stackoverflow.com/questions/18923930/sending-data-to-servicestack-restful-service-getting-access-is-denied/18927067#18927067) – stefan2410 Oct 08 '13 at 19:24
  • After dicking around with this for way too long stefan2410--that did it for me. I simply needed to add the CorsFeature plugin in my appHost.Configure method, change my routes to "GET,OPTIONS" & "POST,OPTIONS", then create a "public void Options(Requestar request) {}" to get this working. A bit of a nightmare but that's what you get for not paying mythz for the (I'm sure amazing) v4 :). – cjones26 Mar 17 '15 at 21:01

3 Answers3

7

The PreRequestFilters is only fired for valid routes which doesn't exclude OPTIONS (e.g. by leaving Verbs=null and allow it to handle all Verbs instead - inc. OPTIONS).

To be able to handle all OPTIONS requests (i.e. even for non-matching routes) you would need to handle the Request at the start of the Request pipeline (i.e. before Routes are matched) with Config.RawHttpHandlers. This is done in the CorsFeature for you in the next major (v4) release of ServiceStack with:

//Handles Request and closes Response after emitting global HTTP Headers
var emitGlobalHeadersHandler = new CustomActionHandler(
    (httpReq, httpRes) => httpRes.EndRequest());

appHost.RawHttpHandlers.Add(httpReq =>
    httpReq.HttpMethod == HttpMethods.Options
        ? emitGlobalHeadersHandler
        : null); 

CustomActionHandler doesn't exist in v3, but it's easily created with:

public class CustomActionHandler : IServiceStackHttpHandler, IHttpHandler 
{
    public Action<IHttpRequest, IHttpResponse> Action { get; set; }

    public CustomActionHandler(Action<IHttpRequest, IHttpResponse> action)
    {
        if (action == null)
            throw new Exception("Action was not supplied to ActionHandler");

        Action = action;
    }

    public void ProcessRequest(IHttpRequest httpReq, IHttpResponse httpRes, string operationName)
    {            
        Action(httpReq, httpRes);
    }

    public void ProcessRequest(HttpContext context)
    {
        ProcessRequest(context.Request.ToRequest(GetType().Name), 
            context.Response.ToResponse(),
            GetType().Name);
    }

    public bool IsReusable
    {
        get { return false; }
    }
}

Using Fallback handler

Another way to match all Routes is to specify a FallbackRoute, e.g to handle all routes you can add a wildcard to the Fallback route with:

[FallbackRoute("/{Path*}")]
public class Fallback
{
    public string Path { get; set; }
}

But as it matches all un-handled routes it no longer gives 404 for non-matching requests since all un-matched routes are now matched. But you can easily handle it manually with:

public class FallbackService : Service
{
    public object Any(Fallback request)
    {
        if (base.Request.HttpMethod == "OPTIONS")
            return null;

        throw HttpError.NotFound("{0} was not found".Fmt(request.Path));
    }
}
Scott
  • 21,211
  • 8
  • 65
  • 72
mythz
  • 141,670
  • 29
  • 246
  • 390
  • I've [implemented the first solution](http://pastebin.com/FhgaXf12) using v3 but it isn't working. EndRequest is called and global headers are added, but the handler returns an empty HTTP response. Fiddler says: "ReadResponse() failed: The server did not return a response for this request.Server returned 0 bytes." Any idea? – ygormutti Jan 10 '14 at 19:53
  • Commenting the EndRequest call and calling ApplyGlobalResponseHeaders instead works. – ygormutti Jan 10 '14 at 20:36
2

You don't have to add the OPTIONS verb to all routes. Instead you can do the following:

Just put this route on your Question class:

[Route("/question/{*}", Verbs = "OPTIONS")]
public class Question
{
}

Then add this to your question services class:

public void Options(Question question)
{
}

Now any route starting with /question/ will support the OPTIONS verb.

You might want to restrict this if you're going to have subroutes like /question/something/ though.

Sandor Drieënhuizen
  • 6,310
  • 5
  • 37
  • 80
0

Following steps worked for me within ServiceStackV3.

1. Added a new class CustomActionHandler

using ServiceStack.ServiceHost;
using ServiceStack.WebHost.Endpoints.Extensions;
using ServiceStack.WebHost.Endpoints.Support;

public class CustomActionHandler : IServiceStackHttpHandler, IHttpHandler 
{
    public Action<IHttpRequest, IHttpResponse> Action { get; set; }

    public CustomActionHandler(Action<IHttpRequest, IHttpResponse> action)
    {
        if (action == null)
            throw new Exception("Action was not supplied to ActionHandler");

        Action = action;
    }

    public void ProcessRequest(IHttpRequest httpReq, IHttpResponse httpRes, string operationName)
    {            
        Action(httpReq, httpRes);
    }

    public void ProcessRequest(HttpContext context)
    {
        ProcessRequest(context.Request.ToRequest(GetType().Name), 
            context.Response.ToResponse(),
            GetType().Name);
    }

    public bool IsReusable
    {
        get { return false; }
    }
}

2. Add the CustomHandler within AppHostBase.Config.RawHttpHandlers collection (this statements can be written inside the Configure(Container container) method).

// Handles Request and closes Response after emitting global HTTP Headers 
var emitGlobalHeadersHandler = new CustomActionHandler((httpReq, httpRes) => httpRes.EndRequest());
Config.RawHttpHandlers.Add(httpReq => httpReq.HttpMethod == HttpMethods.Options ? emitGlobalHeadersHandler : null);
Max
  • 1,810
  • 3
  • 26
  • 37
Abdul
  • 11
  • 1