How can I escape client side routing when wanting to navigate outside the set list of routes setup in the router?
I created a project using Durandal.js and have created SPA's inside different Areas. The problem I ran into is that when I want to navigate outside the current SPA and into another or say back to the home page of the entire application which is not an SPA at all but simply a cshtml page.
What I have tried to do is use Durandal's mapUnknownRoutes handler to intercept and then use window.location.href to navigate out. This works, but when I want to go the home page of the application ("/"), the router matches the "root" of the SPA and doesn't navigate out to the home page but instead the SPA's root route.
The area route in this example is "/spaHome" whose MVC route looks like:
context.MapRoute(
"spaHome_default",
"spaHome/{*catchall}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Here's what I've done to set up the Durandal router:
var routes = [
{ route: ['spaHome', ''], moduleId: 'spaHome', title: "spaHome", hash: "#spaHome"},
{ route: 'one/anotherPage', moduleId: 'one/anotherPage', title: "one/anotherPage", hash: "#one/anotherPage"}
];
router.makeRelative({ moduleId: 'viewmodels' });
router.map(routes)
.mapUnknownRoutes(function (instruction) {
utils.navigateToPath(instruction.fragment);
return false;
})
.activate({ pushState: true, root: "/spaHome" });
Any pointers or leads into the right direction for this would be much appreciated!
After some trial and error I was able to come up with a solution to what I was trying accomplish. It looks like a solid solution, so hopefully it doesn't cause any issues down the road.
I changed the routes array and removed the default route of '' from the first route. This allowed me to have a unknown route to go off of when wanting to hit the normal MVC homepage. I also had to remove the "root" property from the activate function of the router. Which in turn meant I had to explicitly declare the routes in the route array with the extra area portion or the URL ("spaHome/").
Then in my mapUnknownRoutes handler I checked the route for the area portion of the URL, and if that existed, I used the SPA router to show a notfound page for that SPA. Else I assumed that the route exists outside the area and I need to hard navigate to the URL using window.location.href.
var routes = [
{ route: ['spaHome'], moduleId: 'spaHome', title: "spaHome", hash: "#spaHome"},
{ route: 'spaHome/one/anotherPage', moduleId: 'one/anotherPage', title: "one/anotherPage", hash: "#spaHome/one/anotherPage"}
];
router.makeRelative({ moduleId: 'viewmodels' });
router.map(routes)
.mapUnknownRoutes(function (instruction) {
if (instruction.fragment.toLowerCase().indexOf("spaHome/", 0) === -1) {
utils.navigateToPath(instruction.fragment);
} else {
var notfoundRoute = "notfound";
instruction.config.moduleId = notfoundRoute;
history.navigate(notfoundRoute, { trigger: false, replace: true });
}
})
.activate({ pushState: true });
If anyone has a better solution please let me know!
EDIT
Ok, I ran into an issue with the history while doing this. I had to add a line to the Durandal router.js file to stop the previous route from being added to the history queue.
if (!instruction.cancelQueue) {
router.trigger('router:route:before-config', instruction.config, router);
router.trigger('router:route:after-config', instruction.config, router);
queueInstruction(instruction);
}
Edit 2
I also ran into an issue with this method where the navigation doesn't work quite right for IE9 and below.
Related
I'm working on a ASP.NET Core website (previously named ASP.NET 5 / vNext) with Angular. In order for Angular to work I need to have a catch-all route:
app.UseStaticFiles();
app.UseMvc(routes =>
{
// Angular fallback route
routes.MapRoute("angular", "{*url}", new { controller = "Home", action = "Index" });
});
I also have a few files/folders in wwwroot, like:
wwwroot/app
wwwroot/assets
wwwroot/lib
When any requests are made to these paths, for example http://example.com/assets/css/test.css, and the file (test.css) does NOT exist, it should not continue to the fallback route. It should return a 404.
Right now, if the file does not exist it returns the Angular HTML. So, how can I tell it that any path that starts with '/assets' should only be routed / served by UseStaticFiles?
This seems to work:
app.MapWhen(
context => {
var path = context.Request.Path.Value.ToLower();
return
path.StartsWith("/assets") ||
path.StartsWith("/lib") ||
path.StartsWith("/app");
},
config => config.UseStaticFiles());
However, I'm not sure if there are any performance (or other type of) implications. I'll update if I come across any.
It is strange that this common case (since many use SPA) is not covered almost anywhere and everyone has to invent something. I have found that the best way to do that is to add constraint (e.g. do not use the route if there is /api or "." in the path). Unfortunately this is not supported out of the box, but you can write this constraint yourself or copy the one I wrote from here.
There are a bit more details in this post. But generally the code looks like this:
app.UseMvc(routes =>
{
routes.MapRoute(
name: "api",
template: "api/{controller}/{action}");
routes.MapRoute(
name: "angular",
template: "{*url}",
defaults: new {controller = "Home", action = "Index"},
constraints: new {url = new DoesNotContainConstraint(".", "api/") });
});
P.S. Perhaps this constraint exist out of the box now, but I have not found one. Alternatively a RegEx can be used, but simple one should be way faster.
If you are using attribute template routing then you can apply this elegant solution:
[HttpGet("")]
[HttpGet("{*route:regex(^(?!skipped).*$)}")]
public async Task<IActionResult> Default(string route) { throw new NotImplementedException(); }
So all routes started with skipped will be ignored by this action.
If you want multiple prefixes or anything else then just update regex for your needs.
First Get attribute is needed to handle root of site (in this case route variable is null).
Similar to the last question that I asked, but a little more complicated.
I've got an ASP.Net MVC served Angular app.
.when("/Catalog",
{
templateUrl: "/htm/catalog/catalog.htm"
})
.when("/Catalog/:Category1",
{
templateUrl: "/htm/catalog/search.htm"
})
.when("/Catalog/:Category1/:Category2",
{
templateUrl: "/htm/catalog/search.htm"
})
.when("/Catalog/:Category1/:Category2/:Category3",
{
templateUrl: "/htm/catalog/search.htm"
})
.when("/Catalog/:Category1/:Category2/:Category3/:Category4",
{
templateUrl: "/htm/catalog/search.htm"
});
$locationProvider.html5Mode(true);
And the MVC routing looks like this:
// AngularJS Route. This is what allows angular to handle its own routing.
routes.MapRoute(
"hash",
"#/{whatever}/{param}/{param2}/{param3}/{param4}/{param5}",
new
{
controller = "Home",
action = "Index",
param = UrlParameter.Optional,
param2 = UrlParameter.Optional,
param3 = UrlParameter.Optional,
param4 = UrlParameter.Optional,
param5 = UrlParameter.Optional
}
);
// This catches all of the other requests (e.g. /img/logo.jpg);
routes.MapRoute(
"whatever",
"{whatever}/{param}",
new
{
controller = "Home",
action = "Index",
param = UrlParameter.Optional,
param2 = UrlParameter.Optional,
param3 = UrlParameter.Optional,
param4 = UrlParameter.Optional,
param5 = UrlParameter.Optional
}
);
The home/index action just returns my index page:
public void Index()
{
String html = System.IO.File.ReadAllText(HttpContext.Server.MapPath("~/htm/index.htm"));
HttpContext.Response.Write(html);
}
The problem that I'm having is when I try to hit one of those routes with the optional parameters, the page hangs, giving this javascript error:
Error: 10 $digest() iterations reached. Aborting!
Sometimes it will will endlessly render itself (like a mirror looking into a mirror), which makes me think that the "whatever" route is causing problems. Without having those optional parameters on it, the route fails completely. This way the page does render and the parameters are loaded into $routeParams, but the page hangs, presumably because it's rendering itself recursively.
If I take out the optional parameters on the "whatever" MVC route and try to hit the route with the hash notation (/#/Catalog/test), it loads just fine (it doesn't hang and the route parameters are there), but the html5 version (/Catalog/test) fails to find an MVC route.
So clearly my problem is with my routing, but I'm not sure what I'm doing wrong. Can somebody shed some light as ot what the problem might be?
Not sure it will help. But a couple of things to keep in mind.
When $locationProvider.html5Mode(true); hash will only be used by legacy browsers (IE <= 9)
The hash part of the url will not be sent to the server. So you don't need the routes.MapRoute for hash
When a browser requests a URI (i.e /Catalog/test) it will first hit the ASP.NET routing then, when the pages is loaded, it will be handled by AngularJS
It also looks like the routes.MapRoute handler "whatever" send the entire page again, meaning that when angular tries to handle the route and tries to load /htm/catalog/search.htm the ASP.NET route will serve up the page again. This would cause an endless loop, since the page loaded by the angular.js contains its self, and it will try to load it again.
Hope it helps.
I am using nopCommerce 2.2. I am having an issue in URL routing, explained below
I want to use following route.
routes.MapLocalizedRoute("Product",
"{region}/{bookTitle}-{isbn}",
new { controller = "Catalog", action = "Product" },
new[] { "Nop.Web.Controllers" });
So, the URL should look like this.
http://localhost:3129/us/303-tips-for-successful-12345
But unfortunately I am getting error
Server Error in '/' Application.
The resource cannot be found.
Description: HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. Please review the following URL and make sure that it is spelled correctly.
Requested URL: /303-tips-for-successful-12345
In above URL ("/303-tips-for-successful-12345"), the first segment "us" is missing ..
If I use following route in route provider(added static segment "p"),
routes.MapLocalizedRoute("Product",
"p/{region}/{bookTitle}-{isbn}",
new { controller = "Catalog", action = "Product" },
new[] { "Nop.Web.Controllers" });
I will get perfect URL without any error.
http://localhost:3129/p/us/303-tips-for-successful-12345
Your route matches the standard {Controller}/{Action} one in form. The routing engine has no way to know if /us is a Controller or a Region.
I am having a lot of trouble using routing infrastructure of asp.net mvc2. I have following routes registered in my global.asax file
routes.MapRoute(
"strict",
"{controller}.mvc/{docid}/{action}/{id}",
new { action = "Index", id = "", docid = "" },
new { docid = #"\d+"}
);
routes.MapRoute(
"default",
"{controller}.mvc/{action}/{id}",
new { action = "Index", id = "" },
new { docConstraint = new DocumentConstraint() }
);
The problem is with first route ("strict"). Three kind of urls can match first route. mycontroller/23/myaction, mycontroller/23/myaction/12 or mycontroller/23/mvaction/stringid. If I try to use this route without specifying value of id everything works fine for example:
Html.ActionLink("Link text", "ActionName", new{docid = 23});
Everything goes well, but if I use links like:
Html.ActionLink("Link text", "ActionName", new{docid = 23, id = 223})
This will produce url currentcontroller.mvc/23/ActionName/223 that is absolutely correct but when it loads the page it gives a JavaScript error in jquery1.4.2.min.js file.
This is strange: if I change id to someid =223 it will reflect in query string and there will be no JS error.
Edit: I have done some further debugging and found when both id and docid are mentioned in route values one thing is ignored in global.asax that is the ignore path.
routes.RouteExistingFiles = false;
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.ignoreRoute is totally bypassed and I can see names of JS files in route value dictionary while debugging in my controller.
it gives javascript error in
jquery1.4.2.min.js file
The most likely cause for this is that something you are displaying on the page is different and you are performing an action that is causing the error. Can you supply enough of a sample from the rendered page to show what you are using jQuery for?
If we drag scripts from solution explorer to site.master it results in following output
<script type="text/javscript" src="../../scripts/jquery.min.js"></script>
The leading dots (..) are creating the problem. Putting source path in url.content or using /scripts instead of ../../scripts will solve the problem because these leading periods are forcing them to match some route in global.asax.
I have several mvc applications on the same domain, each have their own directory.
mydomain.com/app1
mydomain.com/app2
etc..
When using Url.Content() and Url.Action() when at the root level, 'app1' part is repeated twice in the urls.
// code used to generate the links
<%= Url.Action("tetris", "Apps") %>
Page Url: mydomain.com/app1/
rendered link (broken): mydomain.com/app1/app1/Apps.aspx/tetris
application root appears twice in the rendered url when at the root directory
Page Url: mydomain.com/app1/home.aspx/
rendered link (works): mydomain.com/app1/Apps.aspx/tetris
application root appears once - everything works as expected
my routes - I'm using the routes from Phil haacks blog post
routes.MapRoute(
"Default",
"{controller}.aspx/{action}/{id}",
new { action = "Index", id = "" }
);
routes.MapRoute(
"Root",
"",
new { controller = "Home", action = "Index", id = "" }
);
Any ideas?
That is because applications process web.configs in order of inner most director to outer most directory.
You have two options to fix this behavior.
Specify the namespaces you want the routes to apply to either in the root directory or your sub-directories. http://msdn.microsoft.com/en-us/library/dd504958.aspx
routes.MapRoute(
"Root", "", new {...},
new[] { "MyNamespace.For.Root" }
);
Or in the root directory specify your sub-directories as ignored routes using. http://msdn.microsoft.com/en-us/library/dd505203.aspx
routes.IgnoreRoute("/app1");
routes.IgnoreRoute("/app2");
When I had this issue it was because someone used RouteArea["app1"] attribute on the Controller but also included it on the action GET["apps1/Detail"] instead of just GET["Detail"]