Sam Jenkins
Sam Jenkins

Reputation: 1314

ASP.NET MVC routing parameters not working with areas

I'm currently playing around with areas and routing within them. What I'm trying to achieve is to be able to have a URL that looks like this;

PracticeAdmin/Practice/[Practice Name]

which I would then be able to add things like Edit and Delete to the end of.

I have achieved this in the past when not working with areas by adding this annotation to the action

[Route("PracticeAdmin/Practices/{practiceName}")]
public ActionResult Details(string practiceName)

this would produce the URLs that I would like. The problem I am having is that when I am trying to do this when using areas I get links that look like this;

PracticeAdmin/Practices?practiceName=Practice1

which is not what I am looking for.

The code that I am using to try and produce this with is

PracticeAdminAreaRegistration.cs

using System.Web.Mvc;

namespace TrainingPortal.Areas.PracticeAdmin
{
    public class PracticeAdminAreaRegistration : AreaRegistration 
    {
        public override string AreaName 
        {
            get 
            {
                return "PracticeAdmin";
            }
        }

        public override void RegisterArea(AreaRegistrationContext context) 
        {
            context.MapRoute(
                "PracticeAdmin_default",
                "PracticeAdmin/{controller}/{action}/{id}",
                new { action = "Index", id = UrlParameter.Optional },
                new[] { "TrainingPortal.Areas.PracticeAdmin.Controllers" }
            );
        }
    }
}

RouteConfig.cs

namespace TrainingPortal
{
    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapMvcAttributeRoutes();

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

I have called MapMvcAttributeRoutes here which I believe should mean that the routes are registered even within areas. I have also tried putting the necessary code within PracticeAdminAreaRegistration to do the same thing with no effect.

PracticeAdminController.cs

namespace TrainingPortal.Areas.PracticeAdmin.Controllers
{
    public partial class PracticesController : Controller
    {
        private TpContext db = new TpContext();

        // GET: PracticeAdmin/Practices
        public virtual ActionResult Index()
        {
            return View(db.Practices.ToList());
        }

        [Route("PracticeAdmin/Practice/{practiceName}")]
        public virtual ActionResult Details(string practiceName)
        {
            if (string.IsNullOrWhiteSpace(practiceName))
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }
            Practice practice = db.Practices.FirstOrDefault(m => m.PracticeName.ToLower() == practiceName);
            if (practice == null)
            {
                return HttpNotFound();
            }
            return View(practice);
        }
        ...

Obviously it carries on with other methods but they all follow the same approach as this one.

Index.cshtml snippet

@Html.ActionLink("Delete", MVC.PracticeAdmin.Practices.Delete(item.PracticeName))
@Html.ActionLink("Delete2", "Delete", new { practiceName = item.PracticeName })

Within PracticeAdminArea/Views/Practices/Index.cshtml I have tried using both T4MVC and the normal ActionLink approach which generate exactly the same link (unsurprisingly).

Summary

I have no idea why the Routes I have specified don't appear when trying to create an ActionLink in an area, so I was wondering whether anyone is able to point me in the direction of how I would be able to fix this and get the URL to look how I would like it to?

Upvotes: 3

Views: 1709

Answers (1)

Sam Jenkins
Sam Jenkins

Reputation: 1314

After a bit of playing around I managed to get it working. The way that I ended up fixing it was by calling AreaRegistration.RegisterAllAreas() from the RouteConfig.RegisterRoutes() after having called MapMvcAttributeRoutes()

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        routes.MapMvcAttributeRoutes();

        AreaRegistration.RegisterAllAreas();

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

As you're not allowed to call this method twice (or ASP.NET gets rather upset with you having registered the same route names twice) I removed the call to AreaRegistration.RegisterAllAreas() from Global.asax leaving it looking like this;

public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        GlobalConfiguration.Configure(WebApiConfig.Register);
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
    }
}

Unfortunately this alone didn't solve the problem, I also had to make a couple modifications to the Controller as well. The modifications were to add the RoutePrefix and RouteArea attributes to the Controller like this;

[RouteArea("PracticeAdmin")]
[RoutePrefix("Practice")]
public partial class PracticesController : Controller
{

This had the added benefit that when specifying the route for a particular action through the Route attribute you didn't have to specify those parts any more, so originally an action's signature would have looked like this;

// GET: PracticeAdmin/Practices/{practiceName}/Members
[Route("PracticeAdmin/Practices/{practiceName}/Members")]
public virtual ActionResult Members(string practiceName)
{

it would now look like this;

// GET: PracticeAdmin/Practices/{practiceName}/Members
[Route("{practiceName}/Members")]
public virtual ActionResult Members(string practiceName)
{

After making all those changes, the website is behaving as expected.

Upvotes: 5

Related Questions