I have a action on my controller (controller name is 'makemagic') called 'dosomething' that takes a nullable int and then returns the view 'dosomething.aspx'. At least this is what I am trying to do. Seems no matter I get routed to the Default() view.
public ActionResult dosomething(int? id)
{
var model = // business logic here to fetch model from DB
return View("dosomething", model);
}
There is a /Views/makemagic/dosomething.aspx file that has the Inherits System.Web.Mvc.ViewPage
Do I need to do something to my routes? I have just the 'stock' default routes in my global.aspx.cs file;
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
}
I am calling the action via a href like this in another page;
Click Me!
Seriously driving me nutso. Any suggestions on how to troubleshoot this? I attempted to debug break on my route definitions and seems a break there doesn't happen as one would expect.
Change it so the parameter isn't nullable so it will match the default route, or change the name to something other than id and supply it as a query parameter. An example of the latter would be:
public ActionResult dosomething(int? foo)
{
var model = // business logic here to fetch model from DB
return View("dosomething", model);
}
Click me
The it will work with the default routing implementation. Alternatively, you could do something that would distinguish it from the default route and then you would be able to have a route for it and not have to use query parameters.
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/foo/{id}", // URL with parameters
new { controller = "makemagic", action = "dosomething", id = "" } // Parameter defaults
);
Click Me!
Related
I want to have some controller with the single method that would allow me to navigate through some hierarchy (file system etc.).
In other words I want to have possibility to access this method with flexible routes and get part of routes as parameter. For example in case of this hierarchy
Root
Sub-folder-A
Sub-folder-B
Sub-folder-C
I want to have access folders with the next routes
mymvcapplication/explorer/root
mymvcapplication/explorer/root/sub-folder-a
mymvcapplication/explorer/root/sub-folder-b/sub-folder-c
What and where should I configure to implement it properly?
To support variable number of url parameter values in the request url, you can mark your method parameter with * prefix in the route definition.
With MVC Attribute routing,
[Route("explorer/root/{*levels}")]
public ActionResult Details(string levels = "")
{
if (String.IsNullOrEmpty(levels))
{
//request for root
}
else
{
var levelArray = levels.Split('/');
//check level array and decide what to do
}
return Content("Make sure to return something valid :) ");
}
The last parameter prefixed with * is like a catch-all parameter which will store anything in the url after explorer/root
So when you request yoursite.com/explorer/root/a/b/c/d , the default model binder will map the value "a/b/c/d" to the levels parameter. You can call the Split method on that string to get an array of url segments.
To enable attribute routing, go to RouteConfig.cs and call the MapMvcAttributeRoutes() method in the RegisterRoutes.
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 }
);
}
Our app has multiple tenants. Every tenant has a short code assigned to them that users know them by. I want to use that code in my URLs as a route parameter, and have Ninject inject a DbContext with the tenant's database connection string into the tenant-specific controllers.
So for examine I have a CarController, and every tenant has their own products. The URLs would look like {tenantcode}/{controller}/{action}. I understand how to do this part.
However, I have several controllers that should NOT be instanced by tenant. Specifically, the home controller, and account controller for login/registration. These don't matter.
So example URLs I need:
myapp.com/ - HomeController
myapp.com/Account/Login - AccountController
myapp.com/GM/Car/Add - CarController that has GM's DbContext injected
myapp.com/Ford/Car/Add - CarController that has Ford's DbContext injected
How can I exclude certain controllers from routes? Running ASP.NET MVC 5.
Many thanks to Darko Z for starting me in the right direction. I ended up using a hybrid of traditional routes, and the new attribute based routing in MVC 5.
First, the "excluded" routes got decorated with the new RouteAttribute class
public class HomeController : Controller
{
private readonly TenantContext context;
public HomeController(TenantContext Context)
{
this.context = Context;
}
//
// GET: http://myapp.com/
// By decorating just this action with an empty RouteAttribute, we make it the "start page"
[Route]
public ActionResult Index(bool Error = false)
{
// Look up and make a nice list of the tenants this user can access
var tenantQuery =
from u in context.Users
where u.UserId == userId
from t in u.Tenants
select new
{
t.Id,
t.Name,
};
return View(tenantQuery);
}
}
// By decorating this whole controller with RouteAttribute, all /Account URLs wind up here
[Route("Account/{action}")]
public class AccountController : Controller
{
//
// GET: /Account/LogOn
public ActionResult LogOn()
{
return View();
}
//
// POST: /Account/LogOn
[HttpPost]
public ActionResult LogOn(LogOnViewModel model, string ReturnUrl)
{
// Log on logic here
}
}
Next, I register the tenant generic route that Darko Z suggested. It's important to call MapMvcAttributeRoutes() before making other routes. This is because my attribute based routes are the "exceptions", and like he said, those exceptions have to be at the top to make sure they are picked up first.
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// exceptions are the attribute-based routes
routes.MapMvcAttributeRoutes();
// tenant code is the default route
routes.MapRoute(
name: "Tenant",
url: "{tenantcode}/{controller}/{action}/{id}",
defaults: new { controller = "TenantHome", action = "Index", id = UrlParameter.Optional }
);
}
}
So as I'm sure you know you specify routes in MVC in the order from most specific to most generic. So in your case I would do something like this:
//exclusions - basically hardcoded, pacing this at the top will
//ensure that these will be picked up first. Of course this means
//you must make sure that tenant codes cannot be the same as any
//controller name here
routes.MapRoute(
"Home",
"Home/{action}/{id}",
new { controller = "Home", action = "Index", id = "" }
);
routes.MapRoute(
"Account",
"Account/{action}/{id}",
new { controller = "Account", action = "Index", id = "" }
);
//tenant generic route
routes.MapRoute(
"Default",
"{tenantcode}/{controller}/{action}",
new { tenantcode = "Default", controller = "Tenant", action = "Index" }
);
//default route
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = "" }
);
This is obviously only good if there are less excluded controllers than controllers that need the tenant code. If not then you can take the opposite approach and reverse the above. Main takeaway here is that (happy to be proven wrong) there is no way to have a generic ignore within an AddRoute call. While there is an IgnoreRoute, that just completely doesn't apply any routing rules and is used for static resources. Hope that helps.
I have a site with the normal, default route and several controllers. I would like to distribute "short URL" links that can link back to the home/index action. For example, I can do
/MySite - takes you to Home/Index as default
/MySite/SomeController/SomeAction - takes you to the specified controller/action as default
but I would also like to do:
/MySite/SomeID - takes you to Home/Index with the id param supplied.
I can add a "shortUrl" route and distribute a url like "/MySite/ShortUrl/SomeID", but is there any other way to use an "id-only" url like the one above?
The problem you've got with doing something like this is that the following would then be ambiguous:
/MySite/SomeID
/MySite/SomeController
How do you expect to be able to differentiate between the two? If you don't mind the second being impossible (i.e. you are happy always specifying an action when you specify a controller), you could try something like this:
routes.MapRoute(
"ShortUrl",
"{id}",
new { controller = "Home", action = "Index", id = Url.OptionalParameter }
);
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = Url.OptionalParameter }
);
Requesting /MySite/SomeID should then take you to the same action as MySite/Home/Index/SomeID.
If you need to be able to specify either and ID or a controller (with default action), you could do something like the following (also using the above routing):
public class HomeController : Controller
{
public ActionResult Index(string id)
{
// If the ID represents something, show that something.
if (IdMatchesSomeResource(id))
{
// Do something
return View();
}
// Otherwise, treat it as a request for a controller.
else
{
return RedirectToAction("Index", id);
}
}
}
I have the following route:
routes.MapRoute(
"Property",
"{language}/property/{propertyUrlId}",
new { controller = "PropertyDetails", action = "Property" }
This is the Controller that should be called for that route:
public class PropertyDetailsController : Controller
{
public ActionResult Property(string language, string propertyUrlId)
{
etc.
And the following URL that should use that route:
http://domain.com/en-us/property/3
Instead, I get 404. Any ideas why?
Here are my routes:
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute(
"Property",
"property/{propertyUrlId}",
//new { controller = "PropertyDetails", action = "Property" }, new { language = #"[a-zA-Z]{2}-[a-zA-Z]{2}" }
new { controller = "PropertyDetails", action = "Property" }
);
}
Didn't work with language, or with language/country, either.
You most likely have registered the default route before your Property route. Default route typically looks like this:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
Just register your Property route BEFORE this default route and it will work.
Why it fails? (Assuming you are indeed registering default route first)
en-us -> is interpreted as controller
property -> is interpreted as action
Since you don't have a en-usController with a Property action -> 404
Use "en-us" as a segment of the URL is completely fine. I guess you have registered other routes as well. Try to bring this route to the top of others and at least on top of the default route.
I have tested the scenario, it works just fine for me.
Considering that you want to have the structure of the url as:
http://domain.com/en-us/property/3
use this routing:
routes.MapRoute(
"Property", // Route name
"{language}/property/{propertyUrlId}", // URL with parameters
new { controller = "PropertyDetails", action = "Property", propertyUrlId = UrlParameter.Optional } // Parameter defaults
);
if there is a default routing in your Global.asax file, like this:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
Put the routint above this block of code.
And your Controller Action should look like this:
public ActionResult Property(int propertyUrlId)
{
return View();
}
First of all, there is no reason to break {language} apart into two chunks in the route. As some of you stated, this is fine:
routes.MapRoute(
"Property",
"{language}/property/{propertyUrlId}",
new { controller = "PropertyDetails", action = "Property" }
Second, I omitted some information which was crucial to the solving of this problem. It didn't occur to me to include this in my problem description, as I didn't know there was any relationship. The MVC project is in a solution which also contains a website (non-MVC) which is using the Sitecore CMS as its datastore. Sitecore was stripping out the language segment of the URL and storing it, itself. Once I learned that this was happening, I was able to deal with the problem.
I appreciate all the input, and I apologize for the confusion.
I'm trying to set up my MVC project to have URLs so that I can go to:
/Groups/
/Groups/Register
/Groups/Whatever
But in my controller, I can also flag some actions as admin only, so that they are accessed at:
/Admin/Groups/Delete/{id}
I would like to keep one GroupController, and have actions so that:
public class GroupController : Controller
{
public ActionResult Index(){
return View();
}
[AdminAction]
public ActionResult Delete(int id){
...
return View();
}
}
Allows:
/Groups is a valid URL.
/Admin/Groups is a valid URL (but would call some other action besides Index - maybe)
/Admin/Groups/Delete/{id} is a valid URL (post only, whatever)
/Groups/Delete is an INVALID url.
I realize this is probably a pretty broad question, but I'm new to MVC and I'm not really sure where to start looking, so if you could just point me in the right direction that would be hugely appreciated.
As we discussed in the comments below, while it is possible to use my original answer below to achieve the routing solution you requested, a better solution is to use Areas, establish an Admin area, and create controllers in your Admin area to handle the administrative tasks for different objects, such as Group, User, etc. This allows you to set up restricted administrative functions more easily, and is both a better design and a better security model.
ORIGINAL ANSWER
What you want can be accomplished by using the following routes:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Admin", // Route name
"admin/{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
However, as Akos said in the comments, it is a much better design to separate the administrative functions into a different controller. While this is possible, I would recommend against using this design.
UPDATE
It is possible to use a RouteConstraint on your Default route to make it fail if Admin actions are requested. The Default route would look like this:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional, // Parameter defaults
new { action = IsNotAdminAction() } // route constraint
);
The RouteConstraint would look like this:
public class IsNotAdminAction : IRouteConstraint
{
private string adminActions = "create~delete~edit";
public IsNotAdminAction()
{ }
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
// return false if there is a match
return !adminActions.Contains(values[parameterName].ToString().ToLowerInvariant());
}
}