Vince Vince - 3 months ago 78
ASP.NET (C#) Question

ASP.Net MVC RouteAttribute Error with Route Parameter

I'm getting a lot of different errors while trying to add Route mapping to a certain action!

I'm trying to get this route for

GET /Admin/Users/User/1

and
POST /Admin/Users/User


But sadly there is already a User property in Controller!
So i cannot use
public ActionResult User(long id)
because i need to hide the user property (that i need to keep because it's the IPrincipal of the Controller and i still get the same error).

Defining route this way :

// First in RouteConfig
routes.MapMvcAttributeRoutes();

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

// Then i'm registering my Areas.


this controller is in Admin Areas.
UsersController : Controller

[HttpGet]
[Route(Name = "User/{id:long}")]
public ActionResult GetUser(long id)
{
var model = new UserViewModel
{
User = _usersService.GetUser(id),
Roles = _rolesService.GetRoleDropdown()
};

return View("User");
}

[HttpPost]
[Route(Name = "User")]
public ActionResult GetUser(UserViewModel model)
{
if (ModelState.IsValid)
{
_usersService.UpdateUserRoles(model.User);
return RedirectToAction("Index");
}

return View("User", model);
}


Here is the error i'm getting :

The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Int64' for method 'System.Web.Mvc.ActionResult User(Int64)' in 'MyProjectName.Web.Areas.Admin.Controllers.UsersController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter. Parameter name: parameters


I'm not sure to really understand what's wrong!

I checked this page that explain the attributes and i see nothing wrong
https://blogs.msdn.microsoft.com/webdev/2013/10/17/attribute-routing-in-asp-net-mvc-5/

EDIT 1



It still doesn't work, i changed my registration to

routes.MapMvcAttributeRoutes();

AreaRegistration.RegisterAllAreas();

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


Here is my full controller code because now it doesn't work for my Index action that was not in the initial code.

[RouteArea("Admin")]
[RoutePrefix("Users")]
public class UsersController : Controller
{
private readonly IUsersService _usersService;
private readonly IRolesService _rolesService;

public UsersController(
IUsersService usersService,
IRolesService rolesService)
{
_usersService = usersService;
_rolesService = rolesService;
}

[HttpGet]
[Route(Name = "Index")]
public ActionResult Index()
{
var model = new UsersViewModel
{
Users = _usersService.GetUsers()
};

return View(model);
}

[HttpGet]
[Route(Name = "User/{id:long}")]
public new ActionResult User(long id)
{
var model = new UserViewModel
{
User = _usersService.GetUser(id),
Roles = _rolesService.GetRoleDropdown()
};

return View("User");
}

[HttpPost]
[Route(Name = "User")]
public new ActionResult User(UserViewModel model)
{
if (ModelState.IsValid)
{
_usersService.UpdateUserRoles(model.User);
return RedirectToAction("Index");
}

return View("User", model);
}
}


Now, i'm getting while trying to go to my index action:


The current request is ambiguous between the following action methods:
System.Web.Mvc.ActionResult Index() on type
EspaceBiere.Web.Areas.Admin.Controllers.UsersController
System.Web.Mvc.ActionResult User(Int64) on type
EspaceBiere.Web.Areas.Admin.Controllers.UsersController
System.Web.Mvc.ActionResult
User(EspaceBiere.Web.Areas.Admin.ViewModels.Users.UserViewModel) on
type EspaceBiere.Web.Areas.Admin.Controllers.UsersController


and this while trying to go to User Action


A public action method 'User' was not found on controller
'EspaceBiere.Web.Areas.Admin.Controllers.UsersController'.


And my link to the index action was :

tried with
/Admin/Users
for my
Index
Action

tried with
/Admin/Users/User/1
for
User
Action

EDIT 2



Ok, my index is now working perfectly, but my user action still is not working!
I've removed all the Name properties of the RouteAttribute to keep them in the constructor (as template) -> [Route("User/{id:long}")]

Sorry, if i didn't see them on first read!

Here is the link to the action

<a href="@Url.Action("User", "Users", new { Area = "Admin", id = user.UserId })" class="btn btn-warning">
<i class="fa fa-pencil"></i>
</a>


Here is the error

No matching action was found on controller 'EspaceBiere.Web.Areas.Admin.Controllers.UsersController'. This can happen when a controller uses RouteAttribute for routing, but no action on that controller matches the request.


It does work if i write in the URL /Admin/Users/User/1
So how should i write my Url.Action ?

Answer

You haven't completely understood the concept of using attribute routing if that was your intention. Here is an example of how to configure the routes you wanted.

[RouteArea("Admin")]
[RoutePrefix("Users")]
public class UsersController : Controller {
    private readonly IUsersService _usersService;
    private readonly IRolesService _rolesService;

    public UsersController(
        IUsersService usersService,
        IRolesService rolesService) {
        _usersService = usersService;
        _rolesService = rolesService;
    }

    //GET Admin/Users
    //GET Admin/Users/Index
    [HttpGet]
    [Route("")]
    [Route("Index")]
    public ActionResult Index() {
        var model = new UsersViewModel {
            Users = _usersService.GetUsers()
        };

        return View(model);
    }

    //GET Admin/Users/User/1
    [HttpGet]
    [Route("User/{id:long}", Name = "GetUser")]
    public ActionResult GetUser(long id) {
        var model = new UserViewModel {
            User = _usersService.GetUser(id),
            Roles = _rolesService.GetRoleDropdown()
        };

        return View("User");
    }

    //POST Admin/Users/User
    [HttpPost]
    [Route("User")]
    public ActionResult PostUser(UserViewModel model) {
        if (ModelState.IsValid) {
            _usersService.UpdateUserRoles(model.User);
            return RedirectToAction("Index");
        }

        return View("User", model);
    }
}

If you are using both Areas with route attributes, and areas with convention based routes (set by an AreaRegistration class), then you need to make sure that area registration happen after MVC attribute routes are configured, however before the default convention-based route is set. The reason is that route registration should be ordered from the most specific (attributes) through more general (area registration) to the mist generic (the default route) to avoid generic routes from “hiding” more specific routes by matching incoming requests too early in the pipeline.

// First in RouteConfig
routes.MapMvcAttributeRoutes();

// Then register Areas.
AreaRegistration.RegisterAllAreas();

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

Route Names

You can specify a name for a route, in order to easily allow URI generation for it. For example, for the following route:

[Route("User/{id:long}", Name = "GetUser")]
public ActionResult GetUser(long id)

you could generate a link using Url.RouteUrl:

<a href="@Url.RouteUrl("GetUser", new { Area = "Admin", id = user.UserId })" class="btn btn-warning">
    <i class="fa fa-pencil"></i>
</a>
Comments