Attribute Routing ASP Core 3.0

40 Views Asked by At

How do I specify routes to the following API endpoint using attribute routing?

The product object returned has a few attributes along with a Store and Manufacturer field that contain either null or a Store and Manufacturer object respectively.


    [Route("api/[controller]")]
    [ApiController]
    public class ProductsController : ControllerBase {


        [HttpGet ("")]
        public async Task<ActionResult<IEnumerable<Product>>> Get(
            bool stores = false, int storeId = 0,
            bool manufacturers = false, int manufacturerId = 0
        ) {

            // Default - returns all Products 
            // stores = true will set Product.Store = to the store object
            // storeId = 1 will filter Products to those in Store 1
            // manufacturer = true will st Product.Manufacturer to a manufacturer object
            // manufacturerId = 1 will filter Product to those produced by manufacturer 1

            var products = await _ctx.GetProducts(
                stores, storeId, 
                manufacturers, manufacturerId
            );
            return products.ToList();
        }

I would like to have a few readable routes to access and set the parameters appropriately.

  • [HttpGet ("")]

    • /api/Products to return objects with Store and Manufacturer = null. This is desirable.
    • The ("") isn't needed when it is alone but necessary if I add other routes.
  • [Route("{stores=false}/{storeId=0})]

    • /api/Products/true returns Product objects with Store filled and Manufacturer = null. This is good.
    • /api/Products/true/1 filters on Store 1 which is also good.
    • But it is a stupid url (true? - products, stores, or manufacturers?)
  • [Route("Stores/{storeId?})]

    • /api/Products/Stores/1 leaves stores = false, though filters on Store 1 but does not include the stores object. "Stores" is not in the route data. It is in the Request.Path but I don't want to go there.
    • /api/Products/Stores?storeId=1 doesn't work. Request.QueryString.Value = ?storeId=1 but that doeesn't bind to my storeId argument.

I could continue describing my other experiments but suffice it to say, none gave me the results I'm looking for. I think I'm misunderstanding attribute routing but maybe it wasn't designed to do what I'm trying to do.

I guess what I'd like to see is either modifying a single url with a query string like,

  • /api/Products?stores=true&manufacturerId=1to get products with store info that are produced by manufacturer 1, or
  • /api/Products?storeId=1,stores=true,manufacturers=true to get full details about the products in store 1

Alternatively, it would be ok to have

  • /api/Products/Stores/1 to get Products with store info for products in store 1
  • /api/Products/Stores/Manufacturers/1 to get Projects with store and manufacturer info for those produced by manufacturer 1

Actually, I'm open to any URL schema that is readable.

Thanks!

1

There are 1 best solutions below

2
Kos On

I would redesign the your API Controller a little bit. Would be good if you give some meaningful names to your actions instead of Get().

I also would create a service which will have the logic to provide the requested product and replace the _ctx

    // Default - returns all Products 
    [HttpGet ("")]
    public async Task<ActionResult<IEnumerable<Product>>> GetAllProducts()
    {
        var products = await _ctx.GetAllProducts();
        return products.ToList();
    }

    // stores = true will set Product.Store = to the store object
    // storeId = 1 will filter Products to those in Store 1
    [HttpGet ("Stores/{storeId?}")]
    public async Task<ActionResult<IEnumerable<Product>>> GetProductsByStore(int? storeId)
    {
        if (storeId.HasValue)
        {
            // get products by store id
            products = await _ctx.GetProductsByStoreId(storeId);
        }
        else
        {
             products = await _ctx.GetProductsByCurrentStore(); // just a suggestion I 
         have no idea what you mean by saying "..set Product.Store = to the store object"
        }

        return products.ToList();
    }

    // manufacturer = true will st Product.Manufacturer to a manufacturer object
    // manufacturerId = 1 will filter Product to those produced by manufacturer 1
    [HttpGet ("Manufacturers/{manufacturerId?}")]
    public async Task<ActionResult<IEnumerable<Product>>> GetProductsByManufacturer(int? manufacturerId)
    {
        if (manufacturerId.HasValue)
        {
            products = await _ctx.GetProductsByManufacturerId(manufacturerId);
        }
        else
        {
            products = await _ctx.GetProductsByManufacturer();
        }

        return products.ToList();
    }