4

I have an asp.net core web api, which I can start from the command line (on linux hosts)

dotnet MyWebApi.dll

This will start some webserver that listens on localhost:5000 (per default). Let's assume I have a ValuesController that listens to path /values/. Thus I can get my values from http://localhost:5000/values/, all good.

What I need now is to add a prefix api for all the local paths in the api, so the values controller would respond on: localhost:5000/api/values.

I can't find an elegant way to do this. Obviously I could change it in the controller's route configuration - but that's very redundant and requires a lot of changes, as we have many controllers, and we use [Route(..)] attributes on the controllers themselves.

I've seen the applicationUrl property in the launchsettings.json file in my webapi project. Apparently it contains the configuration used for the Visual Studio debugging/launching. Adding a prefix there (through project properties > debug) works fine and does exactly what I need, but that only works for starting it from VisualStudio (i.e. dotnet run). In the production environment I only have the dotnet publish artifacts available - the launchsettings.json file is not copied over there and there seems no way to provide such a file to the dotnet [assembly] launch command (like with dotnet run).


A bit of background for why I need this: The pattern of having the api controllers listen at / comes from IIS, because there it's common to add apps in subfolders of sites, which handles the path prefixing part. I now deploy my app with docker and the api has its own container, where it's started with the above command - thus the prefix is missing and I need to add it back somehow.

Efrain
  • 3,248
  • 4
  • 34
  • 61
  • Isn't `/api/` the *default* route prefix for Web API? What does your routing code look like? – Panagiotis Kanavos Jul 12 '18 at 10:54
  • The attributes are on the class `[Route("values")] public class ValuesController { ... }` and on the method `[HttpGet]`, `[HttpPost]` etc. – Efrain Jul 12 '18 at 11:00
  • Which someone had to modify and *remove* the `api` prefix that is added by default. By default it's there, in fact the text should be `[Route("api/[controller]")]`. In any case, this is 100% about routing. You can specify routes with attributes, during configuration or a mix. Using attributes is the easiest way unless you decide to *change* all routes after creating a lot of controllers. – Panagiotis Kanavos Jul 12 '18 at 11:05
  • Routing is described in [Routing to controller actions in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/routing?view=aspnetcore-2.1). If you're just starting a new project though, why not simply create a *new* one with `dotnet new webapi` that will have the default routes? – Panagiotis Kanavos Jul 12 '18 at 11:08
  • You should probably check [Areas](https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/areas?view=aspnetcore-2.1) as well. It's a way to define route "namespaces" that will map only to controllers of a specific area. For example the routes for the `Blog` area would only look for controllers that have the `[Area("Blog")]` attribute. You may be able to just add the `{area:exists}/{controller=Home}/{action=Index}/{id?}"` route and add the `Area` attribute to any controller you want without explicitly specifying all route areas – Panagiotis Kanavos Jul 12 '18 at 11:12
  • I don't fully understand the interplay between the .MapRoute(...) method and using attributes. The attributes seem to overrule the MapRoute setup. In any case, I think I have a solution (which I will test and post as answer if successful). Thanks for your support! – Efrain Jul 12 '18 at 11:22

1 Answers1

8

I found a solution that does the trick, in Startup.cs I added UseBasePath:

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        // .. 

        app.UsePathBase(new PathString("/api"));
        app.UseMvc();
    }

This allows getting the values from localhost:5000/api/values and localhost:5000/values. I don't really need or like this ambiguity, but it solves my problem for now.

Efrain
  • 3,248
  • 4
  • 34
  • 61
  • Instead of covering things app why not *fix* them? Why was `api/` removed from the code in the first place? Why not put it back? After all, it's just a File Replace for `[Route("` with `[Route("api/`. Or better yet, a regex replace that would remove *all* hard-coded `Route` attributes and replace them with `"api/[controller]"` – Panagiotis Kanavos Jul 12 '18 at 11:55
  • 1
    Because our API was previously running on IIS, as part of a site consisting of a SinglePageApp + API. The SPA was in main site and the API was added underneath as an "Application" with alias `/api`. So with the original (default template) prefix, the api calls had to be `xyz.com/api/api/values` ... but now we switched to docker, where the api is really standalone, but routing-wise it makes sense to expose it to the outside world in the same, classic 'IIS' way. – Efrain Jul 12 '18 at 12:14
  • These aren't different ways. It's the exact same routing. It shouldn't have been hard-coded in the first place, otherwise a simple change to `MapRoute` or `MapAreaRoute` would be enough. Right now there are many issues - what happens if you want to change a name? You'll have to change both the controller's name *and* the `Route` attribute, or juggle different names – Panagiotis Kanavos Jul 12 '18 at 12:15
  • Instead of `UsePathBase` I'd try using `MapRoute` first. Worst case, both routes would apply - both `api/Values` and `Values`, which is what `UsePathBase` does now. The advantage is that now you can start fixing individual controllers – Panagiotis Kanavos Jul 12 '18 at 12:18
  • Not that `UsePathBase` is wrong - [this Github issue discussion](https://github.com/aspnet/HttpAbstractions/issues/893) explains the motivation behind it and why it doesn't disable the root - it would play havoc with middleware classes. It's meant to handle deployment issues, just like the one you have. – Panagiotis Kanavos Jul 12 '18 at 12:21
  • Ah.. I see, your main issue is that we're not using the default `{controller}/{action}/{id?}` routing, right? - Well... I respect that opinion, but that's just a different topic and I think we have commented off-topic enough already. ;-) Thanks in for your input in any case, I will consider reworking the routing. – Efrain Jul 12 '18 at 12:28
  • 1
    You rarely need to do so. Ideally, the Kestrel server runs behind a reverse proxy server, where you can easily modify the URL. Messing up your code is pointless, unless you really want to deploy the Kestrel server alone. – Lex Li Jul 12 '18 at 15:56
  • @Lex Li: Yes, that's exactly the setup we have, and for most parts that works perfectly fine. The only problem I had was that _swagger_ didn't work right anymore - as it was not aware of the upstream request-rewriting done by nginx, it declared the endpoints without the path prefix. – Efrain Jul 13 '18 at 09:53
  • Thanks, I have the same problem with our API, they put the api server behind a proxy with a prefix, and now the swagger generated curl requests via the UI no longer work because they do not add the prefix. – Talnaci Sergiu Vlad Sep 04 '19 at 09:22