Two GET in MVC C# - asp.net-web-api

public class EmployeesController : ApiController
{
public IEnumerable<Employee> GetAllEmployees()
{
return (new EmployeeData()).GetEmployeeRecords();
}
public Employee GetSingleEmployee(int id)
{
return (new EmployeeData()).GetSingleEmployeeRecord(id);
}
public void Delete(int id)
{
(new EmployeeData()).DeleteEmployeeRecord(id);
}
}
This is what I have now. I'm trying to make the app call the first function with a get call api/employee and the second one with a get call api/employee/(an ID number) eg api/employee/75. The get call always goes to the first one. How do I solve this?
This is my routing:
namespace EmployeeApp
{
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { action = "Index", id=UrlParameter.Optional }
);
}
}
}

After a brief discussion in chat, we discovered that the issue here is that you have two different route configuration files, one for MVC and one for WebAPI. While you could change the name of your actions to match the convention of the WebAPI route configuration (i.e. change GetSingleEmployee to GetEmployeeById), there is another way which would allow you to keep the actions named the same, and have more control over your desired routes. My recommendation is to use the newer Attribute Routing syntax.
First, change your configuration class like so:
public static void Register(HttpConfiguration config)
{
// Web API routes
config.MapHttpAttributeRoutes();
// Other Web API configuration not shown.
}
next, change your class signature to define the routes through attributes:
[RoutePrefix("api/employee")]
public class EmployeesController : ApiController
{
// http://example.com/api/employee/
[Route("")]
public IEnumerable<Employee> GetAllEmployees()
{
return (new EmployeeData()).GetEmployeeRecords();
}
// http://example.com/api/employee/75
[Route("{id:int}"]
public Employee GetSingleEmployee(int id)
{
return (new EmployeeData()).GetSingleEmployeeRecord(id);
}
// http://example.com/api/employee/Delete/75
[Route("Delete/{id:int}")]
public void Delete(int id)
{
(new EmployeeData()).DeleteEmployeeRecord(id);
}
}
This controller now clearly shows it's routes, rather than the routes being defined somewhere else, or being defined by an obscure name matching convention.

Related

How do I route to a method in ASP.NET Web API?

According to the documentation, if I have this in my WebApiConfig.cs:
config.Routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
I should be able to route to a method in my API controller using a URL like this:
http://localhost:55601/api/Customers/Search
Here is my method:
[ResponseType(typeof(int))]
[HttpPost]
public IHttpActionResult Search([FromBody]CustomerDTO SearchTerm)
{
string Name = SearchTerm.Name;
string Email = SearchTerm.Email;
string PhoneNumber = SearchTerm.Phone;
var customer = db.Customers.Single(c => c.Name == Name && c.EmailAddress == Email && c.PhoneNumber == PhoneNumber);
return Ok(customer.id);
}
I'm sending the search data as a JSON object (using HTTP POST method) in the request body.
However, I get an error saying:
Multiple actions were found that match the request
I only have one method in this controller called Search.
I would have thought this should be pretty straightforward, and work the same way it does with MVC controllers. But I think I'm missing something obvious. Can anyone tell me what it is?
EDIT: As per #KevinLaw's request, adding code for controller showing upblic methods. Also, for further information the following request (HTTP GET) works as expected:
http://localhost:55601/api/Customers?email=[recipient#domain]
public class CustomersController : ApiController
{
private ApplicationDbContext db = new ApplicationDbContext();
// GET: api/Customers
public IQueryable<Customer> GetCustomers()
{
//...
}
// GET: api/Customers/5
[ResponseType(typeof(Customer))]
public IHttpActionResult GetCustomer(int id)
{
//...
}
// GET: api/Customers/5
[ResponseType(typeof(Customer))]
public IHttpActionResult GetCustomerByEmail(string email)
{
//...
}
// PUT: api/Customers/5
[ResponseType(typeof(void))]
public IHttpActionResult PutCustomer(int id, Customer customer)
{
//...
}
// POST: api/Customers
[ResponseType(typeof(Customer))]
public IHttpActionResult PostCustomer(Customer customer)
{
//...
}
[ResponseType(typeof(int))]
[HttpPost]
public IHttpActionResult SearchCustomer([FromBody]CustomerDTO SearchTerm)
{
//...
}
// DELETE: api/Customers/5
[ResponseType(typeof(Customer))]
public IHttpActionResult DeleteCustomer(int id)
{
//...
}
}
The problem here is that the WebApiController uses the REST API specs.
Which state that in a Web Api Controller there can be Zero - One Http Verb.
What i mean by that is that you can have one GET,PUT,POST,DELETE,PATCH
The reason you don't have any problem with the GET is because you have them correctly overloaded. () (int) (string).
But your Posts is (Customer) (CustomerDTO) They are both complex objects and the Binder cannot identify which is which when binding to the complex object.
For this to work you need to use Route Attributes or explicit route.
Attribute Routing
Explicit Routing pt1
Explicit Routing pt2
I think the links are enough to get you started.
If you still want to see some code on your specific case leave a comment below and i will give you some examples.
Thanks
EDIT: Added Examples
Attribute Routing
On WebApiConfig.cs
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
}
On Controller
[RoutePrefix("api/test")]
public class TestingController : ApiController
{
[HttpGet]
[Route("")]
public IEnumerable<string> Get()
{
return new[] { "value1", "value2" };
}
[HttpPost]
[Route("search")]
public IHttpActionResult Post([FromBody]SearchCriteria criteria)
{
return Ok(criteria);
}
}
public class SearchCriteria
{
public string Name { get; set; }
}
Explicit Routing
On WebApiConfig.cs
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes config.Routes.MapHttpRoute(
name: "SearchTest",
routeTemplate: "api/test/search",
defaults: new { controller = "Testing", action = "Search" }
);
config.Routes.MapHttpRoute(
name: "TestingController",
routeTemplate: "api/test/{id}",
defaults: new { controller = "Testing", id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
On Controller
public class TestingController : ApiController
{
[HttpGet]
public IEnumerable<string> Get()
{
return new[] { "value1", "value2" };
}
[HttpPost]
public IHttpActionResult Search([FromBody]SearchCriteria criteria)
{
return Ok(criteria);
}
}
public class SearchCriteria
{
public string Name { get; set; }
}

nohttp resource was found that matches the request uri

I'm trying to develop an api that allows both get and post requests at the same address.
public class DataController : ApiController
{
[HttpGet]
public DataResponse Foo()
{
return GetNext();
}
[HttpPost]
public void Foo(long p1, string p2)
{
SaveValue(p1,p2);
}
}
GET works fine. When calling the POST method, I get the following error:
the requested resource does not support http method 'POST'
My WebApiConfig looks like the following:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.EnableCors();
config.MapHttpAttributeRoutes();
config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
config.Routes.MapHttpRoute(name: "DefaultApi", routeTemplate: "api/{controller}/{action}");
}
}
Any idea what I need to change to get this working?
You can do this in two ways-
1- Change the way you are calling API i.e - pass values in url like <url>?p1=value&p2=value
2- Change the signature of the Action in Web API to
public void Foo([FromBody] MyContract data)
where MyContract is a class with two properties
public class MyContract
{
public long p1 { get; set; }
public string p2 { get; set; }
}
This is because by default parameter binding is done through URL and you are passing them through body.

How would I redirect a from a RenderMvcController to UmbracoApiController

A bit of sleep deprived experimentation here..
I've been playing around with Backbone.js recently and are wondering if there's a way to combine the client-side routing with the server-side routing of umbraco. They way I think about it is instead of the typical RenderMvcController ActionResult being returned for a template name in the route table, the controller would redirect to UmbracoApiController to return json - in effect handing off the task of routing to templates to the backbone client.
This is where I'm stuck, redirecting to the UmbracoApiController - hoping someone can see what I've missed.
Cheers!
Edit: Forgot the error message I'm getting.
No route in the route table matches the supplied values.
public class HomeController : Umbraco.Web.Mvc.RenderMvcController
{
[HttpGet]
public ActionResult Index()
{
return RedirectToAction("GetAllProducts", "ProductsApi");
}
}
public class ProductsApiController : Umbraco.Web.WebApi.UmbracoApiController
{
public IEnumerable<string> GetAllProducts()
{
return new[] { "Table", "Chair", "Desk", "Computer", "Beer fridge" };
}
}
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "Umbraco/Api/{controller}/{action}/",
defaults: new { id = RouteParameter.Optional }
);
var appXmlType = config.Formatters.XmlFormatter.SupportedMediaTypes.FirstOrDefault(t => t.MediaType == "application/xml");
config.Formatters.XmlFormatter.SupportedMediaTypes.Remove(appXmlType);
}
}
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
}
I got a similar thing working by extending PluginController rather than UmbracoApiController, following the approach here.

How to ensure ASP.net Web API controller's parameter is not null?

I created a ASP.net Web API controller like that:
public class UsersController : ApiController
{
//...
public void Put([FromBody]User_API user, long UpdateTicks)
{
user.UpdateTicks = UpdateTicks;
//...
}
}
The "user" parameter will be null if the client does not provide correct arguments. Can I make a global filter to check every parameter like this, and will return a 400 message if any error occurs.
Finally, I got the solution:
public class ModelValidateFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ActionArguments.Any(v => v.Value==null))
{
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.BadRequest);
}
}
}
And...
//In Application_Start()
GlobalConfiguration.Configuration.Filters.Add(new ModelValidateFilterAttribute());

WebAPI Routing Assistance

Learning a lot about MapRouting and have spent WAY too much time trying to get what I need done, so I'm hoping someone can help out. :)
I'm looking to do the following:
/api/Entities/1 <- Views the details entity with id of 1 (this is a string, not int)
/api/Entities/1/Action <- Calls a particular action on the entity with the id of 1.
/api/Entities <- Views the entities set.
/api/Entities/Action <- Calls the particular action on the entities set.
The problem I'm encountering is the last one. That is currently being intercepted by the first case, as the id is a string.
Any assistance would be greatly appreciated!
A very good reference is here
Without using Attribute based routing the answer is somewhat long winded as the RPC style (Action) and Rest methods do not co-exist nicely if you are matching to the same verb. As you have noticed the GET() and DOSOMETHING() are seen as duplicate method signatures on the same controller. To get around this you could try use two controllers:
So I would recommend using attribute based routing; however, it is possible with normal methods:
Using standard routing...
Set your routes like so
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "ActionDefaultApi",
routeTemplate: "api/entities/{action}/{actionId}",
defaults: new
{
//Add more as needed
action = "(|dosomething|dosomethingelse)",
actionId = RouteParameter.Optional,
controller = "EntitiesRpc"
});
Controller 1:
public class EntitiesController : ApiController
{
public string Get()
{
return "http://server/api/Entities";
}
public string Get(string id)
{
return string.Format("http://server/api/Entities/{0}", id);
}
}
Controller 2:
public class EntitiesRpcController : ApiController
{
[HttpGet]
public string DoSomething()
{
return "http://server/api/Entities/doSomething";
}
[HttpGet]
public string DoSomething(string actionId)
{
return string.Format("http://server/api/Entities/doSomething/{0}", actionId);
}
[HttpGet]
public string DoSomethingElse()
{
return "http://server/api/Entities/doSomethingelse";
}
[HttpGet]
public string DoSomethingElse(string actionId)
{
return string.Format("http://server/api/Entities/doSomethingelse/{0}", actionId);
}
}
Now assumming you used attribute based routing you could go back to one controller and use something like this:
public class EntitiesController : ApiController
{
[Get("Entities")]
public string Get()
{
return "http://server/api/Entities";
}
[Get("Entities/{id}")]
public string Get(string id)
{
return string.Format("http://server/api/Entities/{0}", id);
}
[Get("Entities/doSomething")]
public string DoSomething()
{
return "http://server/api/Entities/doSomething";
}
[Get("Entities/doSomething/{actionId}")]
public string DoSomething(string actionId)
{
return string.Format("http://server/api/Entities/doSomething/{0}", actionId);
}
[Get("Entities/doSomethingelse")]
public string DoSomethingElse()
{
return "http://server/api/Entities/doSomethingelse";
}
[Get("Entities/doSomethingelse/{actionId}")]
public string DoSomethingElse(string actionId)
{
return string.Format("http://server/api/Entities/doSomethingelse/{0}", actionId);
}
}

Resources