What is wrong with my MVC4 Routes, Parameters, constructors? - c#

I have a problem with a small app i am writing. Now either my error is in my controller class or its in Routes. See images below.
Controller Class.
This is the default Route i have.
And this is the error i get when i run.
Image not very clear but it says:
The parameters dictionary contains a null entry for parameter 'playerId' of non-nullable type 'System.Int32' for method 'System.Web.Mvc.ActionResult Detail(Int32)' in 'GlobalUnited.WebUI.Controllers.PlayerController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter.
Parameter name: parameters
I read through some posts on here, one particularly:
Similar Link
What exactly does he mean when Daniel Renshaw says:
First, I would suggest you use MVC's automatic handling of parameters instead ofpulling them out of the Request yourself. Your controller action has an id parameter which seems to go ignored - use that and add others like it to get the input parameters.
Anyways, after reading that post, i changed my RouteConfig file to:
And Still i got this error. It says:
A route named 'DefaultApi' is already in the route collection. Route names must be unique.
Parameter name: name
I even tried to change my Detail action parameter to: Note the int? declaration
And i got this error after changing:
Is there something i could do to fix this, something less complicated??
All help will be appreciated, thanks.

Your parameter name playerID causes the problem here. ASP.NET MVC can only provide you a parameter named id using the default route. In your case, it cannot map the request to your action because playerID is not nullable or optional. Changing your parameter name to id will solve the problem.
public ActionResult Detail(int id)
You get the second error with route registration because you already have a route named "DefaultApi". You won't need this route if you change the parameter name(also it's registered elsewhere).
Third error is for trying to querying with the null value. ASP.NET MVC cannot map the value in URL to your parameter and you get the default value null. Since there isn't a row with a null value you get an empty sequence, then Single() method throws an exception.

As your parameter is called playerId you must pass it as a route value to the Detail action method and then check if it's null or not. This will take care of that last error you show in your question: Sequence contains no element.
Do this in the action method:
public ActionResult Detail(int? playerId)
{
if(playerId.HasValue)
{
var model = _dataSource.Players.Single(p => p.PlayerId == playerId);
return View(model);
}
// Handle the other possibility where playerId is NULL
}
For ASP.NET MVC to know how to do the correct parameter binding you must call the above action method this way, for example:
#Html.ActionLink("Player Details", "Detail", new { playerId = 1 });

Other than the duplicate route error, your errors have nothing to do with the DefaultApi route. You get a duplicate route because that route is configured in App_Start\WebApiConfig.cs
Your first problem is that you are telling MVC that you have a mandatory Route parameter called playerId, but you are not supplying this route parameter in the URL you're using. In order for this to work you would need to either alter your route to change id to playerId, or add a playerId querystring parameter to your url. If you alter the route, you would need a url like http://my.site/Player/Detail/1 (if you alter the route) or http://my.site/Player/Detail?playerId=1.
Another option is changing the parameter to public ActionResult Detail(int id) which would then use the existing default route that takes a single parameter called id and extracts it from the friendly url that ends in /1.
You could also make the method Detail(int? id), but then you would need to place a null guard around your linq query (because if you don't pass the ID on the URL it can't lookup a null record) so you would have to add this:
if (id.HasValue) {
// execute linq query
}
Alternatively, you could alter your query to return SingleOrDefault() rather than Single().

Related

Unable to POST to Web API endpoint using attribute routing

I have a Web API project that uses a default route of "api/{controller}/{id}" where "id" is optional. In most cases this is sufficient but in some cases I need attribute routing. In this specific case, I have "api/Customers/{customerID}/Orders/{orderID}" where customer ID is required and orderID is optional. Originally my code only required the Order ID but I needed a way to pull the orders for a particular customer so I used an attribute route on my methods to allow this.
I am able to perform GET operations without problem, but when I try to do a POST operation, I get a 500 error. What's odd though is my object gets created so the exception that gets thrown must be coming after the database insert is created but I cannot confirm this since my debugger doesn't work. My API is in a separate project from my UI and for whatever reason I cannot get my debugger to work in the API project so the breakpoints I have set don't work.
The last line of code in my POST method is this:
return CreatedAtRoute("DefaultApi", new { id = order.ID }, order);
The first argument of this method is the route name and the one listed above is for the default route specified in WebApiConfig.cs. This particular route however is different from the default:
[Route("api/Customers/{customerID:int}/Orders")]
Could this be the problem? Since the route in question uses two arguments, I would assume that I'd need to specify them in the routeValues (second) argument to the CreatedAtRoute method.
What do I need to do to make this work? I suspect I may have problems performing PUT and DELETE operations as well, but I need to create an object before I can modify or delete it.
Okay, I solved this myself. I need to set the Name attribute on the route and use that as the first argument for the CreatedAtRouteMethod. I just needed to specify the two route values corresponding to the two method arguments. I was also able to perform PUT and DELETE operations without problems.
[Route("api/Customers/{customerID:int}/Orders", Name = "Test")]
return CreatedAtRoute("Test", new { customerID = customer.customerID, orderID = order.ID }, order);

Null Parameter Error

I am having a problem calling an Edit method in an application I am creating. In the view an ActionLink is clicked that should be passing the order number to the Edit method as a parameter and opening an edit page with the info for the order populated in the fields. However upon clicking the link I receive the error:
The parameters dictionary contains a null entry for parameter 'orderNum' of non-nullable type 'System.Int32' for method 'System.Web.Mvc.ViewResult Edit(Int32)' in 'AddressUpdater.WebUI.Controllers.OrderController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter.
Parameter name: parameters
However the parameter is present in the URL. Here are the relevant method:
public ViewResult Edit(int orderNum)
{
Order order = repository.Orders.First(o => o.OrderNumber == orderNum);
return View(order);
}
If if change the parameter to int? orderNum the page will render without an error but none of the data is there.
Most probably there's something wrong with the sending of the data to the action method Edit, i.e. in your action link. Just open some devtool like Firebug or Chrome Dev tools to inspect what is being sent to the server.
When your url looks like
Edit?OrderNumber=1234
then you need to have a matching parameter on your Action method like
public ViewResult Edit(int orderNumber) {...}
Instead
Edit(int orderNum){...}
won't work. Basically url parameter name and action method parameter name have to match (not case sensitive, but the name has to match)

How to get Redirect() to work how I want

I've been having some problems getting redirects after login to work how I want. So I came up with the idea to store the current page in the viewbag and use that to redirect, so if the page is mydomain.com/debate/1 I end up with "/debate/1" stored in the viewbad but when I try to redirect its giving me this complaint
The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Int32' for method 'System.Web.Mvc.ActionResult DebateDetails(Int32)' in 'PoliticalDebate.Controllers.DebateController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter.
Parameter name: parameters
However If I manually type in mydomain.com/Debate/1 it works as expected.
Is there some way to get Redirect to work how I want ?
Since I don't see any code, I can't comment on the way you are trying to do it (which isn't working). On future posts, please post your code. This is one way how you can redirect if you are simply redirecting to the default action on the controller:
return this.RedirectToAction("Index", new { id = debateDetailsID } );
Although it's very hard to tell what you are truly trying to do because you mention debate/1 yet the method being called is DebateDetails which doesn't match (unless you've changed the default routes, again I don't know, there's no code).
Update
According to your comment, you have an error in your MapRoute. Your MapRoute should look like:
routes.MapRoute("Debate Details",
"debate/{id}",
new { controller = "Debate",
action = "DebateDetails",
// this id value is missing
// so it's not being passed to the controller
id = UrlParameter.Optional } );
the answer is there in the complaint, in this particular case you're sending a parameter so, checkout if this is specified
your code must look like redirectToAction("nameOfAction", new {id = yourIdOnViewBag}

The ASP.NET MVC Routing Querystring vs Embedded Value Bewilderment

This has probably been asked already - if so sorry! I couldn't find it.
I am unsure as to how asp is able to decide when to use a query string and "normal looking paths" (Embedded values)
Take this for example:
routes.MapRoute(
"SomePage",
"Net/Fix/{value}",
new { controller = "Net", action = "Index" }
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
I don't know how to explain - I will try.. If I am wrong please explain it
Question 1.
Is the first argument in mapRoute so that we can specify which routing we want to take place when using hyperlinks?
Question 2.
What does the second argument do?
It appears as if the second argument gives you the option of specifying how the routing should occur as below: "Net/Fix/hello" or by specifying placeholders in the form of {controller}/{action}/{somevar}
Question 3:
I assume if nothing is used in question 2 scenario - this specifies default routing that should take place?
Question 4:
How does ASP.NET infer whether to use a query string or an embedded value..
Because for example when I decide to call my page
http:/localhost:portno/Net/Fix/hello
It dutifully prints hello.. but when I do this
http:/localhost:portno/Net/Index/hello
It doesn't work.. unless I do
http:/localhost:portno/Net/Index?value=hello..
Question is... why?!!!
I hope questions were clear.. I will reply to answers (if any later).
The first argument is a route name. Each route should have a unique name, and they can be used for creating links, to assure a link is based on a certain route. It's not important in your case of matching a route.
The second argument is a matching pattern. Literal values are shown in clear, and parameterized values inside curly braces. {}. The parameterized values are not just for specifying the location of a parameter, but also the name of it.
I'm not sure offhand why you would define a route without any matching pattern. Does such an overload of MapRoute() exist?
The reason you get the behavior you do with this url: http:/localhost:portno/Net/Index?value=hello It matches the second (the default) route, not the first.
However, look at the second route pattern:
"{controller}/{action}/{id}"
The controller is the first parameter, action is the second. So with your URL, that request is routed to the Net controller, Index action. the same as your first example.
Because the query string contains a value parameter, that still gets passed to the action method. And it just so happens your action method has a string parameter named value, so it works.

MVC Action with Optional Parameters -- which is better?

Are there any pros/cons of using the following two alternatives in your action signature:
public ActionResult Action(int? x) // get MVC to bind null when no parameter is provided
{
if(x.HasValue)
{
// do something
}
}
OR
public ActionResult Action(int? x = null) // C# optional parameter (virtual overload)
{
if(x.HasValue)
{
// do something
}
}
I have never seen the second action signature in practice and can't see any usefulness of it.
The first one usually covers all the scenarios:
If no parameter is sent (GET /somecontroller/action), the value of the x argument will be null inside the action
If a x parameter is sent, but it is not a valid integer (GET /somecontroller/action?x=abc), the value of the x argument will be null inside the action and the modelstate will be invalid
If a x parameter is sent and the value represents a valid integer (GET /somecontroller/action?x=123), then x will be assigned to it.
In my examples I have used GET requests with query string parameters but obviously the same applies with other HTTP verbs and if x was a route parameter.
You only need to specify the optional parameter value if it is going to be anything else other than null.
MVC3 will automatically set null as the value of your parameter if nothing is specified in the overload, or in the call to the Action.
However, its worth noting that if there are any non-optional parameters after this parameter in the signature, then null would have to be specified in the call.
Therefore its best to put all optional params at the end of the signature.
Best Asp.net MVC solution - use action method selector
Why not simplify controller action methods by removing unnecessary code branch and have this kind of code as seen here:
public ActionResult Index()
{
// do something when there's no id
}
[RequiresRouteValues("id")]
public ActionResult Index(int id)
{
// do something when id is present
}
This is of course possible, as long as you provide the very simple code for RequiresRouteValuesAttribute action method selector. You can find code in this blog post that does exactly this.
By my opinion this is the best possible solution to this problem, because:
It simplifies code by removing unnecessary branch
Makes code easier to maintain (due to lower complexity)
Extends Asp.net MVC framework as it can and should
Keeps parameter types as they should be without the need to make them nullable
etc.
Anyway. All the details about this technique is explained in great detail in linked post.

Categories

Resources