asp.net-core-mvcasp.net-core-2.0asp.net-mvc-areas

Asp.Net Core Area routing to Api Controller not working


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.


Solution

  • 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