I am creating REST Services. Here I want to create a Post service. Data is inserted into a database, but in response I got this error
UrlHelper.Link must not return null..
My Code
[HttpPost]
[Route("api/CourseRegistrations")]
[ResponseType(typeof(CourseRegistration))]
public IHttpActionResult PostCourseRegistration(CourseRegistration courseRegistration)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.CourseRegistrations.Add(courseRegistration);
db.SaveChanges();
return CreatedAtRoute("DefaultApi", new { id = courseRegistration.course_id }, courseRegistration);
}
I assumed you're using default setting inside WebApiConfig class in App_Start folder like this:
public static class WebApiConfig {
public static void Register(HttpConfiguration config) {
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
Thus it is necessary to provide controller name parameter in CreatedAtRoute method, as in example below:
return CreatedAtRoute("DefaultApi", new { controller = "ApiControllerName", id = courseRegistration.course_id }, courseRegistration);
However, since RouteAttribute is present, it indicates that attribute routing is enabled, then you should provide route name for GET route with proper parameter name and then set POST route as shown in example below:
// GET method
[HttpGet]
[Route("api/CourseRegistrations/{id}", Name = "GetCourseRegistrationById")]
[ResponseType(typeof(CourseRegistration))]
public IHttpActionResult GetCourseRegistration(int id)
{
// do something
}
// POST method
[HttpPost]
[Route("api/CourseRegistrations")]
public IHttpActionResult PostCourseRegistration(CourseRegistration courseRegistration)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.CourseRegistrations.Add(courseRegistration);
db.SaveChanges();
// replace 'DefaultApi' with route name for redirect to GET method
return CreatedAtRoute("GetCourseRegistrationById", new { id = courseRegistration.course_id }, courseRegistration);
}
Note:
1) The parameter name contained inside CreatedAtRoute must match with target parameter name used in action method, otherwise the UrlHelper.Link() method will return null.
2) The route name in RouteAttribute should be different from action method name to avoid confusion.
Reference:
Attribute Routing in ASP.NET Web API 2: Route Names
Related issue:
Attribute Routing and CreatedAtRoute
I have an existing WebAPI 2 project that has the current routing :
config.Routes.MapHttpRoute
(
name: "API",
routeTemplate: "api/{controller}/{id}/{function}",
defaults: new { id = RouteParameter.Optional, function = RouteParameter.Optional }
);
The controllers consist of a generic basecontroller and derived controllers per "entity type" that implement
the routes :
[GET] api/{entity}/ <- returns an overview list of entities
[GET] api/{entity}/{id} <- returns the full entity + details
[POST] api/{entity}/{id} <- saves the entity
[DEL] api/{entity}/{id} <- deletes the entity
[POST] api/{entity}/ <- creates a new entity
[POST] api/{entity}/{id}/{function} <- performs a function on an entity (eg. recalculate, send orders,..)
Now i want to add a new method to my basecontroller to be able to get the "count" for an overviewlist. So basically
[GET] api/{entity}/count
I've added the following route to the webapi config :
config.Routes.MapHttpRoute
(
name: "count",
routeTemplate: "api/{controller}/", defaults: new { action = "count" }
);
and added a method to my controller:
[HttpGet]
public async Task<int> Count()
{
return 5;//just a fixed testvalue
}
If i now browse to /api/{entity}/count , i get the value "5" returned.
But the problem is that the overviewlist /api/{entity}/ is no longer working. It says :
ExceptionMessage: "Multiple actions were found that match the request"
I've tried paying around with the "Route" attribute and and the order of the routes, but I cannot get it as I want (which is: everything working like before + the addition of the "count" in the API). I've also looked around on SO and found threads like How to add custom methods to ASP.NET WebAPI controller? but I still can't get working :(
Any idea ?
Thnx.
This is because /{id}/{function} are defined as optional parameters to the API route and both routes matches the url provided.
There is no need to define another route for that just define the method in the controller and decorate it with [HttpGet] attribute and you should be fine.
The new route you configured for your count action enter in conflict with [GET] api/{entity}/ when /count is not specified.
You can remove your new route and use the default /api/{controller}/{action} which will call your count method when you call the url /api/{entity}/count
After a great deal of searching around I've cooked up the following solution that doesn't need any new routes
and is more "RESTfull" according to my searches.
I've opted for the /api{entity}?count query parameter and passing the count in a response header "X-Total-Count".
But the problem is, my exisitng GET method already returns a Generic IList which I cannot just change
without breaking the API. I can also not just return an "object" which is a list or and int, depending on whether
the "count" queryparameter is supplied, because that breaks the Swagger documentation (it no longer sees the returntype)
I've made a new class CountList :
public interface IHasCount
{
int TotalCount { get; }
}
public class CountList<T> : List<T>, IHasCount
{
public int TotalCount { get; set; }
public CountList(int count)
{
TotalCount = count;
}
public CountList(IList<T> list )
{
this.AddRange(list);
this.TotalCount = list.Count;
}
}
A nice side-effect of inheriting fromù the List is that the extra information is stripped off during json-serialization
so I can safely return a Countlist instead of a List, and existing clients won't now the difference !
Then for extracting the count out of the Countlist and put it into a response header, I made a small action filter:
public class GetCountFilter : ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
base.OnActionExecuted(actionExecutedContext);
var counter = (actionExecutedContext.ActionContext.Response.Content as ObjectContent)?.Value as IHasCount;
if (counter != null)
actionExecutedContext.ActionContext.Response.Headers.Add("X-Total-Count", counter.TotalCount.ToString());
}
}
And then I've decorated my Get method :
[HttpGet]
[GetCountFilter]
public async Task<CountList<T>> GetOverview()
{
//special case, we only need the count !
if (ServerContext.QueryFilter.CountOnly) //custom object that parses the queryParameters
{
//todo, but out of scope here,, make a real Count method in the manager that actually executes a count query instead of fetching the whole list
var count = (await _entityManager.GetOverview()).Count;
var result = new CountList<T>(count);
return result;
}
//return the full list
return new CountList<T>( await _entityManager.GetOverview());
}
So , as an endresult, when a client invokes /api/{entity} he gets the overview as usual, with the addidtion of the
X-Total-Count header filled in.
And when he invokes /api/{entity}?count, he gets an empty list, but the total count is still in the header !
This does the trick for me ! If I'm completely not seeing the elephant in the room, please do tell me !
Right now, my API controller has 2 methods: one to get ALL events, and one to get ONE event.
namespace HobbsEventsMobile.Controllers
{
public class EventController : ApiController
{
// GET api/event
[HttpGet]
public List<HobbsEventsMobile.Models.Event> Get()
{
return HobbsEventsMobile.Models.Event.GetEventSummary();
}
// GET api/event/5
[HttpGet]
public HobbsEventsMobile.Models.Event Get(int id)
{
return HobbsEventsMobile.Models.Event.GetEventDetails(id);
}
}
}
Newly requested functionality requires me to add a way to call events for the current week. I have a stored proc and a method to call this, but I am not sure how to specify the URL. I would like to add this:
[HttpGet]
public List<HobbsEventsMobile.Models.Event> Get()
{
return HobbsEventsMobile.Models.Event.GetThisWeeksEvents();
}
but make it accessible at m.mydomain.com/api/event/thisweek (or something). How do I do that?
You have two different options depending on what version of ASP.NET Web API you're running. If you're on version one you can simply follow the convention based routing and use:
public class EventController : ApiController
{
[HttpGet]
public List<HobbsEventsMobile.Models.Event> ThisWeek()
{
return HobbsEventsMobile.Models.Event.GetThisWeeksEvents();
}
}
You will also need to modify your route definitions to support an action name (by default the framework picks the method based upon the HTTP verb):
config.Routes.MapHttpRoute(
"DefaultApiWithId",
"api/{controller}/{id}",
new { id = RouteParameter.Optional }, new { id = #"\d+" }
);
config.Routes.MapHttpRoute(
"DefaultApiWithAction",
"api/{controller}/{action}"
);
config.Routes.MapHttpRoute(
"DefaultApiGet",
"api/{controller}",
new { action = "Get" },
new { httpMethod = new HttpMethodConstraint("GET") }
);
If you're using version two, you can still use the convention based routing, but you also have the ability to use attribute routing:
public class EventController : ApiController
{
[HttpGet]
[Route("event/thisweek")]
public List<HobbsEventsMobile.Models.Event> ICanNameThisWhateverIWant()
{
return HobbsEventsMobile.Models.Event.GetThisWeeksEvents();
}
}
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).
I'm converting from the WCF Web API to the new ASP.NET MVC 4 Web API. I have a UsersController, and I want to have a method named Authenticate. I see examples of how to do GetAll, GetOne, Post, and Delete, however what if I want to add extra methods into these services? For instance, my UsersService should have a method called Authenticate where they pass in a username and password, however it doesn't work.
public class UsersController : BaseApiController
{
public string GetAll()
{
return "getall!";
}
public string Get(int id)
{
return "get 1! " + id;
}
public User GetAuthenticate(string userName, string password, string applicationName)
{
LogWriter.Write(String.Format("Received authenticate request for username {0} and password {1} and application {2}",
userName, password, applicationName));
//check if valid leapfrog login.
var decodedUsername = userName.Replace("%40", "#");
var encodedPassword = password.Length > 0 ? Utility.HashString(password) : String.Empty;
var leapFrogUsers = LeapFrogUserData.FindAll(decodedUsername, encodedPassword);
if (leapFrogUsers.Count > 0)
{
return new User
{
Id = (uint)leapFrogUsers[0].Id,
Guid = leapFrogUsers[0].Guid
};
}
else
throw new HttpResponseException("Invalid login credentials");
}
}
I can browse to myapi/api/users/ and it will call GetAll and I can browse to myapi/api/users/1 and it will call Get, however if I call myapi/api/users/authenticate?username={0}&password={1} then it will call Get (NOT Authenticate) and error:
The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Int32' for method 'System.String Get(Int32)' in 'Navtrak.Services.WCF.NavtrakAPI.Controllers.UsersController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter.
How can I call custom method names such as Authenticate?
By default the route configuration follows RESTFul conventions meaning that it will accept only the Get, Post, Put and Delete action names (look at the route in global.asax => by default it doesn't allow you to specify any action name => it uses the HTTP verb to dispatch). So when you send a GET request to /api/users/authenticate you are basically calling the Get(int id) action and passing id=authenticate which obviously crashes because your Get action expects an integer.
If you want to have different action names than the standard ones you could modify your route definition in global.asax:
Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { action = "get", id = RouteParameter.Optional }
);
Now you can navigate to /api/users/getauthenticate to authenticate the user.
This is the best method I have come up with so far to incorporate extra GET methods while supporting the normal REST methods as well. Add the following routes to your WebApiConfig:
routes.MapHttpRoute("DefaultApiWithId", "Api/{controller}/{id}", new { id = RouteParameter.Optional }, new { id = #"\d+" });
routes.MapHttpRoute("DefaultApiWithAction", "Api/{controller}/{action}");
routes.MapHttpRoute("DefaultApiGet", "Api/{controller}", new { action = "Get" }, new { httpMethod = new HttpMethodConstraint(HttpMethod.Get) });
routes.MapHttpRoute("DefaultApiPost", "Api/{controller}", new {action = "Post"}, new {httpMethod = new HttpMethodConstraint(HttpMethod.Post)});
I verified this solution with the test class below. I was able to successfully hit each method in my controller below:
public class TestController : ApiController
{
public string Get()
{
return string.Empty;
}
public string Get(int id)
{
return string.Empty;
}
public string GetAll()
{
return string.Empty;
}
public void Post([FromBody]string value)
{
}
public void Put(int id, [FromBody]string value)
{
}
public void Delete(int id)
{
}
}
I verified that it supports the following requests:
GET /Test
GET /Test/1
GET /Test/GetAll
POST /Test
PUT /Test/1
DELETE /Test/1
Note That if your extra GET actions do not begin with 'Get' you may want to add an HttpGet attribute to the method.
I am days into the MVC4 world.
For what its worth, I have a SitesAPIController, and I needed a custom method, that could be called like:
http://localhost:9000/api/SitesAPI/Disposition/0
With different values for the last parameter to get record with different dispositions.
What Finally worked for me was:
The method in the SitesAPIController:
// GET api/SitesAPI/Disposition/1
[ActionName("Disposition")]
[HttpGet]
public Site Disposition(int disposition)
{
Site site = db.Sites.Where(s => s.Disposition == disposition).First();
return site;
}
And this in the WebApiConfig.cs
// this was already there
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
// this i added
config.Routes.MapHttpRoute(
name: "Action",
routeTemplate: "api/{controller}/{action}/{disposition}"
);
For as long as I was naming the {disposition} as {id} i was encountering:
{
"Message": "No HTTP resource was found that matches the request URI 'http://localhost:9000/api/SitesAPI/Disposition/0'.",
"MessageDetail": "No action was found on the controller 'SitesAPI' that matches the request."
}
When I renamed it to {disposition} it started working. So apparently the parameter name is matched with the value in the placeholder.
Feel free to edit this answer to make it more accurate/explanatory.
Web Api by default expects URL in the form of api/{controller}/{id}, to override this default routing. you can set routing with any of below two ways.
First option:
Add below route registration in WebApiConfig.cs
config.Routes.MapHttpRoute(
name: "CustomApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Decorate your action method with HttpGet and parameters as below
[HttpGet]
public HttpResponseMessage ReadMyData(string param1,
string param2, string param3)
{
// your code here
}
for calling above method url will be like below
http://localhost:[yourport]/api/MyData/ReadMyData?param1=value1¶m2=value2¶m3=value3
Second option
Add route prefix to Controller class and Decorate your action method with HttpGet as below.
In this case no need change any WebApiConfig.cs. It can have default routing.
[RoutePrefix("api/{controller}/{action}")]
public class MyDataController : ApiController
{
[HttpGet]
public HttpResponseMessage ReadMyData(string param1,
string param2, string param3)
{
// your code here
}
}
for calling above method url will be like below
http://localhost:[yourport]/api/MyData/ReadMyData?param1=value1¶m2=value2¶m3=value3
In case you're using ASP.NET 5 with ASP.NET MVC 6, most of these answers simply won't work because you'll normally let MVC create the appropriate route collection for you (using the default RESTful conventions), meaning that you won't find any Routes.MapRoute() call to edit at will.
The ConfigureServices() method invoked by the Startup.cs file will register MVC with the Dependency Injection framework built into ASP.NET 5: that way, when you call ApplicationBuilder.UseMvc() later in that class, the MVC framework will automatically add these default routes to your app. We can take a look of what happens behind the hood by looking at the UseMvc() method implementation within the framework source code:
public static IApplicationBuilder UseMvc(
[NotNull] this IApplicationBuilder app,
[NotNull] Action<IRouteBuilder> configureRoutes)
{
// Verify if AddMvc was done before calling UseMvc
// We use the MvcMarkerService to make sure if all the services were added.
MvcServicesHelper.ThrowIfMvcNotRegistered(app.ApplicationServices);
var routes = new RouteBuilder
{
DefaultHandler = new MvcRouteHandler(),
ServiceProvider = app.ApplicationServices
};
configureRoutes(routes);
// Adding the attribute route comes after running the user-code because
// we want to respect any changes to the DefaultHandler.
routes.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute(
routes.DefaultHandler,
app.ApplicationServices));
return app.UseRouter(routes.Build());
}
The good thing about this is that the framework now handles all the hard work, iterating through all the Controller's Actions and setting up their default routes, thus saving you some redundant work.
The bad thing is, there's little or no documentation about how you could add your own routes. Luckily enough, you can easily do that by using either a Convention-Based and/or an Attribute-Based approach (aka Attribute Routing).
Convention-Based
In your Startup.cs class, replace this:
app.UseMvc();
with this:
app.UseMvc(routes =>
{
// Route Sample A
routes.MapRoute(
name: "RouteSampleA",
template: "MyOwnGet",
defaults: new { controller = "Items", action = "Get" }
);
// Route Sample B
routes.MapRoute(
name: "RouteSampleB",
template: "MyOwnPost",
defaults: new { controller = "Items", action = "Post" }
);
});
Attribute-Based
A great thing about MVC6 is that you can also define routes on a per-controller basis by decorating either the Controller class and/or the Action methods with the appropriate RouteAttribute and/or HttpGet / HttpPost template parameters, such as the following:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc;
namespace MyNamespace.Controllers
{
[Route("api/[controller]")]
public class ItemsController : Controller
{
// GET: api/items
[HttpGet()]
public IEnumerable<string> Get()
{
return GetLatestItems();
}
// GET: api/items/5
[HttpGet("{num}")]
public IEnumerable<string> Get(int num)
{
return GetLatestItems(5);
}
// GET: api/items/GetLatestItems
[HttpGet("GetLatestItems")]
public IEnumerable<string> GetLatestItems()
{
return GetLatestItems(5);
}
// GET api/items/GetLatestItems/5
[HttpGet("GetLatestItems/{num}")]
public IEnumerable<string> GetLatestItems(int num)
{
return new string[] { "test", "test2" };
}
// POST: /api/items/PostSomething
[HttpPost("PostSomething")]
public IActionResult Post([FromBody]string someData)
{
return Content("OK, got it!");
}
}
}
This controller will handle the following requests:
[GET] api/items
[GET] api/items/5
[GET] api/items/GetLatestItems
[GET] api/items/GetLatestItems/5
[POST] api/items/PostSomething
Also notice that if you use the two approaches togheter, Attribute-based routes (when defined) would override Convention-based ones, and both of them would override the default routes defined by UseMvc().
For more info, you can also read the following post on my blog.
See this article for a longer discussion of named actions. It also shows that you can use the [HttpGet] attribute instead of prefixing the action name with "get".
http://www.asp.net/web-api/overview/web-api-routing-and-actions/routing-in-aspnet-web-api
Web APi 2 and later versions support a new type of routing, called attribute routing. As the name implies, attribute routing uses attributes to define routes. Attribute routing gives you more control over the URIs in your web API. For example, you can easily create URIs that describe hierarchies of resources.
For example:
[Route("customers/{customerId}/orders")]
public IEnumerable<Order> GetOrdersByCustomer(int customerId) { ... }
Will perfect and you don't need any extra code for example in WebApiConfig.cs.
Just you have to be sure web api routing is enabled or not in WebApiConfig.cs , if not you can activate like below:
// Web API routes
config.MapHttpAttributeRoutes();
You don't have to do something more or change something in WebApiConfig.cs. For more details you can have a look this article.
Just modify your WebAPIConfig.cs as bellow
Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { action = "get", id = RouteParameter.Optional });
Then implement your API as bellow
// GET: api/Controller_Name/Show/1
[ActionName("Show")]
[HttpGet]
public EventPlanner Id(int id){}