How do I create a catch all route with the new Attribute routing in MVC
I tried this:
[Route("{pagenode}", Order = 999)]
But when I have a named route like
[Route("contact"]
I get the "Multiple controller types were found that match the URL. This can happen if attribute routes on multiple controllers match the requested URL." error.
This can be done with Attribute Routing if the first "directory" in the path is fixed.
For example, to match anything that hits /questions or /questions/4 or /questions/answers/42 then you would use [Route("questions/{*catchall}"].
You can't do this with Attribute routing, do this the MVC4 way:
Map a route in your routemapper like this:
routes.MapRoute("RouteName","{*url}",new { controller = "YourFancyController", action = "YourAction" });
This will be your catch-all Route.
If you would like to map all the routes to their controller you can do this:
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 });
}
The ability to do this must have changed.
In my default controller, still called 'Home' I have one result method which I want executed for an unrecognised URL structure. The routing attribute is: [Route("{*catchall}")]. This is successfully executed for any old thing.
I have a second method which is always successfully executed based on its route (and I've thrown a few route 'styles' at it to see if it always works). I can only assume that the framework always registers the catch-all route last as this is the behaviour I'm seeing.
This is also a brand new, not configured (except for nuGet packages) MVC 5 project excepting that my methods have been changed to return JsonResult (not even doing their job yet but returning little anonymously typed objects). The catch-all for example returns: Json(new { Message = "Invalid Request" }, JsonRequestBehavior.AllowGet). Yes, yes I set the StatusCode first etc etc, this isn't about MY project ;).
I'm sure I haven't left anything out since there's so little to it but if any clarification is wanted I'll see about adding it.
Related
I am porting a Asp.Net MVC application to Razor Pages.
In some of the controllers of the MVC application it makes use of return View("someOtherView", someModelForOtherView);
How do I port this to Razor Pages?
What I need to do is to transfer the request over to another Razor Page and pass the prepared PageModel to it (the other page does not need to execute OnMethod() but simply render its html.
Or, in other words, I only need to swap the template file that should be rendered with another one.
I cannot use Redirect as there must not be another roundtrip via the browser.
I doubt this is (easily) possible. From the github request that Lerner linked above, it's noted Razor Pages weren't designed to do that.
The closest workaround I was able to achieve was to turn my destination Razor Page into a View. (Hence, no code-behind.) Obviously that will only be possible if your destination page is never directly accessed via URL. For example, if you want to redirect to /Pages/MyPage, and you still need to be able to access the url http://example.com/MyPage, this won't work.
But, say all you want is a generic error or status page. Those don't have to be directly-accessible through URL. This works well for that.
Here's a couple extension methods on PageModel to do it, one that accepts models and one that doesn't:
public static ViewResult View(this PageModel pageModel, string viewName) {
return new ViewResult() {
ViewName = viewName,
ViewData = pageModel.ViewData,
TempData = pageModel.TempData
};
}
public static ViewResult View<TModel>(this PageModel pageModel, string viewName, TModel model) {
var viewDataDictionary = new ViewDataDictionary<TModel>(new EmptyModelMetadataProvider(), new ModelStateDictionary()) {
Model = model
};
foreach (var kvp in pageModel.ViewData) viewDataDictionary.Add(kvp);
return new ViewResult {
ViewName = viewName,
ViewData = viewDataDictionary,
TempData = pageModel.TempData
};
}
FYI, the reason for having to recreate the view dictionary is because the one in your pageModel is going to have a model type specific to the current Page, not to the View you're directing to, and you can't change the Model within a ViewDataDictionary to a different type. MVC would complain and throw an exception.
Usage:
public IActionResult OnGet(string id) {
// check if id is good here
if (idIsNoGood) return this.View("InvalidId", new ErrorModel...);
else {
return Page();
}
}
The above will look for InvalidId.cshtml view, which can be in the same folder as your page, the root /Pages/ folder, or /Pages/Shared/. And it'll still use your Layout too, like any other page.
Just make sure your cshtml file doesn't have a #page directive at the top; this won't work for a Razor page, only a View.
Example InvalidId.cshtml:
#model MyProject.Models.ErrorModel
<h1>Invalid Request</h1>
<p>#Model.Message</p>
Let's assume I have an ActionDescriptor (or MethodInfo) object that points to some action method in my application. I want to get route table's entries (System.Web.Routing.Route objects) associated with this action.
Is there, by any chance, some framework method that might get me this information, or do I have to parse the route table somehow? How would you suggest to do this, in the second case?
That's how I did this:
var routeProvider = new DefaultDirectRouteProvider();
var routeEntries = routeProvider.GetDirectRoutes(
_actionDescriptor.ControllerDescriptor, new[] { _actionDescriptor }, new DefaultInlineConstraintResolver());
Im building a site with JSF and i need to have the following urls:
www.---.com/domain1/page.jsf
www.---.com/domain2/page.jsf
...
www.---.com/domainN/page.jsf
In ASP.NET i was able to do that this way:
routes.MapRoute(
name: "Default",
url: "{domain}/{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional });
I have tried using http://www.ocpsoft.org/rewrite/ like this:
.addRule(Join.path("/{domain}/clients/{page}").to("/clients/{page}.jsf"));
but the effect its not the same.
For example when redirecting to another page inside a beans method with:
return "page?faces-redirect=true";
the url generated by that does not contain the domain part. I cant also get the domain part from the request url because its not there.
How else can i achieve this?
Thanks
I am attempting to switch from RouteConfig to Routing Attributes.
I am following along the Pro ASP.NET MVC 5 book from Adam Freeman and I'm trying to convert the following code that handles the paging of clients.
routes.MapRoute(
name: null,
url: "{controller}/Page{page}",
defaults: new { action = "Index", status = (string)null },
constraints: new { page = #"\d+" }
);
This works great! As I go to different URLs, the links look very nice
http://localhost:65534/Client - Default page
http://localhost:65534/Client/Page2 - Second page
Now I've decided to try out Url Attributes and having a bit of problems when it comes to how 'pretty' the links are. All of the links are working fine, but it's the 'routing rewriting' that I am trying to fix.
Here are the important parts of my controller.
[RoutePrefix("Client")]
[Route("{action=index}/{id:int?}")]
public class ClientController : Controller {
[Route("Page{page:int?}")]
public ActionResult Index(string sortOrder, string search = null, int page = 1) {
With the attribute above the Index, going to /Client or to /Client/Page gives me a 404.
Adding a blank route to catch the default page
[Route("Page{page:int?}")]
[Route]
Works for /Client and /Client/Page3, but now the rewriting of the URL is messed up. Clicking on page 3 of the pager gives me a URL of
http://localhost:65534/Client?page=3
which is not what I want. Changing the routing to
[Route("Page{page:int?}")]
[Route("{page=1:int?}")]
Works almost 100%, but the default link for /Client is now
http://localhost:65534/Client/Page
So, I am now asking for help. How can I correctly convert the original MapRoute to the attributes?
Just use:
[Route("", Order = 1)]
[Route("Page{page:int}", Order = 2)]
UPDATE
Plainly and simply, the routing framework is dumb. It doesn't make decisions about which route is the most appropriate, it merely finds a matching route and returns. If you do something like:
Url.Action("Index", "Client", new { page = 1 })
You're expecting the generated URL to be /Client/Page1, but since you have a route where page is essentially optional, it always will choose that route and append anything it can't stuff into the URL as a querystring, i.e. /Client?page=1. The only way to get around this is to actually name the route you want and use that named route to generate the URL. For example:
[Route("", Order = 1)]
[Route("Page{page:int}", Name = "ClientWithPage", Order = 2)]
And then:
Url.RouteUrl("ClientWithPage", new { page = 1 })
Then, you'll get the route you expect because you're directly referencing it.
UPDATE #2
I'm not sure what you mean by "go into PagedList.MVC and add a name property to it". It doesn't require any core changes to the code because PagedList already has support for custom page links. Just change your pager code to something like:
#Html.PagedListPager((IPagedList)ViewBag.OnePageOfItems, page => Url.RouteUrl("ClientWithPage", new { page = page }))
And you'll get the URL style you want. Attribute routing can be a bit more finicky than traditional routing, but I'd hardly call it useless. It's far more flexible than traditional routing, but that flexibility has some costs.
I am trying to make a custom route but I cannot get it working and even though everything seems okay it always returns 404.
So here are the route defined.
It is defined first before the default and according to route debugger this is the route that gets hit.(Matched Route: Game/{id}/{title})
routes.Add(
"GamesDefault",
new Route("Game/{id}/{title}",
new RouteValueDictionary(new { controller = "Games", action = "ShowGame" }),
new DefaultMvcRouteHandler(urlTranslator, urlFoundAction)));
Here is the path Im trying to reach: /Game/5/test
And this is the Controller declaration. The GamesController is placed in the Controllers folder and its view are in Views/Games/showGames.cshtml.
public GamesController()
{
}
public ActionResult ShowGames(int id, string title)
{
return View(title);
}
The DefaultMvcRouteHandler doesnt do anything fancy.
public class DefaultMvcRouteHandler : IRouteHandler
{
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return new MvcHandler(requestContext);
}
}
The default route works without problems, and I have tried everything I can find like changing the name of the route so it doesnt match any folders or anything like that.
If anyone have any ideas on what more to try I would be most grateful.
As per my comment you are passing incorrect default route values for the controller and action values.
Update your route like so:
routes.Add(
"GamesDefault",
new Route("Game/{id}/{title}",
new RouteValueDictionary(new { controller = "GamesController", action = "ShowGames" }),
new DefaultMvcRouteHandler(urlTranslator, urlFoundAction)));