MVC 5 Attribute Routing: Removing Unnecessary Route Attribute From Controller Actions - asp.net-mvc

I have a requirement where the resources on the home controller should be accessed without the need to enter the name of the controller in the URL. For instance, index action should be loaded when I navigate to mysite.com/ and contact page should be loaded using mysite.com/contact and so on.
The controller below satisfies this requirement but I wonder if there is a way to remove Route attributes that have the same name as the controller action?
For example, using [Route("contact")] for action contact does not make sense. When I remove this route attribute, the URL is generated as mysite.com/home/contact whereas I want this to be mysite.com/contact.
[RoutePrefix("")] // Remove the name of the controller from the URL
public class HomeController : Controller
{
public ViewResult Index()
{
return View("Index");
}
[Route("contact")]
public ViewResult Contact()
{
return View("Contact");
}
[Route("about")]
public ViewResult About()
{
return View("About");
}
[Route("terms-and-conditions")]
public ViewResult TermsAndConditions()
{
return View("TermsAndConditions");
}
}
FYI, here is my route config:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
routes.LowercaseUrls = true;
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
and the Application_Start event:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}

Instead of using an empty value as the route prefix, I ended up using the route attribute on specific controller actions like the following:
public class HomeController : Controller
{
public ViewResult Index()
{
return View("Index");
}
[Route("contact-us")]
public ViewResult Contact()
{
return View("Contact");
}
[Route("about-us")]
public ViewResult About()
{
return View("About");
}
[Route("terms-and-conditions")]
public ViewResult TermsAndConditions()
{
return View("TermsAndConditions");
}
}
This ensures that each defined route is accessed from the root of the site.

Related

webapi MVC - Return HomePage when create WebApi Empty

I've created an empty webapi project and I would like to add a homepage View. For some reason, it does not work - it always returns authorization access deny, but I've set the attribute [AllowAnonymous] on HomeController.
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
[AllowAnonymous]
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
return View();
}
}
Can anyone help with this issue please? Thanks

Same route parameters with different controllers' action methods causes an error in Asp .Net MVC

I m using attribute routing feature of Asp .Net Mvc.
My first action is like below which is placed in SurveyController
[Route("{surveyName}")]
public ActionResult SurveyIndex()
{
return View();
}
And my second action is like below which is placed in MainCategoryController
[Route("{categoryUrlKey}")]
public ActionResult Index(string categoryUrlKey)
{
return View();
}
I'm not using convention based routing.
Below is my RouteConfig.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional });
routes.MapAttributeRoutes();
}
Now the problem is when i click to a survey it redirects to the MainCategory/Index route. I know it is because of same route pattern but i cant change this into another thing.
how can I handle this situation?
Thanks
You should prefix the routes on your MainCaregoryController, either at the controller level like this:
[RoutePrefix("category")]
public class MainCategoryController : Controller {
or at the action level like this:
[Route("category/{categoryUrlKey}")]
public ActionResult Index(string categoryUrlKey)
{
return View();
}
Routes should not clash. This route:
[Route("{categoryUrlKey}")]
public ActionResult Index(string categoryUrlKey)
{
return View();
}
matches any string and passes that string value into the action, so without a prefix it will match:
http://localhost/validcategorykey
and
http://localhost/something/id/isthispointmakingsense
and your categoryUrlKey parameter would equal "validcategorykey" in the first instance and "something/id/isthispointmakingsense" in the second.
Now as for this route:
[Route("{surveyName}")]
public ActionResult SurveyIndex()
{
return View();
}
This just won't work period. This needs to be changed to:
[Route("survey/{surveyName}")]
public ActionResult SurveyIndex(string surveyName)
{
return View();
}

Right way to Redirect a URL segment to MVC Controller

I have an existing Controller
public class HomeController : Controller
{
public ActionResult Index()
{
return Redirect("/Scorecard");
}
[OutputCache(Duration = 18000)]
public ActionResult Scorecard()
{
return View();
}
}
This currently Maps to http://siteurl/Home/Scorecard . I wanted to the segment http://siteurl/scorecard to redirect to this Controller Action . What would the best wayt to do this . I tried checking the RequestUrl in Session_Start in Global.aspx but the redirects dont seem to be happening . The other alternative I thought of was using a Different Controller like "ScorecardController" and then having a RedirectToAction("Scorecard","Home") in the Index view there.
you could add a FilterAccess class on your App_Start folder to do something like this:
public class FilterAcess : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
//Redirect
if (HttpContext.Current.Request.Url=="http://siteurl/scorecard"){
context.HttpContext.Response.Redirect("~/Home/Scorecard");
}
}
}
RedirectToAction is better way to do it, because, in case you change routing table later, redirect URL will be in adapted.
public class HomeController: Controller
{
public ActionResult Index()
{
return RedirectToAction("Scorecard");
}
[OutputCache(Duration = 18000)]
public ActionResult Scorecard()
{
return View();
}
}
You should also update RouteTable with additional route, before "Default" route:
public static void RegisterRoutes(RouteCollection routes)
{
routes.LowercaseUrls = true;
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "NoHomeSegmentInUrl",
url: "{action}/{id}",
defaults: new { controller = "Home", id = UrlParameter.Optional });
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
And, for lower case routes you need line routes.LowercaseUrls = true;

Can I have a controller with the same name as an area?

Let's say I have an area named Admin in my MVC application. This area has a HomeController with an Index action like so:
using System.Web.Mvc;
namespace AreasPractice.Areas.Admin.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
return Content("Admin.HomeController");
}
}
}
The area registration for the Admin area has the following defaults for the area related route:
using System.Web.Mvc;
namespace AreasPractice.Areas.Admin
{
public class AdminAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "Admin";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Admin_default",
"Admin/{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
//, new[] { "AreasPractice.Areas.Admin.Controllers" }
);
}
}
}
My application's root area also has a HomeController with an Index action and a route config like so:
using System.Web.Mvc;
namespace AreasPractice.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
return Content("From home controller of the root");
}
}
}
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
, namespaces: new[] { "AreasPractice.Controllers" }
);
}
}
Now, I add an AdminController with an Index action in the root area of my application like so:
using System.Web.Mvc;
namespace AreasPractice.Controllers
{
public class AdminController : Controller
{
public ActionResult Index()
{
return Content("Root -> Admin Controller -> Index action.");
}
}
}
When I run the application, I am expecting to see something interesting, like, may be an exception to the effect, "I can't figure out what route you want."
But when I run the application, it runs just fine and a request for:
/Admin/
yields to the HomeController in the Admin area.
This is because, apparently, and as I recall, the routing mechanism works on a priority basis. It finds the first match and goes with it.
And it is apparently finding that the Admin_default route satisfies the request pattern even before it applies the Default route.
My question(s):
Is my understanding so far correct?
What do I do to play with it? What if I want it to go to the
AdminController in the root area of the application?
Okay, I got the answer after some more thinking. If I just moved the area registration in the global.asax file to after the default route has been registered, it now goes to the AdminController of my root instead of going to the Admin area's HomeController.
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
// Remove from here
// AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
// Put here after the default route has been
// registered via the RouteConfig.RegisterRoutes
// call above
AreaRegistration.RegisterAllAreas();
}
}

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?
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.

Resources