简体   繁体   中英

Route attribute Name property

I have ProductsController and OwnersController:

public class ProductsController : ApiController
{

    //constructor is here

    // GET /api/products
    public IHttpActionResult GetProducts()
    {
        return Ok(new ApiResponse());
    }

    // GET /api/products/{productCode}
    [HttpGet, Route("api/products/{productCode}")]
    public IHttpActionResult GetProductByCode(string productCode)
    {
         return Ok(new ApiResponse());
    }

    // POST /api/products
    public IHttpActionResult PostProduct(Product product /*my class*/)
    {
        return CreatedAtRoute("DefaultApi", new { id = product.Id }, product);
    }
}

It works perfectly. But now I create second controller and do the same things but I get the error when trying POST method. Another methods work well!

Lets take a look at the code first:

public class OwnersController : ApiController
{
    // constructor

    // GET /api/owners/{label}
    // GET /api/owners/{label}?skip=1&take=4
    [Route("api/owners/{label}")]
    public IHttpActionResult GetOwnersExamples(string label, int skip=0, int take=10)
    {
        return Ok(new ApiResponse());
    }
    // POST /api/owners/{productCode}
    //[HttpPost]
    [Route("api/owners/{productCode}"/*, Name = "CreateOwner"*/)]
    public IHttpActionResult PostOwner(string productCode, Owner owner)
    {
        return CreatedAtRoute("DefaultApi", new { id = Owner.Id }, owner);
    }  
}

Error message:

UrlHelper.Link must not return null


My RouteConfig :

config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional }
);

As I understand problem that CreateAtRoute method must get another RouteName . As you see I can solve the problem by adding Route Name parameter (commented now) and replace "DefaultApi" with "CreateOwner" but it looks like a hack. I believe there is another method to do it avoiding Name Property .

PS Looks like my Web API can see only first controller (ProductsController) - any other methods doesn't work if I delete explicit Route [Route("...")] ...

As I understand problem that CreateAtRoute method must get another RouteName. As you see I can solve the problem by adding Route Name parameter (commented now) and replace "DefaultApi" with "CreateOwner" but it looks like a hack. I believe there is another method to do it avoiding Name Property.

Your understanding is almost correct. However you should specify a name not for the current route, but for the one that points to created resource. CreatedAtRoute fills response Location header that should contain a GET-able URI for newly created resource.

Here is a working sample:

[HttpGet]
[Route("api/owners/{id}", Name = "GetOwnerById")]
public IHttpActionResult GetOwner(int id)
{
    //  Obtain the owner by id here
    return Ok(new ApiResponse());
}

[HttpPost]
[Route("api/owners/{productCode}"/*, Name = "CreateOwner"*/)]
public IHttpActionResult PostOwner(string productCode, Owner owner)
{
    return CreatedAtRoute("GetOwnerById", new { id = owner.Id }, owner);
}

(Note: For get this example working, you should comment GetOwnersExamples action, otherwise multiple action will match your GET request.)

You said that it looks like a hack, but it is not. CreatedAtRoute takes a route name as argument and you should provide it. How otherwise the proper action will be selected and Location header will be built?

I solve the problem using next steps:

  1. Delete all [RoutePrefix] for controller - let them work by default - it works perfectly for simple requests.
  2. IMPORTANT: check all methods for duplicates! Problem was I had 2 methods with routes api/controller/{label} and api/controller/{parameter} - it can't understand which of them to use by default. (Or use explicit uri: api/controller?label=1 )
  3. IMPORTANT: avoid to put into api methods a lot of complex types - create Wrapper for them and put only one parameter!

All this actions let me delete excess attributes and make methods more readable.

Here is the result:

public IHttpActionResult PostOwner(OwnerWrapper modelWrapper)
{
    string productCode = modelWrapper.Product.Code;
    Owner owner = modelWrapper.Owners[0];

    return CreatedAtRoute("DefaultApi", new { id = Owner.Id }, owner);
}

It is just test case, so we can see productCode is never used, but my real realization is more difficult.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM