ASP.NET WebAPI routing confusing me with null paramters - asp.net-web-api

I can't figure out why I need to create an empty method signature to allow a Get rest call with 3 null parameters to work. I have the following Code:
public class SessionPresenterController : ApiController
{
public HttpResponseMessage Get()
{
return Get(null, null, null);
}
public HttpResponseMessage Get(int? codeCampYearId, int? sessionId, int? attendeesId)
{
and in my WebApiConfig I have
config.Routes.MapHttpRoute
("API Default Rest", "rest/{controller}/{id}",
new { id = RouteParameter.Optional });
config.Routes.MapHttpRoute
("API Default RPC", "rpc/{controller}/{action}/{id}",
new { id = RouteParameter.Optional });
If I call /rest/SessionPresenter without any parameters and I Don't have the Get() defined, the Get with the three null parameters is not found.
Why not?

Try specifying defaults for your parameters:
public HttpResponseMessage Get(
int? codeCampYearId = null,
int? sessionId = null,
int? attendeesId = null)
{
//...
}

Jacobs' snippet will solve your issue. Let's answert the question why not? why get is not found..
There is a complete documentation of the Routing and Action Selection. Let's use some extratc and reveal what happens:
Action Selection
Create a list of all actions on the controller that match the HTTP request method.
If the route dictionary has an "action" entry, remove actions whose name does not match this value.
Try to match action parameters to the URI, as follows:
For each action, get a list of the parameters that are a simple type, where the binding gets the parameter from the URI. Exclude optional parameters.
From this list, try to find a match for each parameter name, either in the route dictionary or in the URI query string. Matches are case insensitive and do not depend on the parameter order.
Select an action where every parameter in the list has a match in the URI.
If more that one action meets these criteria, pick the one with the most parameter matches.
Other words, if there will be only Get with three parameters (omited Get()), to decided which action should be selected:
Selected was Get(int? codeCampYearId, int? sessionId, int? attendeesId)
still the Get(int? codeCampYearId, int? sessionId, int? attendeesId) is selected
URL is /rest/SessionPresenter
no optional parameters excluded. All have to be found
URL does not have a match for all three parameters
So to solve it, we have to either pass all params (empty, null):
/rest/SessionPresenter?codeCampYearId&attendeesId&sessionid
Or change the signature to have parameters optinal (Jacobs' answer), and skipped during the action selection

Related

Web Api 2 empty string parameter GET

i have setup the route to be:
[Route("{id}/users/search/{search}")]
and the associated action is:
SomeAction(int id, string text)
The service has the following function.
for the resource with id={id} and the users of this resource get the users that match with the {search} term (username, email etc).
the {search} can have a value so the service returns only the matching entities or does not have a value (empty string or null) so the service returns everything.
For the part with a value it works fine.
For the second part i cannot find something to set the get request that matches the empty string.
i tried the following:
1/users/search/null {search} = "null"
1/users/search/ does not match route
1/users/search does not match route
has anyone a hint how this could be done?
Update: i have tried to replace the action:
SomeAction(int id, string text)
with:
SomeAction(Model model) where model is
public class ApplicationUserSearchModel
{
[Required]
public int Id { get; set; }
[Required(AllowEmptyStrings = true)]
public string MatchText { get; set; }
}
with no luck since i don't know what to send in order to match the url to this.
You should tag your search parameter with ? to mark it as optional in the route, and set it to null by default in your action.
[Route("{id}/users/search/{search?}")]
public HttpResponseMessage Search(int id, string search = null)
I originally thought the route/action parameter names were the issue, but I was incorrect. Here is the previous answer:
The parameter names in your route definition and your action don't match, which is causing your problem.
[Route("{id}/users/search/{search}")]
public HttpResponseMessage Search(int id, string text)
You should update the string parameter in your action from text to search, to match the parameter name in your Route attribute.

Asp.Net MVC 3 variable in url

The default MVC 3 route config is
{controller}/{action}/{id}
My NEWS application structure is like
/News/Latest10
/News/Critical/10June2013
/Entertainment/Latest10
Bold ones being controller, italics as actions, and normal text are optional params.
Now I want to add new variable, language, into the url structure.
It should be like
/en/News/Latest10
/ja/News/Critical/10June2013
/de/Entertainment/Latest10
I would like to know how to access this language variable in the controller. Is it possible?
Thanks
To meet your needs change the Route config to:
routes.MapRoute(
name: "Language",
url: "{language}/{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional, language="en" },
constraints: new {language=new LanguageConstraint()}
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
The two key parts are the route itself, {language}/{controller}/{action}/{id} and the constraint part, new {language=new LanguageConstraint()}.
The first part will select the {language} part as a variable (default being en for now) to the controller. The controller signature:
public ActionResult Index(string language) {
will pick up the new language variable. Since adding language to each and every controller could seem cumbersome you could create a ViewModelBase class to passed to every controller with a property that contains the language value, which every subsequent View Model class inherits from.
Without a constraint the route pattern would pick up all values in the url for the language part and writing a Regex expression to match all wanted language values would be tedious, I think it's easier to write an IRouteConstraint based class similar to the following:
public class LanguageConstraint : IRouteConstraint{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values,
RouteDirection routeDirection) {
//create accepted lanaguages collection from somewhere.
string[] languageArray = new[]{"en","jp", "de"};
string language = values["language"].ToString();
if (string.IsNullOrEmpty(language))
return false;
return languageArray.FirstOrDefault(l=>l.Equals(language,StringComparison.InvariantCultureIgnoreCase)) != null;
}
}
Simply it creates a list of known language values and check the provided language value against that list. If it doesn't exist false is returned and a 404 is thrown.

MVC 3 exception: The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Int32' for method 'System.Web.Mvc

My application seems to run fine, but I keep getting these exceptions in log4net logs:
The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Int32' for method 'System.Web.Mvc.ActionResult Agency(Int32)' in 'COPSGMIS.Controllers.QuestionController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter.
Not sure whats going wrong?
My controller:
public ActionResult Agency(int id)
{
QuestionDAL qd = new QuestionDAL();
var agency = qd.GetAgencyDetails(id);
agency.Reviews = qd.GetAgencyReviews(id);
return View(agency);
}
My routes:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
This error is thrown if you attempt to call this controller action and you do not specify the id either in the path portion or as query string parameter. Since your controller action takes an id as parameter you should make sure that you always specify this parameter.
Make sure that when you are requesting this action you have specified a valid id in the url:
http://example.com/somecontroller/agency/123
If you are generating an anchor, make sure there's an id:
#Html.ActionLink("click me", "agency", new { id = "123" })
If you are sending an AJAX request, also make sure that the id is present in the url.
If on the other hand the parameter is optional, you could make it a nullable integer:
public ActionResult Agency(int? id)
but in this case you will have to handle the case where the parameter value is not specified.

WebApi parameters

I am very new to WebApi and don't understand parameters mapping.
I had a controller with HttpGet method with 2 parameters. In WebApiConfig mapping defined like
config.Routes.MapHttpRoute(
name: "MyActionApi",
routeTemplate: "api/{controller}/{action}/{p},{q}");
which seemed to work fine.
By analogy I've added another controller (DetailsController) that has 3 parameter HttpGet method.
I've added
config.Routes.MapHttpRoute(
name: "MyActionApi2",
routeTemplate: "api/{controller}/{action}/{p},{q},{r}");
But navigating to
http://mysite/api/Details/CrossReport/12,14,Peter
gives 404 error and says
No action was found on the controller 'Details' that matches the request.
But navigating like this
http://mysite/api/Details/FilterByDate/12,14?q=10
gives correct results.
Why is that? I'd like to have it comma separated as in the first case. And why it works in first case but not the second one?
Working controller's method:
public IEnumerable<Order> FilterByDate(DateTime dateStart, DateTime dateEnd).
Not working:
public IEnumerable<Detail> FilterByDate(DateTime dateStart, DateTime dateEnd, int maxCount)
Both have HttpGet attribute.
You need to define a matching action. Try adding an action with the following signature on your DetailsController class:
[HttpGet]
public IEnumerable<Detail> CrossReport(string p, string q, string r)
As you see, the action name and parameter names must match what you have on your route.
One thought, have you made sure that MyActionApi2 ahead of MyActionApi in you routing config? If I am not mistaken, It looks for the first possible match... and so MyActionApi would match (even if there are 3 parameters)
There are two types of parameters in WebApi: parameters in routes and parameters in body/url.
Parameter in route
In this example the id param is in the route.
http://mysite/api/Details/CrossReport/{id}
The route params are separate by "/" and there are part of the route.
http://mysite/api/Details/CrossReport/{id}/{name}/{detailId}
In your web api controller you must be:
public IEnumerable<Order> FilterByDate(int id, string name, int detailId)
Parameter in body/url
The parameters in url are separate by & and all this params are after a ? in route. For example.
http://mysite/api/Details/CrossReport?id=3&name="john"&detailId=5
And in your web api controller is the same:
public IEnumerable<Order> FilterByDate(int id, string name, int detailId)
If the object is compound by others properties:
public class MyObject
{
public int Id { get; set; }
public string Name { get; set; }
}
You can only have one in the body of the message and you have to send in the body on the message and your web api controller receipt them:
public IEnumerable<Order> FilterByDate(MyObject obj)

Pass a parameter to a field in another page ASPMVC3

I have a person class that has an index file that lists each person.
An Html.ActionLink points to another controller for notes on the person.
One person, many notes, so I want to pass the PersonID as a param and insert it into the new note form. The parameter is not the NoteID, ie the key
#Html.ActionLink("Note", "Create", "Note", new { id = item.PersonID }, null)
The PersonID is in the url that passes to the note form.
How do I get the PersonID into the Note form?
Many thanks,
Harry
In your Create action method in the Note controller, you will receive the ID as a paramter to the action method if you include a parameter for it in the function:
public ActionResult Create(string id)
{
}
From there it is up to you to pass the id into the view you are using to construct the Create Note page. If you have a Note model object and one of the properties is the PersonID, you might want to new up a new Note object, set the PersonID to the value passed in the parameter, and then pass the new Note object to the view page as the model:
return View(newNote);
Hope that is what you are looking for.
You can knockout it using the same name as u declared ID
Or.. just handle it on another Controller using
the same name. ID:
[HttpGet]
public ViewResult Create(int id)
{
return View(id); <--
}
Then with razor-engine it comes with #Model

Resources