3

I have an API controller hosted in an area. However, the routing doesn't seem to be working as my ajax calls keep returning 404's when trying to hit the controller actions. Breakpoints in the controller constructor are never hit.

[Area("WorldBuilder")]
[Route("api/[controller]")]
[ApiController]
public class WorldApiController : ControllerBase
{
    IWorldService _worldService;
    IUserRepository _userRepository;

    public WorldApiController(IWorldService worldService, IUserRepository userRepository)
    {
        _worldService = worldService;
        _userRepository = userRepository;
    }

    [HttpGet]
    public ActionResult<WorldIndexViewModel> RegionSetSearch()
    {
        string searchTerm = null;
        var userId = User.GetUserId();
        WorldIndexViewModel model = new WorldIndexViewModel();
        IEnumerable<UserModel> users = _userRepository.GetUsers();
        UserModel defaultUser = new UserModel(new Microsoft.AspNetCore.Identity.IdentityUser("UNKNOWN"), new List<Claim>());
        model.OwnedRegionSets = _worldService.GetOwnedRegionSets(userId, searchTerm);
        var editableRegionSets = _worldService.GetEditableRegionSets(userId, searchTerm);
        if (editableRegionSets != null)
        {
            model.EditableRegionSets = editableRegionSets.GroupBy(rs =>
                (users.FirstOrDefault(u => u.IdentityUser.Id == rs.OwnerId) ?? defaultUser)
                    .IdentityUser.UserName)
            .Select(g => new RegionSetCollectionModel(g)).ToList();
        }
        var viewableRegionSets = _worldService.GetViewableRegionSets(userId, searchTerm);
        if (viewableRegionSets != null)
        {
            model.ViewableRegionSets = viewableRegionSets.Where(vrs => vrs.OwnerId != userId).GroupBy(rs =>
                    (users.FirstOrDefault(u => u.IdentityUser.Id == rs.OwnerId) ?? defaultUser)
                        .IdentityUser.UserName)
                .Select(g => new RegionSetCollectionModel(g)).ToList();
        }
        return model;
    }
}

And my startup.cs file:

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {


        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseCookiePolicy();

        app.UseAuthentication();

        app.UseMvc(routes =>
        {

            routes.MapRoute(name: "areaRoute",
              template: "{area}/{controller=Home}/{action=Index}/{id?}");

            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });

        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            app.UseDatabaseErrorPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
            app.UseHsts();
        }
    }
}

I have tried the following ajax addresses:

   localhost:44344/api/WorldApi/RegionSetSearch
   localhost:44344/WorldBuilder/api/WorldApi/RegionSetSearch
   localhost:44344/api/WorldBuilder/WorldApi/RegionSetSearch
   localhost:44344/WorldBuilder/WorldApi/RegionSetSerarch

For the last address I tried, I removed the "api/" from the Route data annotation on the controller.

I'm not sure what I'm doing wrong here. I'm following all of the examples I've found online.

Beengie
  • 1,588
  • 4
  • 18
  • 36
charles082986
  • 141
  • 1
  • 3
  • 12

1 Answers1

11

There are two routing type in MVC, conventions routing which is for mvc and route attribute routing which is for web api.

For area which is configured in conventions routings for MVC should not be combine with route attribute. Route attribute will override the default convention routing.

If you prefer attribute routing, you could

[Route("WorldBuilder/api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // GET api/values
    [HttpGet("RegionSetSearch")]
    public ActionResult<IEnumerable<string>> RegionSetSearch()
    {
        return new string[] { "value1", "value2" };
    }        
}

Pay attention to [HttpGet("RegionSetSearch")] which define the action for RegionSetSearch and append a placeholder in the url.

The Reuqest is https://localhost:44389/worldbuilder/api/values/RegionSetSearch

If you prefer conventions routing, you could remove Route and ApiController like

[Area("WorldBuilder")]
public class ValuesController : ControllerBase
{
    // GET api/values
    [HttpGet]
    public ActionResult<IEnumerable<string>> RegionSetSearch()
    {
        return new string[] { "value1", "value2" };
    }        
}

With this way, you need to change the UseMvc like

app.UseMvc(routes => {
    routes.MapRoute("areaRoute", "{area:exists}/api/{controller}/{action}/{id?}");
});

And the request is https://localhost:44389/worldbuilder/api/values/RegionSetSearch

Beengie
  • 1,588
  • 4
  • 18
  • 36
Edward
  • 28,296
  • 11
  • 76
  • 121
  • Removing `ApiController` is done so that the controller may be involved in conventional routing. Note that removing `ApiController` results in losing some other behavior for the controller: Automatic HTTP 400 responses, Binding source parameter inference, Multipart/form-data request inference, and Problem details for error status codes. https://learn.microsoft.com/en-us/aspnet/core/web-api/?view=aspnetcore-3.1#apicontroller-attribute – Benjamin Brandt Feb 19 '20 at 19:21
  • How would like the Controllers and Models folder structure in case of using attr routing and having areas like this? – Zeshan Munir Jul 09 '20 at 21:59