Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Route Name for HttpGet attribute Name for base generic controller class in asp.net core 2

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.

like image 354
HExit Avatar asked Feb 06 '18 01:02

HExit


People also ask

What is attribute routing in ASP.NET Core?

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.

What is difference between attribute and conventional routing?

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.

How do I set the default route in .NET Core Web API?

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.


1 Answers

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.

like image 189
CodeFuller Avatar answered Sep 19 '22 16:09

CodeFuller