I have a generic controller, which have several derived controller classes. but I cannot figure out how to handle the HttpGet's route name since it require constant.
[HttpGet("{id}", Name ="should not hard coded here for derived class")]
public virtual async Task<IActionResult> Get(int id)
I need the route name because in my HttpPost function I want to return CreatedAtRoute() which require HttpGet's route name
The route name cannot be hard coded because all the derived class need to have a different route name.
here is the base controller
public abstract class BaseController<TEntity, TContext> : Controller where TEntity : BaseOptionType, new() where TContext : DbContext
{
private readonly IGenericRepository<TEntity, TContext> _repository;
private readonly ILogger<BaseGenericOptionTypesController<TEntity, TContext>> _logger;
public BaseController(IGenericRepository<TEntity, TContext> repository, ILogger<BaseController<TEntity, TContext>> logger)
{
_repository = repository;
_logger = logger;
}
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[HttpGet("{id}", Name = "should not hard code here for derived class")]
public virtual async Task<IActionResult> Get(int id)
{
var optionType = await _repository.FindByIdAsync(id);
if (optionType == null)
{
_logger.LogInformation($"[ID not found]");
return NotFound();
}
return Ok(optionType);
}
}
Here is the Derived Controller
[Route("api/v1/DerivedControllerA")]
public class DerivedControllerA : BaseController<TimeOff, HRContext>
{
public DerivedControllerA(IGenericRepository<TimeOff, HRContext> repository, ILogger<DerivedControllerA> logger)
: base(repository, logger)
{
}
}
Any help would be appreciated, Thank you.
Summary. Attribute routing in ASP.NET Core 3.0 allows us to define specific, customized routes for actions in our systems. Said routes are applied directly at the controller action level, allowing for high cohesion between route and action.
Conventional routing: The route is determined based on conventions that are defined in route templates that, at runtime, will map requests to controllers and actions (methods). Attribute-based routing: The route is determined based on attributes that you set on your controllers and methods.
Right click your web project -> Select Properties -> Select the Debug tab on the left -> Then edit the 'Launch Url' field to set your own default launch url.
I will not argue with NightOwl888 about use of base controllers in MVC. There are pros and cons, and I've dealt with the projects where use of base controllers was justified.
As regards original question, seems like the easiest way to get around this problem is to use CreatedAtAction
instead of CreatedAtRoute
. CreatedAtAction
does not require you to name your routes, you could just use Get
action name from base controller. If CreatedAtAction
is called from DerivedControllerA
, it will produce the URL for Get
action in DerivedControllerA
, and if it's called from DerivedControllerB
, it will produce the URL for Get
action in DerivedControllerB
. So seems like shift to CreatedAtAction
covers your use case pretty well.
Here is a sample call to CreatedAtAction
:
[HttpPost]
public virtual IActionResult Post(/* ... */)
{
// Create and save an instance in repository
// var createdObject = ...;
return CreatedAtAction(nameof(Get), new
{
// Put actual id here
id = 123
}, createdObject);
}
The common mistake is to call overload of CreatedAtAction
with 2 parameters. This version takes created object for response body, not the route values, which often results to No route matches the supplied values
error. If you don't want to return representation of created resource in the response, you could pass null
as 3rd parameter:
return CreatedAtAction(nameof(Get), new
{
// Put actual id here
id = 123
}, null);
If for some reason you want to stick with CreatedAtRoute
call, the only possible solution that comes to my mind is to have distinct action in each derived class which just calls to base method with the actual logic:
[Route("api/v1/DerivedControllerA")]
public class DerivedControllerA : BaseController<TimeOff, HRContext>
{
// ...
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[HttpGet("{id}", Name = "RouteForDerivedControllerA")]
public virtual Task<IActionResult> Get(int id)
{
return base.Get(id);
}
}
public abstract class BaseController<TEntity, TContext> : Controller where TEntity : BaseOptionType, new() where TContext : DbContext
{
// ...
public virtual async Task<IActionResult> Get(int id)
{
// Actual logic goes here
}
}
Such solution however devalues use of BaseController
in fact.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With