Routes with different controllers but same action name fails to produce wanted urls

前提是你 提交于 2020-12-02 06:03:52

问题


I am trying to set up a API for my MVC web app that will have a lot of routes but much of the same part for each one. Basically a CRUD for each area. I am also setting it up to be version-able. I have set up two controllers each with a simple action to get started and receive a conflict right off the bat. The error I get is

I am after these urls

  • https://foo.bar/aim/v1/contacts/delete/11111
  • https://foo.bar/aim/v1/locations/delete/11111
  • and so on

The MVC will let you have a

  • https://foo.bar/home/index/
  • https://foo.bar/contacts/index/
  • and so on

So I am looking for a away around needlessly naming things like contacts_delete and locations_delete producing URLs like

  • https://foo.bar/aim/v1/contacts/contacts_delete/11111
  • https://foo.bar/aim/v1/locations/locations_delete/11111
  • and so on

    (which is in the observation of the root issue provided in the comments and below anwsers like Route names must be unique. You cannot have two different routes named delete. Use deleteLocation and deleteContact instead.)

I may as well just do https://foo.bar/aim/v1/contacts_delete/11111 but that seems so senseless to me. If the MVC can do it, i have to believe that there is a way to make this happen.

The error I get is:

Attribute routes with the same name 'delete' must have the same template:

Action: 'rest.fais.foo.edu.Controllers.aimContactsController.delete (rest.fais.foo.edu)' - Template: 'aim/v1/contacts/delete/{id}'

Action: 'rest.fais.foo.edu.Controllers.aimLocationsController.delete (rest.fais.foo.edu)' - Template: 'aim/v1/locations/delete/{id}'

For the controllers I have set up

[EnableCors("SubDomains")]
[ResponseCache(NoStore = true, Duration = 0)]
[Produces("application/json")]
[Route("aim/v1/contacts/[action]")]
[ProducesResponseType(typeof(errorJson), 500)]
public class aimContactsController : Controller
{
    private readonly IHostingEnvironment _appEnvironment;
    private readonly AimDbContext _aim_context;
    private readonly UserManager<ApplicationUser> _userManager;

    public aimContactsController(IHostingEnvironment appEnvironment,
        AimDbContext aim_context,
        UserManager<ApplicationUser> userManager)
    {
        _appEnvironment = appEnvironment;
        _userManager = userManager;
        _aim_context = aim_context;
    }



    [HttpPost("{id}", Name = "delete")]
    public IActionResult delete(string id)
    {

        return Json(new
        {
            results = "deleted"
        });
    }

}


[EnableCors("SubDomains")]
[ResponseCache(NoStore = true, Duration = 0)]
[Produces("application/json")]
[Route("aim/v1/locations/[action]")]
[ProducesResponseType(typeof(errorJson), 500)]
public class aimLocationsController : Controller
{
    private readonly IHostingEnvironment _appEnvironment;
    private readonly AimDbContext _aim_context;
    private readonly UserManager<ApplicationUser> _userManager;

    public aimLocationsController(IHostingEnvironment appEnvironment,
        AimDbContext aim_context,
        UserManager<ApplicationUser> userManager)
    {
        _appEnvironment = appEnvironment;
        _userManager = userManager;
        _aim_context = aim_context;
    }



    [HttpPost("{id}", Name = "delete")]
    public IActionResult delete(string id)
    {

        return Json(new
        {
            results = "deleted"
        });
    }

}

For the Startup.cs I have

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole(Configuration.GetSection("Logging"));
        loggerFactory.AddDebug();
        //RolesData.SeedRoles(app.ApplicationServices).Wait();

        app.UseApplicationInsightsRequestTelemetry();

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

        app.UseIdentity();
        app.UseDefaultFiles();
        app.UseStaticFiles();

        //app.UseResponseCompression();

        // Add external authentication middleware below. To configure them please see http://go.microsoft.com/fwlink/?LinkID=532715
        app.UseSession();

        // custom Authentication Middleware
        app.UseWhen(x => (x.Request.Path.StartsWithSegments("/aim_write", StringComparison.OrdinalIgnoreCase) || x.Request.Path.StartsWithSegments("/rms", StringComparison.OrdinalIgnoreCase)),
        builder =>
        {
            builder.UseMiddleware<AuthenticationMiddleware>();
        });

        // Enable middleware to serve generated Swagger as a JSON endpoint.
        app.UseSwagger();

        // Enable middleware to serve swagger-ui (HTML, JS, CSS etc.), specifying the Swagger JSON endpoint.
        app.UseSwaggerUI(c =>
        {
            c.RoutePrefix = "docs";
            //c.SwaggerEndpoint("/docs/v1/wsu_restful.json", "v1.0.0");swagger
            c.SwaggerEndpoint("/swagger/v1/swagger.json", "v1.0.0");
        });


        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "javascript",
                template: "javascript/{action}.js",
                defaults: new { controller = "mainline" });
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
            routes.MapRoute(
                name: "AIMApi",
                template: "aim/v1/{action}/{id?}",
                defaults: new { controller = "aim" });
            routes.MapRoute(
                name: "AIMContactsApi",
                template: "aim/v1/contacts/{action}/{id?}",
                defaults: new { controller = "aimContactsController" }
            );
            routes.MapRoute(
                name: "AIMLocationsApi",
                template: "aim/v1/locations/{action}/{id?}",
                defaults: new { controller = "aimLocationsController" }
            );
            routes.MapRoute(
                name: "RMSApi",
                template: "{controller=rms}/v1/{action}/{id?}");
        });
    }
}

What I can't seem to google out the answer how to work around this. I want to fix the immediate issue, but any suggests are welcomed.


回答1:


Both your routes are named the same, this cannot work in ASP.NET Core MVC.

I'm not talking about the methods naming, but about routes naming. You called both your routes with the same identifier Name = "delete" inside the HttpPost attribute. Route names in MVC uniquely identifies a route template.

From what I can see you do not really need to identify your routes, but only to distinguish different URIs. For this reason you may freely remove the Name property of HttpPost attribute on your action methods. This should be enough for ASP.NET Core router to match your action methods.

If you, instead, what to revert using only attribute routing you better change your controller to the following:

// other code omitted for clarity
[Route("aim/v1/contacts/")]
public class aimContactsController : Controller
{
    [HttpPost("delete/{id}")]
    public IActionResult delete(string id)
    {
        // omitted ...
    }
}



回答2:


Another possible solution is:

// other solution
[Route("aim/v1/contacts/[Action]")]
public class aimContactsController : Controller
{
    [HttpPost("{id}")]
    public IActionResult delete(string id)
    {
        // omitted ...
    }
}



回答3:


Example with Action name dynamically:

[Route("api/{lang}/[controller]/[Action]")]
    [ApiController]
    public class SpatiaController : ControllerBase
    {
        private readonly ISpatialService _service;
        private readonly ILogger<SpatialController> _logger;

        public SpatialUnitsIGMEController(ILogger<SpatialController> logger, ISpatialService service) 
        {
            _service = service;
            _logger = logger;
        }


        [HttpGet]
        public async Task<ActionResult<IQueryable<ItemDto>>> Get50k(string lang)
        {
            var result = await _service.GetAll50k(lang);
            return Ok(result);
        }

        [HttpGet("{name}")]
        public async Task<ActionResult<ItemDto>> Get50k(string lang, string name)
        {
            var result = await _service.Get50k(lang, name);
            return Ok(result);
        }
    }

To call these endpoints the following calls would be used:

https://example.domain.com/api/en/spatial/get50k --> we get all the data in English

https://example.domain.com/api/en/spatial/get50k/madrid --> we obtain the data of madrid in english

https://example.domain.com/api/es/spatial/get50k/madrid --> we obtain the data of madrid in spanish

(using the action and language in the route)



来源:https://stackoverflow.com/questions/45290373/routes-with-different-controllers-but-same-action-name-fails-to-produce-wanted-u

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!