문제

I am new to web api coming from a WCF background and as prep I watched Shawn Wildermuth's Pluralsight course on the subject before diving in. His course material was designed around more traditional routing. One of the subjects the course dives into is HATEOAS and how easy it is to achieve this with a base api controller and model factory.

One of the first things I hit when implementing against attribute routing was the need for the UrlHelper to have a route name as the first argument of the Link() method, something that was inherited in the conventional routing configured in the WebApiConfig.cs.

I worked around this by decorating one of my controllers route attributes with the Name property and it appears that all methods in that controller have access to the name property regardless of which method I put it on (see code below). While I find this a bit odd, it works. Now that I had HATEOAS implemented, I noticed the URL's it was generating were in the query string format and not "url" formatted (I know the term is wrong but bear with me). Instead of .../api/deliverables/1 I am getting .../api/deliverables?id=1.

This is "ok" but not the desired output. While I still have not figured out how to adjust the formatting the of the return value of the URL, I figured I would test the query string against my controller and found that in the query string format my controller does not work but in the "url" format it does.

I then spent an hour trying to figure out why. I have attempted different decorations (i.e. [FromUri] which from my reading should only be necessary for complex objects which default to the message body) to setting default values, constraints and making it optional (i.e. {id?}).

Below is the code in question, both for the controller, the base api controller and the model factory that makes the HATEOAS implementation possible.

The 3 questions I have are:

1) How to make the controller accept the "id" on the querystring AND in the url format (.../deliverables/1 and .../deliverables?id=1.

2) How to make the Link method of the URL helper return the value in the url format (it is currently returning it as a query string.

3) Proper way to name routes in WebAPI 2. What I am doing (assigning a name to a single method and the others appear to inherit it simply smells and I have to believe this would crumble as I actually start to implement more complex code. Is Shawn's implementation flawed in some way? I like not having to hard code a URL for test/development purposes but maybe UrlHelper is not the best way to achieve this. It seems to carry with it a lot of baggage that may not be necessary.

Controller:

[RoutePrefix("api/deliverables")]
public class DeliverablesController : BaseApiController
{
    private readonly IDeliverableService _deliverableService;
    private readonly IUnitOfWork _unitOfWork;

    public DeliverablesController(IDeliverableService deliverableService, IUnitOfWorkAsync unitOfWork)
    {
        _deliverableService = deliverableService;
        _unitOfWork = unitOfWork;
    }

    [Route("", Name = "Deliverables")]
    public IHttpActionResult Get()
    {
        return Ok(_deliverableService.Get().Select(TheModelFactory.Create));
    }

    [Route("{id}")]
    public IHttpActionResult Get(int id)
    {
        return Ok(TheModelFactory.Create(_deliverableService.Find(id)));
    }

    [Route("")]
    public IHttpActionResult Post([FromBody]DeliverableModel model)
    {
        try
        {
            var entity = TheModelFactory.Parse(model);

            if (entity == null)
            {
                return BadRequest("Could not parse Deliverable entry in body.");
            }

            _deliverableService.Insert(entity);
            _unitOfWork.SaveChanges();

            return Created(Request.RequestUri + "/" + entity.Id.ToString(CultureInfo.InvariantCulture),TheModelFactory.Create(entity));


        }
        catch (Exception exception)
        {
            return BadRequest(exception.Message);
        }
    }
}

Base API Controller:

public abstract class BaseApiController : ApiController
{
    private ModelFactory _modelFactory;

    protected ModelFactory TheModelFactory
    {
        get
        {
            return _modelFactory ?? (_modelFactory = new ModelFactory(Request));
        }
    }
}

Model Factory:

public class ModelFactory
{
    private readonly UrlHelper _urlHelper;

    public ModelFactory(HttpRequestMessage request)
    {
        _urlHelper = new UrlHelper(request);
    }

    public DeliverableModel Create(Deliverable deliverable)
    {
        return new DeliverableModel
               {
                   Url = _urlHelper.Link("deliverables", new { id = deliverable.Id }),
                   Description = deliverable.Description,
                   Name = deliverable.Name,
                   Id = deliverable.Id
               };
    }

    public Deliverable Parse(DeliverableModel model)
    {
        try
        {
            if (string.IsNullOrEmpty(model.Name))
               return null;

            var entity = new Deliverable
                         {
                             Name = model.Name,
                             Description = !string.IsNullOrEmpty(model.Description)
                                 ? model.Description
                                 : string.Empty
                         };

            return entity;
        }
        catch (Exception)
        {
            return null;
        }
    }
}

As a point of clarification, non-attribute (traditional) routing works without an issue for both the URI and query string formats:

config.Routes.MapHttpRoute(
            name: "deliverables",
            routeTemplate: "api/deliverables/{id}",
            defaults: new { controller = "deliverables", id = RouteParameter.Optional }
        );
도움이 되었습니까?

해결책

In my opinion, this is one of the problems with Attributed routing. That's why I use it for exceptional cases only. I use route tables for the majority of routing then drop down into attributed routing for exceptional cases.

To solve this your way, have you thought about multiple routes on the Get(id)? (I don't actually think this would work, but its worth a try).

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top