WebApi routing - many GET methods - c#

I have the following (standard) WebApiConfig:
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Configure Web API to use only bearer token authentication.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
and the following api controller:
[RoutePrefix("api/books")]
public class BooksController : ApiController
{
// GET api/Books
[Route("")]
public IQueryable<string> GetBooks()
{
return null;
}
// GET api/Books/5
[Route("{id:int}")]
public async Task<IHttpActionResult> GetBook(int id)
{
return Ok();
}
[Route("{id:int}/details")]
public async Task<IHttpActionResult> GetBookDetail(int id)
{
return Ok();
}
[Route("abc")]
public IQueryable<string> GetBooksByGenre(string genre)
{
return null;
}
[Route("~api/authors/{authorId}/books")]
public IQueryable<string> GetBooksByAuthor(int authorId)
{
return null;
}
}
It found appropriate method when I call
api/books
api/books/1
api/books/1/details
but it can't find api/books/abc.
If I change [Route("abc")] to [Route("{genre}")] it works (pass abc as genre parameter).
But I need to have many GET methods with different names.
What did I do wrong?

Try
// GET api/Books/genres/horror
[Route("genres/{genre}")]
public IQueryable<string> GetBooksByGenre(string genre)
{
return null;
}
Or even
// GET api/genres/horror/books
[Route("~api/genres/{genre}/books")]
public IQueryable<string> GetBooksByGenre(string genre)
{
return null;
}

Related

404 Error With MVC.NET Web API

I'm making a very simple Web API using MVC.NET to retrieve values from the following database:
CREATE TABLE [dbo].[Rates] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[Code] VARCHAR (3) NOT NULL,
[Name] VARCHAR (50) NOT NULL,
[Rate] DECIMAL (5, 2) NOT NULL,
PRIMARY KEY CLUSTERED ([Id] ASC)
);
For whatever reason that I do not understand, whenever I compile my solution and navigate to localhost:xxxxx/api or api/Rates (my controller), I get the following error:
Server Error in '/' Application
The Resource cannot be found. (A Http 404 error)
I do not understand why this is happening, as it's a freshly built api application, using Entity Framework.
Below are my controllers and WebApiConfig classes. Perhaps something in one of those is amiss?
WebApiConfig:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Web.Http;
using Microsoft.Owin.Security.OAuth;
using Newtonsoft.Json.Serialization;
using System.Net.Http.Headers;
namespace ExchangeService
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Configure Web API to use only bearer token authentication.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "localhost:63484/api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Formatters.Remove(config.Formatters.XmlFormatter);
config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));
}
}
}
ValuesController (Left as default)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
namespace ExchangeService.Controllers
{
[Authorize]
public class ValuesController : ApiController
{
// GET api/values
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values/5
public string Get(int id)
{
return "value";
}
// POST api/values
public void Post([FromBody]string value)
{
}
// PUT api/values/5
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/values/5
public void Delete(int id)
{
}
}
}
And finally, my Rates controller:
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Description;
using ExchangeService.Models;
namespace ExchangeService.Controllers
{
public class RatesController : ApiController
{
private ExRatesDBEntities db = new ExRatesDBEntities();
// GET: api/Rates
public IQueryable<Rate> GetRates()
{
return db.Rates;
}
// GET: api/Rates/5
[ResponseType(typeof(Rate))]
public IHttpActionResult GetRate(int id)
{
Rate rate = db.Rates.Find(id);
if (rate == null)
{
return NotFound();
}
return Ok(rate);
}
// PUT: api/Rates/5
[ResponseType(typeof(void))]
public IHttpActionResult PutRate(int id, Rate rate)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (id != rate.Id)
{
return BadRequest();
}
db.Entry(rate).State = EntityState.Modified;
try
{
db.SaveChanges();
}
catch (DbUpdateConcurrencyException)
{
if (!RateExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return StatusCode(HttpStatusCode.NoContent);
}
// POST: api/Rates
[ResponseType(typeof(Rate))]
public IHttpActionResult PostRate(Rate rate)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.Rates.Add(rate);
db.SaveChanges();
return CreatedAtRoute("DefaultApi", new { id = rate.Id }, rate);
}
// DELETE: api/Rates/5
[ResponseType(typeof(Rate))]
public IHttpActionResult DeleteRate(int id)
{
Rate rate = db.Rates.Find(id);
if (rate == null)
{
return NotFound();
}
db.Rates.Remove(rate);
db.SaveChanges();
return Ok(rate);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
private bool RateExists(int id)
{
return db.Rates.Count(e => e.Id == id) > 0;
}
}
}
The only other point of interest I can think of, is that this application is running from an external hard drive. I can't think of any reason why this should be an issue, but thought it would be worth noting. Thanks.
For whatever reason that I do not understand, whenever I compile my solution and navigate to localhost:xxxxx/api or api/Rates (my controller), I get the following error:
Server Error in '/' Application
The Resource cannot be found. (A Http 404 error)
In the first case it cause, because you didn't specify API controller, in the second case, because you didn't specify method of API controller.
Try to call it as http://localhost:63484/api/Rates/GetRates
UPDATE:
Looks like you haven't registered your routes correctly, since you are using both MVC and Web API, so try these configurations:
WebApiConfig class:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Configure Web API to use only bearer token authentication.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
RouteConfig class:
public static class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
And then call them in your Global.asax class:
protected void Application_Start()
{
...
//next line registers web api routes
GlobalConfiguration.Configure(WebApiConfig.Register);
...
//next line registers mvc routes
RouteConfig.RegisterRoutes(RouteTable.Routes);
...
}
I don't believe you need to list the port.
Change the following in your WebApiConfig:
routeTemplate: "localhost:63484/api/{controller}/{id}"
to
routeTemplate: "api/{controller}/{id}"
Try also renaming:
GetRates() to Get()
and calling:
http://localhost:63484/api/Rates
For a rate with an Id you will need to make the following changes:
// GET: api/Rates/5
[ResponseType(typeof(Rate))]
public IHttpActionResult GetRate(int id)
{
Rate rate = db.Rates.Find(id);
if (rate == null)
{
return NotFound();
}
return Ok(rate);
}
to
// GET: api/Rates/5
[ResponseType(typeof(Rate))]
public IHttpActionResult Get(int id)
{
Rate rate = db.Rates.Find(id);
if (rate == null)
{
return NotFound();
}
return Ok(rate);
}
Actually all your Actions in your RateController need to be renamed. Use the same naming convention as you did in your ValuesController. WepAPI is meant to operate off of the named Actions Get(), Put(), Post() etc.

Web api controller selection by route

I have two different class library that contains same name controllers.
namespace OldApiService{
public class GreetingController: ApiController{
public string Get(){ return "hello from old api"; }
}
}
namespace NewApiService{
public class GreetingController: ApiController{
public string Get(){ return "hello from new api"; }
}
}
And I have a main We Api applciation that contains Route and other helper classes. This application references NewApiService and OldApiService assemblies.
namespace MyApi {
public class Startup
{
public void Configuration(IAppBuilder appBuilder)
{
HttpConfiguration config = new HttpConfiguration();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{api}/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
appBuilder.UseWebApi(config);
}
}
}
I want to select a controller that specified url parameter.
http://localhost:4035/api/old/greeting will use OldApiService Controller
http://localhost:4035/api/new/greeting will use NewApiService Controller
I tried to change url route settings but not worked. Duplicated controller error occurred.
Is there any way to override the controller selection mechanism. Simply I will get route value (old or new) and select the controller from specified namespace.
Multiple types were found that match the controller named 'greeting'.
This can happen if the route that services this request
('api/{version}/{controller}/{id}') found multiple controllers defined
with the same name but differing namespaces, which is not supported.
The request for 'greeting' has found the following matching
controllers: OldApiService.GreetingController
NewApiService.GreetingController
I think this is an important issue for asp.net web api.
Old library
namespace OldApiService{
public static class WebApiConfig {
public static void Register(HttpConfiguration config) {
config.config.MapHttpAttributeRoutes();
}
}
[RoutePrefix("api/old/greeting")]
public class GreetingController: ApiController{
[Route("")]
public string Get(){ return "hello from old api"; }
}
}
Other library
namespace NewApiService{
public static class WebApiConfig {
public static void Register(HttpConfiguration config) {
config.config.MapHttpAttributeRoutes();
}
}
[RoutePrefix("api/new/greeting")]
public class GreetingController: ApiController{
[Route("")]
public string Get(){ return "hello from new api"; }
}
}
Start up
namespace MyApi {
public class Startup {
public void Configuration(IAppBuilder appBuilder) {
var config = new HttpConfiguration();
//Map attribute routes
OldApiService.WebApiConfig.Register(config);
NewApiService.WebApiConfig.Register(config);
//convention-based routes
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{api}/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
appBuilder.UseWebApi(config);
}
}
}
I would use the RoutePrefix/Route attributes.
namespace OldApiService{
[RoutePrefix("api/old/greeting")]
public class GreetingController: ApiController{
[Route("")]
public string Get(){ return "hello from old api"; }
}
}
namespace NewApiService{
[RoutePrefix("api/new/greeting")]
public class GreetingController: ApiController{
[Route("")]
public string Get(){ return "hello from new api"; }
}
}

WEB API 2 controller can't access the resource

I have a webforms project that supports MVC. I have created a WEB API 2 controller that used a model class which was generated using EF database first to convert SQL table into entity model. when I launch my application, the controller doesn't work as expected. I get the following when try to access the api:
HTTP Error 404.0 - Not Found
The resource you are looking for has been removed, had its name changed, or is temporarily unavailable.
My Controller class is as following:
namespace YuClone.Controllers {
public class VideosController : ApiController
{
private YuCloneContext db = new YuCloneContext();
// GET: api/Videos
public IQueryable<video> Getvideos()
{
return db.videos;
}
// GET: api/Videos/5
[ResponseType(typeof(video))]
public IHttpActionResult Getvideo(long id)
{
video video = db.videos.Find(id);
if (video == null)
{
return NotFound();
}
return Ok(video);
}
// PUT: api/Videos/5
[ResponseType(typeof(void))]
public IHttpActionResult Putvideo(long id, video video)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (id != video.videoid)
{
return BadRequest();
}
db.Entry(video).State = EntityState.Modified;
try
{
db.SaveChanges();
}
catch (DbUpdateConcurrencyException)
{
if (!videoExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return StatusCode(HttpStatusCode.NoContent);
}
// POST: api/Videos
[ResponseType(typeof(video))]
public IHttpActionResult Postvideo(video video)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.videos.Add(video);
db.SaveChanges();
return CreatedAtRoute("DefaultApi", new { id = video.videoid }, video);
}
// DELETE: api/Videos/5
[ResponseType(typeof(video))]
public IHttpActionResult Deletevideo(long id)
{
video video = db.videos.Find(id);
if (video == null)
{
return NotFound();
}
db.videos.Remove(video);
db.SaveChanges();
return Ok(video);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
private bool videoExists(long id)
{
return db.videos.Count(e => e.videoid == id) > 0;
}
} }
What can be done to fix this?
Edit:
This is how the route is configured in WebApiConfig :
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
which is registered in application_start method in Global.aspx as :
RouteConfig.RegisterRoutes(RouteTable.Routes);
The URL I'm using to access the resource is :
http://localhost:5958/api/videos
You need to register your WebAPI routes in Global.asax.cs:
GlobalConfiguration.Configure(WebApiConfig.Register);
You are not registering the WebAPI routes.
RouteConfig.RegisterRoutes(RouteTable.Routes);
This code only registers MVC controller routes. For Web API, you need to register web api routes by calling the class you mentioned -
var config = GlobalConfiguration.Configuration;
WebApiConfig.Register(config); //register api routes
RouteConfig.RegisterRoutes(RouteTable.Routes);
This should work for you.
Make sure you are calling the RouteConfig.RegisterRoutes after the WebApiConfig.Register. Like this:
System.Web.Http.GlobalConfiguration.Configure(WebApiConfig.Register);
RouteConfig.RegisterRoutes(RouteTable.Routes);

WebApi viewmodel validation not working

I've been banging my head on trying to get ViewModels to validate with webapi 2.2
From the docs ..it should work:
http://www.asp.net/web-api/overview/formats-and-model-binding/model-validation-in-aspnet-web-api
namespace WebApplication3.Controllers
{
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ModelState.IsValid == false)
{
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState);
}
}
}
public class TestViewModel
{
[Required]
[EmailAddress]
[MinLength(3)]
[MaxLength(255)]
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
}
public class ValuesController : ApiController
{
[ValidateModel]
[HttpGet]
public string Test(TestViewModel email)
{
if (ModelState.IsValid)
{
return "ok";
}
return "not ok";
}
}
}
With or without the ValidateModelAttribute it just returns "ok" all the time...
The ValidateModelAttribute is registered in WebApiConfig
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
config.Filters.Add(new ValidateModelAttribute());
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
Anyone have an idea whats going on here ? It's so much simpler to use DataAnnotations to prevalidate data.
Sample request:
http://localhost:55788/api/values/Test?email=ss
Returns: ok
Neither GET/POST changes anything
Where simple MVC controllers seem to have no problem, in this web api example we apparently have to specify [FromUri]
This works just fine
[HttpGet]
public string Test([FromUri]TestViewModel email)
{
if (ModelState.IsValid)
{
return "ok";
}
return "not ok";
}
With this code i can now also implement jsonP behavior
Also the custom ValidateModelAttribute becomes obsolete, though it can still be useful if you want to throw an exception systematically when a ViewModel is invalid. I rather just handle it in code to be able to return custom error objects.

ASP.NET Web Api Routing Broken

I am unable to get basic routing to work in my asp.net web api project. I have followed examples on asp.net (http://www.asp.net/web-api/overview/web-api-routing-and-actions) and I have searched throughout stackoverflow in an attempt to find a solution. Whatever examples I have tried, I cannot get attribute routing to work.
This is my controller:
public class EmployeeController : ApiController
{
private readonly IRepository<Employee> _employees;
public EmployeeController(IRepository<Employee> repo)
{
_employees = repo;
}
[Route("")]
public IEnumerable<Employee> GetEmployees()
{
return _employees.Queryable();
}
[Route("{id:int}")]
public Employee GetEmployee(int id)
{
return _employees.Queryable().FirstOrDefault();
}
}
This is my Global.asax.cs:
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
}
}
This is my WebApiConfig.cs:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
No matter what I attempt, I end up either with a 404 or as in the case of the code above, I get the message
No HTTP resource was found that matches the request URI
'http://localhost:2442/api/employee/1'.
No action was found on the controller 'Employee' that matches the
request.
with or without the integer parameter.
Either use the attribute routing for your controller, or don't use it all. That means you need to decorate your controller with RoutePrefix instead of relying on the configured routes.
[RoutePrefix("api/employee")
public class EmployeeController : ApiController
{
private readonly IRepository<Employee> _employees;
public EmployeeController(IRepository<Employee> repo)
{
_employees = repo;
}
[Route("")]
public IEnumerable<Employee> GetEmployees()
{
return _employees.Queryable();
}
[Route("{id}")]
public Employee GetEmployee(int id)
{
return _employees.Queryable().FirstOrDefault();
}
}
or in the below example, we rely on the defined route instead of using attribute routing.
public class EmployeeController : ApiController
{
private readonly IRepository<Employee> _employees;
public EmployeeController(IRepository<Employee> repo)
{
_employees = repo;
}
public IEnumerable<Employee> GetEmployees()
{
return _employees.Queryable();
}
public Employee GetEmployee(int id)
{
return _employees.Queryable().FirstOrDefault();
}
}
If you mix and match, it confuses things.
Did you try putting the RoutePrefix attribute on your class like this:
[RoutePrefix("api/employee")]
public class EmployeeController : ApiController

Categories

Resources