How to prefix base route for all service service routes

Hello,

I using Servicestack self hosting and I’m wanting to prefix ALL routes with “api/” base path. I can modify the routes defined by route attributes, but all the plugin routes (auth service, register service, apikeys, etc remain the same).

This answer Can not call /auth after adding "api" prefix indicated a there’s a way to update the auth service routes, but I’m looking to change all services to be on the same base path.

I thought setting the HandlerFactoryPath = “api” would do this, but it has no affect on the defined rest paths.

Thanks,
Brian

The HandlerFactoryPath is for ASP.NET, for self-hosts you can just start listening on a custom path, e.g:

new AppHost().Init().Start(“http://localhost:8080/api/”);

Thanks for the quick response!

I tried this and it kill the handling of razor pages. I’m currently using razor to serve up some basic web pages and view and will eventually be using Aurelia. I want to still be able to use the root of the domain to serve up those files. I’m trying to get a clean path for all the api routes and also reflect that in the docs (ie. metadata route paths).

Is there a way to get to all the service routes before their routes registered during startup?

You can dynamically modify all Services that were defined with [Route] Attributes with:

public class AppHost : AppHostBase
{
    //...
    public override RouteAttribute[] GetRouteAttributes(Type requestType)
    {
        var routes = base.GetRouteAttributes(requestType);
        routes.Each(x => x.Path = "/api" + x.Path);
        return routes;
    }
}

But the built-in Auth Services aren’t registered with Attributes, but they can be changed with something like:

Plugins.Add(new AuthFeature {
  //...
    ServiceRoutes = new Dictionary<Type, string[]> {
      {typeof(AuthenticateService), new[] { "/api/auth", "/api/auth/{provider}" }},
    }
}

Looks like that would only work for AuthFeature. After digging around in the code I found this override that gets me 99% there along with the GetRouteAttribute override. Every default plugin path except /metadata, /operations/metadata got changed correctly. However, swagger is lost now that /resources path has changed to /api/resources.

    /// <summary>
    /// Rewrite all atRestPaths to use base api route.  (This gets most of the built-in service stack routes)
    /// </summary>
    /// <param name="serviceType"></param>
    /// <param name="atRestPaths"></param>
    public override void RegisterService(Type serviceType, params string[] atRestPaths)
    {
        var newPaths = atRestPaths;
        if (!IsNullOrEmpty(HostSettings.BaseApiRoute))
            newPaths = atRestPaths.Select(s => "/" + HostSettings.BaseApiRoute + s).ToArray();

        base.RegisterService(serviceType, newPaths);
    }

    /// <summary>
    /// Rewrite all rest paths coming from routeAttributes. (Does not affect service stack built-in routes)
    /// </summary>
    public override RouteAttribute[] GetRouteAttributes(System.Type requestType)
    {
        if (IsNullOrEmpty(HostSettings.BaseApiRoute)) return base.GetRouteAttributes(requestType);

        var routes = base.GetRouteAttributes(requestType);
        routes.Each(x => x.Path = "/" + HostSettings.BaseApiRoute + x.Path);
        return routes;
    }

Any other side-effects that you can think of doing it this way?

Ok, so after looking around how the Metadata links were being created, the HandlerFactoryPath is being used in parts of the data and links and other parts it isn’t.

So I circled back to my original question and removed the overrides and set the HandlerFactoryPath = “api” again and tested the actual rest paths and this actually works on all routes (built-in and route attributes). However the operations/metadata page and swagger pages are “showing” the paths without the “/api” prefix on all the routes so they aren’t using the HandlerFactoryPath like the other pages. However, when you “Try it Out” in swagger, the correct routes are used. Also, postman data export doesn’t include the /api paths.



The HandlerFactoryPath solution also doesn’t “move” the routes, it creates new ones on the “/api” path. Default routes can still be used (e.g. /auth/logout and /api/auth/logout both work).

Those are the pre-defined Routes which follows an explicit and predictable format which doesn’t use any Custom Route.

Please note you shouldn’t use HandlerFactoryPath for self-hosts, it’s for ASP.NET Hosts only.

Just noticed your last comment about the handler path. Is there some reason why I shouldn’t use that, because it looks like that is the easiest way to shift all the routes without breaking functionality on other plugins as I found on this earlier reply How to prefix base route for all service service routes.

From what I’ve tested, this works even though the original default routes are still on the root path. Still doesn’t fix my original intent on moving all routes to the /api path while preserving razor pages on the web root.

Because the HandlerFactoryPath is only meant for ASP.NET hosts to be able to properly calculate the BaseUrl for when ServiceStack is hosted at a /custom path in an ASP.NET Web App. ServiceStack’s hosted from the BaseUrl which is its root for everything, services, razor views, static files, etc. ServiceStack doesn’t see any request that comes before the BaseUrl so it can’t be configured to listen on http://localhost:8080/api/ and serve any requests from http://localhost:8080/.

ServiceStack also doesn’t distinguish between a Service that returns JSON or when it’s used to populate the View Model of a Razor View when requested by a browser, they’re just different JSON or HTML formats of a Service Response.

If you want to ServiceStack to serve requests from both http://localhost:8080/ and http://localhost:8080/api/ it needs to be hosted from http://localhost:8080/ then use Custom Routes to specify which Services should be prefixed with /api.

If you also want to remap your Assign Roles Services you’ll need to specify the new routes for them as well, e.g:

Plugins.Add(new AuthFeature {
  //...
    ServiceRoutes = new Dictionary<Type, string[]> {
      {typeof(AuthenticateService), new[] { "/api/auth", "/api/auth/{provider}" }},
      {typeof(AssignRolesService), new[] { "/api/assignroles" }},
      {typeof(UnAssignRolesService), new[] { "/api/unassignroles" }},
    }
}

You can’t change the built-in Pre-defined Routes because they need to be predictable from external clients from the BaseUrl independent of any Server configuration.

Personally I wouldn’t prefix and segregate my JSON API’s with /api, places I’ve seen this convention used is due to an artificial limitation imposed by Web API and MVC or ServiceStack and MVC being different implementations where hosting at different paths is the only way to make both work within the same Web Application. In a REST-ful Service you’d typically wouldn’t have different routes for /customers to return a Web Page for Browsers and a different Route to return a JSON Response to JSON clients, the client would just use the Accept Header to specify which format it wants returned.

But if for some reason I had to segregate Services between / and /api, I would be manually adding the prefix in each Route definition e.g, [Route("/api/customers")] to make it clear which explicit route it’s available from. You can use GetRouteAttributes() to save some typing, but the external route mapping is the declarative endpoint for your Service and not something I’d want to save typing and hide behind a Startup script.

Maybe I’m trying to do to much with Service Stack self host. My intent is to keep a clear separation between the web service routes and routes that may be handled by the host for web pages, razor pages, or SPA the same way it is separated with ASP.net hosts. If the self host’s only responsibility was web services this wouldn’t be as much of an issue.

I guess my point is if the clients have to know the base api Url (for web services), shouldn’t the server (whether it is asp.net host or self host) also enforce the same base route (for web services). Relative route definitions and pre-defined routes wouldn’t need to change, but if you wanted your base route to be “/api/v2” or “/my_nondefault_api_path” you could enforce this at the host without changing your static route definitions in code.

1 Like

Is it possible to limit the verbs for the ServiceRoutes as well? I know I can adjust attributes via the .AddAttributes but it seems this is not working. If I only want the POST verb for all authentication calls, how do I define this together with the ServiceRoutes?

No ServiceRoutes doesn’t support specifying verbs. You could use a Global Request Filter to reject requests with Authenticate DTO and non POST verbs.