Bhuwan Pandey
Bhuwan Pandey

Reputation: 513

Id coming Null in the Action Method

Don't know why, but my Action method showing null for id. I'm passing id in the url but still its coming null

Controller Action Method

public ActionResult SpecificEmpDetails(string empId)
{
    CustomerBAL customer = new CustomerBAL();
    Customer custs = customer.Employees.Single(emp => emp.EmpID.ToString() == empId);
    ViewBag.Customers = custs;
    return View("EmpDetails");
}

View

   <ul>
            <li>
                Customer ID : @ViewBag.Customers.EmpID
            </li>
            <li>
                Customer Name : @ViewBag.Customers.EmpName
            </li>
            <li>
                Customer Age : @ViewBag.Customers.EmpAge
            </li>
            <li>
                Customer Gender : @ViewBag.Customers.EmpGender
            </li>
            <li>
                Customer Salary : @ViewBag.Customers.EmpSalary
            </li>
        </ul>

Employee Database Context

public class CustomerBAL : DbContext
{
    public DbSet<Customer> Employees { get; set; }
}

Employee Model Class

[Table("tblEmployee")]
public class Customer
{
    [Key]
    public int EmpID { get; set; }
    public string EmpName { get; set; }
    public string EmpGender { get; set; }
    public int EmpAge { get; set; }
    public int EmpSalary { get; set; }
}

Route Config

routes.MapRoute(
               name: "SpecificUser",
               url: "{controller}/{action}/{id}",
               defaults: new { controller = "Test", action = "SpecificEmpDetails", id = UrlParameter.Optional }
           );

URL

localhost:8983/Test/SpecificEmpDetails/1

Id is coming as null, but when I'm changing the ID(in the watch window) during debugging from null to any available id, its showing the required employee data. I'm totally confused why id is not getting passed to the controller action method from the url.

Upvotes: 1

Views: 2783

Answers (2)

NightOwl888
NightOwl888

Reputation: 56849

There are 2 different things going on here.

Routing

First of all, your route definition is:

routes.MapRoute(
           name: "SpecificUser",
           url: "{controller}/{action}/{id}",
           defaults: new { controller = "Test", action = "SpecificEmpDetails", id = UrlParameter.Optional }
       );

You are specifying that the name of the route value that is passed is {id}. Therefore, any URL that matches this route will be entered into the URL table with the route key of id and value that is passed in the URL.

For example, with the URL /Test/SpecificEmpDetails/1, your route table looks like this:

| Key         | Value               |
|-------------|---------------------|
| controller  | Test                |
| action      | SpecificEmpDetails  |
| id          | 1                   |

The reason it looks this way is because you are passing the values in the URL, and they are being converted into route values via the placeholders you defined.

Value Providers

The way MVC gets the values to the action method parameters (or technically, any value of a Model) is through value providers.

Value providers lookup the key from several places, such as query string parameters, form values, and route values to try to match the key names with what are in each of those dictionaries. If the parameter name of the action method matches, it will pass the value through. If there is no match, the value will be the default value of the data type.

You can see the value providers that are registered by default (and what order of precedence they have) by looking at the static ValueProviderFactories.Factories property. You can also remove and add/insert custom value provider factories to this property.

enter image description here

Therefore, with a custom value provider, you could make one name map to another name if you really wanted to. In this case, you could make the request for the key empId match the route value id.

That said, I recommend you do one of the following.

Option 1: Use the Default Route, and stick with the name "Id"

The route you have provided won't work in the same MVC application as the default route. The reason is that it will match any URL that has 0, 1, 2, or 3 segments just like the default route. If a URL of this length is passed to the application, it will match the first route registered and will ignore the other. See Why map special routes first before common routes in asp.net mvc? for a complete explanation.

So, if you just use one route in your application, and use id as both your route key and action method parameter, everything will flow accordingly and will be simpler than using a custom route.

IMO, it probably doesn't make much sense to make the id parameter optional for this particular URL. You have no code in your action method to deal with an empty id value anyway. Note however if you pass the URL /Test/SpecificEmpDetails, it will still match the Default route and match your controller action anyway (with an empty value). To avoid this, you can insert an IgnoreRoute to block the URL from matching in this case.

// Ignore the URL if it is passed with no id, so you get a 404 not found
routes.IgnoreRoute("Test/SpecificEmpDetails");

routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

And your action method:

public ActionResult SpecificEmpDetails(string id)
{
    CustomerBAL customer = new CustomerBAL();
    Customer custs = customer.Employees.Single(emp => emp.EmpID.ToString() == id);
    ViewBag.Customers = custs;
    return View("EmpDetails");
}

Option 2: Use a custom route and change the parameter name to "empId"

If you want a custom route, you must constrain the route so it will not match every value that is passed. In this case, you only want it to match /Test/SpecificEmpDetails/<SomeID>, so the simplest option is just to use literal segments instead of placeholders.

You also need to place your custom route first before the default route or it will be an unreachable execution path.

As above, it will still match the Default route and match your controller action anyway (with a null value, since id is not equal to empId). So you should also insert an IgnoreRoute to block the URL from matching in this case.

// Ignore the URL if it is passed with no empId, so you get a 404 not found
routes.IgnoreRoute("Test/SpecificEmpDetails");

routes.MapRoute(
    name: "SpecificUser",
    url: "Test/SpecificEmpDetails/{empId}",
    defaults: new { controller = "Test", action = "SpecificEmpDetails" }
);

routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

Then you can use your method as you have it in your question.

public ActionResult SpecificEmpDetails(string empId)
{
    CustomerBAL customer = new CustomerBAL();
    Customer custs = customer.Employees.Single(emp => emp.EmpID.ToString() == empId);
    ViewBag.Customers = custs;
    return View("EmpDetails");
}

Upvotes: 2

Kahbazi
Kahbazi

Reputation: 14995

The Id in Url will not pass into empId becasuse in the MapRoute the number after action will goes to Id not empId

Change your action like this and everything will works fine.

public ActionResult SpecificEmpDetails(string Id)
{
    CustomerBAL customer = new CustomerBAL();
    Customer custs = customer.Employees.Single(emp => emp.EmpID.ToString() == Id);
    ViewBag.Customers = custs;
    return View("EmpDetails");
}

If you don't want to change your method, you must use this url in order to fix it :

localhost:8983/Test/SpecificEmpDetails?empId=1

Upvotes: 1

Related Questions