ASP.NET Web Api Routing Broken - c#

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

Related

Issue with web api

i am working with ASP.Net web API's.I have created a folder named api in controllers folder and then created an Api controller in that api folder.
In the admin controller i have simply placed the following code to check wheather the api is working or not.
public class AdminController : ApiController
{
DBEntities _context;
public AdminController()
{
_context = new DBEntities();
}
[HttpGet]
public IEnumerable<string> GetUsers()
{
return new string[] { "Muhammad","Ali"};
}
}
Then from browser i am calling http://localhost:57368/api/admin but it gives me "The resource cannot be found" with http 404 error code.It should atleast return JSON result but gives this error instead.
Any help is highly appreciated.
And when i go to network tab in chrome it shows the following details of the request:
protected void Application_Start()
{
GlobalConfiguration.Configure(config =>
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
});
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
Your global.asax.cs should look like this
[RoutePrefix("api/admin")]
public class AdminController : ApiController
{
DBEntities _context;
public AdminController()
{
_context = new DBEntities();
}
[Route("GetUsers")]
[HttpGet]
public IEnumerable<string> GetUsers()
{
return new string[] { "Muhammad","Ali"};
}
}
URL: http://localhost:57368/api/admin/GetUsers
you should use 'RoutePrefix' and 'Route' attributes. API should know how to navigate the incoming request to an action. this attributes help you. don't forget add this namespace ' using System.Web.Http'.
using System.Web.Http
[RoutePrefix("api/admin")]
public class AdminController : ApiController
{
DBEntities _context;
public AdminController()
{
_context = new DBEntities();
}
[HttpGet]
[Route("GetUsers")]
public IEnumerable<string> GetUsers()
{
return new string[] { "Muhammad","Ali"};
}
}
In the Global.asax i registered Api using GlobalConfiguration.Configure(WebApiConfig.Register)
but issue was that i placed it at the bottom due to which MVC took precedence and when i called api/admin it gave error saying no resourse was found beacuse MVC was in action.
I added this GlobalConfiguration.Configure(WebApiConfig.Register) at the top and in ApplicationStart() in Gloabl.asax and it worked fine.

ASP.net Web Api Versioning

I have ASP.net Web Api project and I decided that it was time to support versioning. I am using official Microsoft Nuget to support versioning (more info here), and I decided to version by namespace (as exampled here).
Unfortunately I cannot get code to work. If I call my method like this:
http://localhost:7291/api/Saved/GetNumberOfSavedWorkoutsForUser?api-version=2.0
I get error:
Multiple types were found that match the controller named 'Saved'. This can happen if the route that services this request ('api/{controller}/{action}/{id}') found multiple controllers defined with the same name but differing namespaces, which is not supported.
And if I call it like this: http://localhost:7291/v2/Saved/GetNumberOfSavedWorkoutsForUser
I get error 404:
The resource you are looking for has been removed, had its name changed, or is temporarily unavailable.
I am not sure what I am doing wrong. Here is my code:
Startup.cs
public void Configuration(IAppBuilder app)
{
var configuration = new HttpConfiguration();
var httpServer = new HttpServer(configuration);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
// reporting api versions will return the headers "api-supported-versions" and "api-deprecated-versions"
configuration.AddApiVersioning(o =>
{
o.AssumeDefaultVersionWhenUnspecified = true;
o.ReportApiVersions = true;
o.DefaultApiVersion = ApiVersion.Default;
});
configuration.Routes.MapHttpRoute(
"VersionedUrl",
"v{apiVersion}/{controller}/{action}/{id}",
defaults: null,
constraints: new { apiVersion = new ApiVersionRouteConstraint() });
configuration.Routes.MapHttpRoute(
"VersionedQueryString",
"api/{controller}/{action}/{id}",
defaults: null);
app.UseWebApi(httpServer);
ConfigureAuth(app);
}
Saved Controller (v1)
namespace Master.Infrastructure.Api.Controllers
{
[Authorize]
[RoutePrefix("api/Saved")]
[ApiVersion("1.0")]
public class SavedController : ApiController
{
private readonly IUserService _userService;
public SavedController(IUserService userService)
{
_userService = userService;
}
[HttpGet]
[ActionName("GetNumberOfSavedWorkouts")]
public async Task<NumberOfSavedWorkouts> GetNumberOfSavedWorkouts()
{
var numOfSavedWorkouts = new NumberOfSavedWorkouts
{
CurrentNumberOfSavedWorkouts =
await _userService.GetNumberOfSavedWorkoutsForUserAsync(User.Identity.GetUserId())
};
return numOfSavedWorkouts;
}
}
}
Saved Controller (v2)
namespace Master.Infrastructure.Api.V2.Controllers
{
[Authorize]
[ApiVersion("2.0")]
[RoutePrefix("v{version:apiVersion}/Saved")]
public class SavedController : ApiController
{
private readonly ISavedWorkoutService _savedWorkoutService;
public SavedController(ISavedWorkoutService savedWorkoutService)
{
_savedWorkoutService = savedWorkoutService;
}
[ActionName("GetNumberOfSavedWorkoutsForUser")]
public async Task<IHttpActionResult> GetNumberOfSavedWorkoutsForUser()
{
var cnt = await _savedWorkoutService.CountNumberOfSavedWorkoutsForUser(User.Identity.GetUserId());
return Ok(cnt);
}
}
}
Your routes are incorrect. I strongly discourage you from mixing routing styles unless you really need to. It can be very difficult to troubleshoot.
There are several things going on here:
You have configurations to version both by query string and URL segment, which one do you want? I would choose only one. The default and my recommendation would be to use the query string method.
Your convention-based route is different from the attribute-base route
Since you have RoutePrefixAttribute defined, it appears you prefer the attribute-routing style. I would remove all convention-based routes (ex: configuration.Routes.MapHttpRoute).
In your convention, the route template:
v{apiVersion}/{controller}/{action}/{id}
but in your attribute it's:
api/Saved
Neither of these will match your expected routes:
http://localhost:7291/api/Saved/GetNumberOfSavedWorkoutsForUser
http://localhost:7291/v2/Saved/GetNumberOfSavedWorkoutsForUser
For the query string method using route attributes, things should look like:
configuration.AddApiVersioning(o => o.ReportApiVersions = true);
namespace Master.Infrastructure.Api.Controllers
{
[Authorize]
[ApiVersion("1.0")]
[RoutePrefix("api/Saved")]
public class SavedController : ApiController
{
private readonly IUserService _userService;
public SavedController(IUserService userService) => _userService = userService;
[HttpGet]
[Route("GetNumberOfSavedWorkouts")]
public async Task<IHttpActionResult> GetNumberOfSavedWorkouts()
{
var userId = User.Identity.GetUserId();
var count = await _userService.GetNumberOfSavedWorkoutsForUserAsync(userId);
return Ok(new NumberOfSavedWorkouts(){ CurrentNumberOfSavedWorkouts = count });
}
}
}
namespace Master.Infrastructure.Api.V2.Controllers
{
[Authorize]
[ApiVersion("2.0")]
[RoutePrefix("api/Saved")]
public class SavedController : ApiController
{
private readonly ISavedWorkoutService _savedWorkoutService;
public SavedController(ISavedWorkoutService savedWorkoutService) => _savedWorkoutService = savedWorkoutService;
[HttpGet]
[Route("GetNumberOfSavedWorkoutsForUser")]
public async Task<IHttpActionResult> GetNumberOfSavedWorkoutsForUser()
{
var userId = User.Identity.GetUserId();
var count = await _savedWorkoutService.CountNumberOfSavedWorkoutsForUser(userId);
return Ok(count);
}
}
}
The following should then work:
http://localhost:7291/api/Saved/GetNumberOfSavedWorkouts?api-version=1.0
http://localhost:7291/api/Saved/GetNumberOfSavedWorkoutsForUser?api-version=2.0
I hope that help.s

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"; }
}
}

WebApi routing - many GET methods

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;
}

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.

Categories

Resources