Routing optional parameters - c#

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&param2=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&param2=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";
}

Related

Attribute routing with empty parameter

I created the following route in my RouteConfig.cs
config.Routes.MapHttpRoute(
name: "CustomFilter",
routeTemplate: "api/{controller}/{category}/{begin},{pageSize}",
defaults: new
{
category = RouteParameter.Optional,
begin = RouteParameter.Optional,
take = RouteParameter.Optional
}
);
That is used by the method below:
public IHttpActionResult GetStudentsByCategory(string category, int begin, int pageSize)
{
..
}
The custom route works fine, unless the category parameter is missing
1. api/students/tech/1,3 (is working)
2. api/students//1,3 (not working)
Is it possible to make the second URL request (without a category parameter) work?
Add one more Route without category, it should be above current one
config.Routes.MapHttpRoute(
name: "CustomFilter-without-cat",
routeTemplate: "api/{controller}/{begin},{pageSize}",
defaults: new
{
begin = RouteParameter.Optional,
take = RouteParameter.Optional
}
);
config.Routes.MapHttpRoute(
name: "CustomFilter",
routeTemplate: "api/{controller}/{category}/{begin},{pageSize}",
defaults: new
{
category = RouteParameter.Optional,
begin = RouteParameter.Optional,
take = RouteParameter.Optional
}
);

The parameters dictionary contains a null entry for parameter but parameter exists

I have a simple method in my controller - GetMeterProfileData with 1 parameter - meterID
public class AlertsDashboardController : Controller
{
public ActionResult GetMeterProfileData(int meterID)
{
List<IMeterProfileData> result = new List<IMeterProfileData>();
{
if (meterID == 1)
{
result.Add(new MeterProfileData() {PeriodDateTime = Convert.ToDateTime("01/01/2000 00:00"), Consumption = 102});
result.Add(new MeterProfileData() {PeriodDateTime = Convert.ToDateTime("31/01/2000 00:30"), Consumption = 12});
}
else
{
result.Add(new MeterProfileData() {PeriodDateTime = Convert.ToDateTime("01/01/2004 00:00"), Consumption = 500});
result.Add(new MeterProfileData() {PeriodDateTime = Convert.ToDateTime("31/01/2004 00:30"), Consumption = 445});
}
}
return Json(result, JsonRequestBehavior.AllowGet);
}
}
I am using the URL
http://localhost:54728/AlertsDashboard/GetMeterProfileData/2
As you can see, MeterID is being specified
So why do I get the error
The parameters dictionary contains a null entry for parameter
'meterID' of non-nullable type 'System.Int32' for method
'System.Web.Mvc.ActionResult GetMeterProfileData(Int32)' in
'STC.MVC.Web.Controllers.AlertsDashboardController'. An optional
parameter must be a reference type, a nullable type, or be declared as
an optional parameter.
My routing is set up as below
Do I have to add a new route for this method? That wouldnt make much sense to have to do that because I would have to set up loads of routes potentially.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new {id = RouteParameter.Optional}
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new {controller = "Home", action = "Index", id = UrlParameter.Optional}
);
}
I also try adding a new route as below but that didnt work either
routes.MapRoute(
name: "Default2",
url: "{controller}/{action}/{id}",
defaults: new { controller = "AlertsDashboard", action = "Index", id = UrlParameter.Optional }
);
What am I missing?
Paul
Parameter name matters and it must be id when using default routes. You need to either update your method signature to
public ActionResult GetMeterProfileData(int id)
Or define a different route with the correct parameter name:
routes.MapRoute(
name: "Default2",
url: "{controller}/{action}/{meterID}",
defaults: new { controller = "AlertsDashboard", action = "Index", meterID = UrlParameter.Optional }
);

Nested functions in controller

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.

Is there a way to tell routing that my default action name is equal to the HTTP Action Verb?

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.

MVC Web Api Route with default action not working

In my TestController I have the following:
[HttpGet]
public IEnumerable<String> Active()
{
var result = new List<string> { "active1", "active2" };
return result;
}
[HttpGet]
public String Active(int id)
{
var result = new List<string> { "active1", "active2" };
return result[id];
}
In RouteConfig the mapping is:
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { action = "", id = RouteParameter.Optional });
In a browser the following request works:
api/test/active/1
But this returns a Internal Server Error:
api/test/active
What do you have to do to return a action that may or maynot have a parameter in a similar manner to the default Get?
Update 1
As Cuong Le suggested, changing the ordering of routes helped, the routes are now:
routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
I had to remove action = "" from the ActionApi route otherwise the standard Get on the other controllers stopped working (i.e. api/values)
api/test/active is now resolving, but I now get a 500 Internal Server Error for /api/test is it possile to have both resolves, so api/test would return "all" and /test/active only return "some"?
It is probably getting confused since you have two methods named action. Try deleting or renaming one of them and see if that works.
One way to do it is to provide a default value for the parameter,
[HttpGet]
public String Active(int id = 0)
{
var result = new List<string> { "active1", "active2" };
if (id == 0) {
return result;
} else {
return result[id];
}
}

Categories

Resources