New to MVC and the API template, familiar with C#.
Plain new WebAPI project with a simple controller. Url path like /api/Clients/1, is there a way to sort of nest controllers? Or execute a function in the same controller by following a url path like this: /api/Clients/1/Sysinfo/typeOf?
I have a model Client which contains properties which are sysinfo items. /api/Clients/1 returns all the properties of an object Client with the Id of 1. I want only specific items returned with /api/Clients/1/Sysinfo/RAM for example.
#Joachim Rosskopf
I have tried that approach. It result in a 404. /clients/1/ works. /clients/1/sysinfo does not work.
Using the following routes:
routes.MapRoute(
name: "Sysinfo",
url: "Clients/{id}/Sysinfo/{type}",
defaults: new { controller = "Sysinfo", type = UrlParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Created a new controller SysinfoController:
public class SysinfoController : ApiController
{
public Sysinfo GetAllSysinfoItems()
{
return new Sysinfo { Id = 1, RAM = "1GB" };
}
public IHttpActionResult GetSysinfoByType(int id)
{
return Ok();
}
}
You have to adjust your routing configuration and add special entries for the child routes. It is important to add the most specific route first:
routes.MapHttpRoute(
name: "SysInfoApi",
routeTemplate: "api/Clients/{id}/Sysinfo/{param}",
defaults: new { controller = "SysInfo", param = RouteParameter.Optional }
);
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
In the Above example there is a specific route for a SysInfo controller, which handles the nested resource. As well as the default route.
The SysInfo controller with actions for GET-requests could look the following
public class SysInfoController : ApiController
{
// GET api/client/{id}/sysinfo/
public string Get(int id)
{
return "value";
}
// GET api/client/{id}/sysinfo/{param}
public string Get(int id, string param)
{
return "value";
}
}
I don't know of a method to handle hierarchical ressources in WebApi automatically.
Related
I have the following route in my WebApiConfig
config.Routes.MapHttpRoute(
name: "PaginateMessages",
routeTemplate: "api/Message/PaginateMessages/{conversationId}/{lastMessageId}",
defaults: new { controller = "Message", action = "PaginateMessages", conversationId = RouteParameter.Optional, lastMessageId = RouteParameter.Optional }
);
And I have the corresponding action in the Message controller:
[HttpGet]
public async Task<List<MessageDTO>> PaginateMessages(int conversationId, int lastMessageId)
{
return null;
}
However when I try to hit the endpoint I get a 404:
http://localhost:60162/api/Message/PaginateMessages/71/150
Is it not possible to have multiple route parameters as shown above?
You will need to add {controller} and {action} in route template.
config.Routes.MapHttpRoute(
name: "PaginateMessages",
routeTemplate: "api/{controller}/{action}/{conversationId}/{lastMessageId}",
defaults: new {controller = "Conversation", action = "GetConversation", conversationId = RouteParameter.Optional, lastMessageId = RouteParameter.Optional}
);
Alternative way in Web API 2 is to remove custom route configuration in WebApiConfig, and use Route attribute.
public class MessageController : ApiController
{
[HttpGet]
[Route("api/Message/PaginateMessages/{conversationId}/{lastMessageId}")]
public async Task<List<MessageDTO>> PaginateMessages(
int conversationId, int lastMessageId)
{
return null;
}
}
public IEnumerable<Employee> GetEmployees()
{
//code and return
}
public Employee GetSingleEmployee(int Id)
{
//code and return
}
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 }
);
}
}
}
Use annotation like
[HttpGet]
[Route("employee")]
public IEnumerable<Employee> GetEmployees()
{
//code and return
}
[HttpGet]
[Route("employee/{Id}")]
public Employee GetSingleEmployee(int Id)
{
//code and return
}
On class above you RoutePrefix to
[RoutePrefix("api")]
As others have stated without seeing your routing it could be difficult to guess but assuming it is something like this:
routes.MapHttpRoute(
name: "API Default",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
the optional id parameter should work as long as the method parameter is lowercase.
If you want to use the action as part of the routing then use this, placing it above the default route:
routes.MapHttpRoute(
name: "Api By Action",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
I have a blogging system that I'm building and I can't seem to get ASP.NET MVC to understand my route.
the route I need is /blogs/student/firstname-lastname so /blogs/student/john-doe, which routes to a blogs area, student controller's index action, which takes a string name parameter.
Here is my route
routes.MapRoute(
name: "StudentBlogs",
url: "blogs/student/{name}",
defaults: new { controller = "Student", action="Index"}
);
My controller action
public ActionResult Index(string name)
{
string[] nameparts = name.Split(new char[]{'-'});
string firstName = nameparts[0];
string lastName = nameparts[1];
if (nameparts.Length == 2 && name != null)
{
// load students blog from database
}
return RedirectToAction("Index", "Index", new { area = "Blogs" });
}
But it won't seem to resolve...it works fine with /blogs/student/?name=firstname-lastname, but not using the route I want, which is /blogs/student/firstname-lastname. Any advice on how to fix this would be greatly appreciated.
My RouteConfig
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "StudentBlogs",
url: "blogs/student/{name}",
defaults: new { controller = "Student", action = "Index"},
constraints: new { name = #"[a-zA-Z-]+" },
namespaces: new string[] { "IAUCollege.Areas.Blogs.Controllers" }
);
routes.MapRoute(
name: "Sitemap",
url :"sitemap.xml",
defaults: new { controller = "XmlSiteMap", action = "Index", page = 0}
);
//CmsRoute is moved to Gloabal.asax
// campus maps route
routes.MapRoute(
name: "CampusMaps",
url: "locations/campusmaps",
defaults: new { controller = "CampusMaps", action = "Index", id = UrlParameter.Optional }
);
// core route
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
// error routes
routes.MapRoute(
name: "Error",
url: "Error/{status}",
defaults: new { controller = "Error", action = "Error404", status = UrlParameter.Optional }
);
// Add our route registration for MvcSiteMapProvider sitemaps
MvcSiteMapProvider.Web.Mvc.XmlSiteMapController.RegisterRoutes(routes);
}
}
You have to declare custom routes before the default routes. Otherwise it will be mapping to {controller}/{action}/{id}.
Global.asax typically looks like this:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
If you created an Area named Blogs, there is a corresponding BlogsAreaRegistration.cs file that looks like this:
public class BlogsAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Blogs";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Admin_default",
"Blogs/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
}
}
Hyphens are sometimes treated like forward slashes in routes. When you are using the route blogs/students/john-doe, my guess is that it is matching the Area pattern above using blogs/students/john/doe, which would result in a 404. Add your custom route to the BlogsAreaRegistration.cs file above the default routes.
Try adding the parameter to the route:
routes.MapRoute(
name: "StudentBlogs",
url: "blogs/student/{name}",
defaults: new { controller = "Student", action="Index", name = UrlParameter.Optional}
);
Try adding a constraint for the name parameter:
routes.MapRoute(
name: "StudentBlogs",
url: "blogs/student/{name}",
defaults: new { controller = "Student", action="Index" },
constraints: new { name = #"[a-zA-Z-]+" }
);
Dashes are a bit weird in MVC at times.. because they are used to resolve underscores. I will remove this answer if this doesn't work (although.. it should).
This has the added benefit of failing to match the route if a URL such as /blogs/student/12387 is used.
EDIT:
If you have controllers with the same name.. you need to include namespaces in both of your routes in each area. It doesn't matter where the controllers are.. even if in separate areas.
Try adding the appropriate namespace to each of the routes that deal with the Student controller. Somewhat like this:
routes.MapRoute(
name: "StudentBlogs",
url: "blogs/student/{name}",
defaults: new { controller = "Student", action="Index" },
namespaces: new string[] { "Website.Areas.Blogs.Controllers" }
);
..and perhaps Website.Areas.Admin.Controllers for the one in the admin area.
I've looked through this document on MSDN and can't come up with the answer.
Considering that I have a route defined like this:
config.Routes.MapHttpRoute(
name: "DefaultWithActionAndID",
routeTemplate: "v{version}/{controller}/{action}/{id}",
defaults: null,
constraints: new {
action = #"[a-zA-Z]+",
id = #"\d+"
}
);
config.Routes.MapHttpRoute(
name: "DefaultWithID",
routeTemplate: "v{version}/{controller}/{id}",
defaults: null,
constraints: new {
id = #"\d+"
}
);
config.Routes.MapHttpRoute(
name: "DefaultWithoutActionOrId",
routeTemplate: "v{version}/{controller}",
defaults: null,
);
Now I have two controllers that looks like this:
public class ItemController:ApiController{
[HttpGet]
public Item Get(int id){}
[HttpGet]
public Item GetSomething(int id){}
[HttpPut]
public Item Put(Item newItem){}
}
public class AnotherController:ApiController{
[HttpPut]
public HttpResponseMessage Put(Something item){}
}
I'd like to be able to call all of these endpoints like this:
GET /api/Item/344
GET /api/Item?id=344
GET /api/Item/Something/2334
GET /api/Item/Something?id=2334
PUT /api/Item body={newItem}
PUT /api/Another body={newSomething}
This will work, but only if I add "Get" as the default action name. If I do not specify a default action name in my route, then it complains about multiple matching action names. If I do add the default action name, then I cannot call PUT to the Put() method without an error because the action name doesn't match the default and isn't found.
// Will work in some cases, but not all
config.Routes.MapHttpRoute(
name: "DefaultWithID",
routeTemplate: "v{version}/{controller}/{id}",
defaults: new {
action="Get",
id=RouteParameters.Optional
},
constraints: new {
id = #"\d+"
}
);
// Works
GET /api/Item/344
GET /api/Item?id=344
GET /api/Item/Something/2334
GET /api/Item/Something?id=2334
// Doesn't work
PUT /api/Item body={newItem}
PUT /api/Another body={newSomething}
How can I tell Routing to use the Action with the name that matches my HTTP Verb, if one exists before trying to use
If you define your routes as follows:
config.Routes.MapHttpRoute(
name: "DefaultWithActionAndID",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional },
constraints: new {action = #"[a-zA-Z]+", id = #"\d*" }
);
config.Routes.MapHttpRoute(
name: "DefaultWithID",
routeTemplate: "api/{controller}/{id}",
defaults: new { action = "GET", id = RouteParameter.Optional },
constraints: new { id = #"\d*", httpMethod = new HttpMethodConstraint(new string[] { "GET" }) }
);
config.Routes.MapHttpRoute(
name: "DefaultWithoutActionOrId",
routeTemplate: "api/{controller}",
defaults: new { action = "PUT" },
constraints: new { httpMethod = new HttpMethodConstraint(new string[] { "PUT" }) }
);
And also place the ActionName attribute on your GetSomething method as so:
[ActionName("Something")]
public Item GetSomething(int id){}
You should then be able to hit all the endpoints mentioned above.
The way I see it you'd need the following setup:
1.
/api/Item/344
{controller}/{id}
2.
/api/Item/Something/2334
{controller}/{action}/{id}
and decorate the 'GetSomething' method as follows:
[ActionName("Something")]
public Item GetSomething(int id){}
3.
/api/Item?id=344
/api/Item/Something?id=2334
I'm not entirely sure about these - have you tried adding a default to the routes above:
defaults: new { id = RouteParameter.Optional }
4.
I'd expect PUT to just work if #3 is applied
Let me know if that changes anything.
I'm working on my first web api and I'm stuck trying to create rules for these urls
1) http://mydomain.com/values/4 --> this number can be any, this is just an example
2) http://mydomain.com/values/
3) http://mydomain.com/values/?param1=test1¶m2=test2
4) http://mydomain.com/values/?param1=test1
5) http://mydomain.com/values/?param2=test2
My current routing rules are
routes.MapHttpRoute(
name: "Route1",
routeTemplate: "{controller}/{id}/"
);
routes.MapHttpRoute(
name: "Route2",
routeTemplate: "{controller}/{param1}/{param2}",
defaults: new { param1 = RouteParameter.Optional, param2 = RouteParameter.Optional }
);
And my methods serving these urls
// GET values/
// GET values/?param1=test1¶m2=test2
// GET values/?param1=test1
// GET values/?param2=test2
public IEnumerable Get(string param1, string param2)
{
return new string[] { "value1", "value2" };
}
// GET values/5
public string Get(int id)
{
return "value";
}
I have 2 problems
1) http://mydomain.com/values/ is not resolving
2) http://mydomain.com/values/?param1=test1 and http://mydomain.com/values/?param2=test2 are not resolving.
Could you help me to create routes for serving these urls?
routes.MapHttpRoute(
name: "Route1",
routeTemplate: "{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Should do what you need. Query string parameters are already mapped to Action parameters as long as they are named the same, no need to add them in the route.
First you don't need the route
routes.MapHttpRoute(
name: "Route2",
routeTemplate: "{controller}/{param1}/{param2}",
defaults: new { param1 = RouteParameter.Optional, param2 = RouteParameter.Optional }
);
Secondly to resolve http://mydomain.com/values/ you need to make parameter id optional by making it int?
// GET values/5
public string Get(int? id)
{
return "value";
}