I have an angular application (backed by .Net 4.6 and MVC5) with routes such as
/admin
/admin/manageusers
/admin/export
I can reach these via client side routing if my asp.net application sends me to /Admin.
However, if I refresh the page the MVC routing engine doesn't know where to send me and I end up with a 404.
I've tried as many variations of this as I can think of with no success.
context.MapRoute(
"Admin_root",
"Admin/{*url}",
new { area = "Admin", controller = "Admin", action = "Index" });
Is there a way to tell MVC to ignore what would traditionally be the controller and action and just continue with the Index action?
Is there a way to build a catch all route that will ignore what would typically be the controller and action?
The ASP.NET Core Angular project template includes a call to MapSpaFallbackRoute for exactly this purpose. You can see a detailed explanation of this in Github.
Here's the code example:
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
routes.MapSpaFallbackRoute(
name: "spa-fallback",
defaults: new { controller = "Home", action = "Index" });
});
Here's part of the explanation:
Then, since MapSpaFallbackRoute is last, any other requests that don't appear to be for static files will be served by invoking the Index action on HomeController. This action's view should serve your client-side application code, allowing the client-side routing system to handle whatever URL has been requested.
You'll need a reference to Microsoft.AspNetCore.SpaServices if you don't already have it.
EDIT: Since you've specified that you're not using ASP.NET Core, take a look at this asnwer: How to use ASP.NET MVC and AngularJS routing?. It's a similar approach to what I described but for ASP.NET MVC 5. The solution suggested there does seem to basically be the same as what you've described, however.
Related
This is an MVC 6/WebApi application. I'm attempting to use WebApi on the backend with AngularJS on the frontend.
I have two static files: index.html and login.html
(There will eventually be more static files.)
My Angular app is contained in the index.html while views (such as /login) are loaded from static files (i.e. login.html).
If I go to my root URL, index.html is loaded just fine. However, if I go to /login, I receive a 404 page not found.
Now, let it be said, I DO NOT want to bog down my application with a bunch of controllers and views as it's unnecessary to simply serve static files. That's overkill.
I have my routes setup to serve API calls (i.e. ~/api/whatever). How I get MVC 6 to ignore all other routes?
FYI, it also appears that MapPageRoute is deprecated in MVC 6?
EDIT:
One thing I've done to actually get it working is add my own middleware to intercept the request. The code below is added after all of my default middleware in the Configure method:
app.Use(next => async context =>
{
if (context.Request.Path.Value.StartsWith("/api"))
await next.Invoke(context);
else
await context.Response.WriteAsync(System.IO.File.ReadAllText("index.html"));
});
This seems a bit much and it's just a hack. All it does is allows any request that begins with "/api" to go through (which is then picked up by the MVC middleware), but any other call is served with the contents of the index.html. So, if my requested URL is "/login", the index.html file is served, then Angular looks at the route and loads the login.html file into view.
Any other suggestions?
Okay, so since something didn't exist in the MVC 6/ASP.NET 5 framework, I've created my own middleware that provides a lot more flexibility. It has been added to GitHub and is available through NuGet.
The project page is: https://github.com/a11smiles/AngularMiddleware
I was able do what you are asking here. Basically, I added a catch all route to my index action:
[Route("{*path}")]
meaning if no MVC action does not exist, call my Index action and angular routing will take from there
I have an MVC Razor app that I have just converted to a Single Page Application using AngularJS and Angular UI Router.
I have a problem in that when I go to a URL (via a refresh) such as /settings/options MVC attempts to look for the Options() method on the Settings controller. Once the page has finished loading the Angular routing takes over and shows the correct page.
Because my layout page HAS to have a RenderBody() call (else an exception is thrown) I end up with my page looking correct but having a 404 page at the bottom. The 404 is rendered into RenderBody because it cannot find the Options() method on the Settings controller
How can I fix this? I presume using some kind of MVC Routing but not sure where to start. Here is my current route
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 });
}
This question has a similar problem except they use WebAPI and I have my endpoints within a normal controller. If I use the solution here all my AJAX requests get redirected too
I feel like this question (How to use ASP.NET MVC and AngularJS routing?) might help you. They're using different "Areas" of the site, but it's the same concept for the routing.
You could also look into the URL Rewriter for IIS config and use that to make rules to always route your SPA requests to the angular app...
How do I configure IIS for URL Rewriting an AngularJS application in HTML5 mode?
I am developing an AngularJS 1.4 Application within a Symfony2 Bundle. Symfony provides the "backend" (API) and Angular the frontend (of course).
I am using the new router and stick to the components driven folder approach suggested by several guides and best practice examples. But since I build my JavaScript with gulp and only include the complete Build-Files there are issues with Angular controllers not finding their templates.
I show you my solution, which I don't like:
(function () {
'use strict';
angular
.module('myModule', ['ngNewRouter', 'myModule.dashboard'])
.config(TemplateMapping)
.controller('AppController', AppController);
/* #ngInject */
function AppController ($router) {
$router.config([
{ path: '/', redirectTo: '/dashboard' },
{ path: '/dashboard', component: 'dashboard' }
]);
}
/* #ngInject */
function TemplateMapping($componentLoaderProvider) {
$componentLoaderProvider.setTemplateMapping(function (name) {
return {
'dashboard': '/bundles/mybundle/templates/dashboard/dashboard.html'
}[name];
});
}
}());
I write my Angular Code in src/myBundle/Resources/js/ and gulp puts the final Build to src/myBundle/Resources/public/ which is then available in the Twig Template that holds the Angular app.
What I am doing right now, is basically putting the templates of my components not where they belong (that would be src/myBundle/Resources/js/components/dashboard/ for the dashboard example) but into src/myBundle/Resources/public/templates.
I have to tell the router that the template is located elsewhere through $componentLoaderProvider.setTemplateMapping().
I have two Questions:
Could I solve this template location problem in a more elegant way?
Can I tell the router directly (in the AppController) where the template is?
The cool thing with API based architectures is that you can uncouple the backend app (the API) from the frontend (the AngularJS app).
I recommend to create two separate applications for frontend and backend. Each application should be in its own Git repository and can eventually be hosted on its own server:
The Symfony application follows Symfony Best Practices and exposes only a REST API. It holds all the business logic. It has no template (you can remove Twig). Data will be preferably exposed in JSON.
The AngularJS application relies on all frontend tools you want (Gulp, Yeoman...) and consumed the API. It manages the presentation and contains only "assets" (a static index.html file, JavaScript files, CSS stylesheets, images...).
The API is the contract between the two side and they can evolve separately. Maintenance is eased and the coupling is tight. The frontend app can even be hosted directly on a CDN such as Amazon CloudFront or GitHub pages for maximum performance.
I've written an article detailing this approach and presenting some tools I've built. It uses Symfony and AngularJS.
If you still want to have a single application:
put the AngularJS app in a non-public directory such as app/frontend
configure the Gulp script to write dist files in web/
be careful to CSRF attacks (take a look at my DunglasAngularCsrfBundle)
The best approach as Kévin mentioned is to separate your front-end and back-end in a way that even if you change the back-end to another technology you don't need to change your front-end.
There are also 2 git projects that can help you to have a nicer app.
1) FOSJsRoutingBundle
https://github.com/FriendsOfSymfony/FOSJsRoutingBundle
Which allow you to use your Symfony routing in your JavaScript code
2) angular-schema-form
https://github.com/json-schema-form/angular-schema-form
Which is a dynamic form generator. It creates and validates a form from a JSON schema.
So what I did was to create a Angular Schema Form Serializer in Symfony to convert a Symfony form to a Jason schema which can be used by angular-schema-form to generate my forms dynamically. In this case I don't need to implement form in html. Even if I change the API I just need to provide the same Jason schema for my forms.
The best approach is to separate completely your Angular app from Symfony backend. But if you want to use the Angular in some very little part of your legacy app, you can pass your baseURL to your component, for ex. :
<yourComponent baseurl="{{ app.request.scheme ~ '://' ~
app.request.httpHost ~ app.request.basePath }}"></yourComponent>
and then use it in your templateUrl function
templateUrl: function($attrs) {
return $attrs.baseurl + 'templats/yourComponent.template.html';
}
I'm trying to create an ASP MVC 4 project with Ui-Router, however I've come across a problem.
My current server-side routing configuration looks like this:
// Controller/Action route
routes.MapRoute(
name: "Default",
url: "{controller}/{action}");
// Redirect any other routes to Site/Index so AngularJS can handle routing
// Place routes above this otherwise they will be ignored
routes.MapRoute(
name: "Catch-All Redirect to Index",
url: "{*url}",
defaults: new { controller = "Site", action = "Index" }
);
And client-side
angular.module('loluk.home')
.config(['$stateProvider', function ($stateProvider) {
$stateProvider.state('index', {
url: '',
templateUrl: '/home/index'
});
}]);
Site/Index, the redirect action, simply contains the HTML file that has the ng-app directive. Any other actions/controllers will return API data, or a template. So, in my case, I have Home/Index which returns a template containing <h1>Hello, World!</h1>.
This is all fine and dandy if one of my states in ui-router requests "home/index" via templateUrl for my application. However, the problem I have now is that if I browse to http://localhost/Home/Index, I will see the template in it's raw form - rather than what I am expecting to see, which is the whole application in the Home/Index state. This makes sense as that is how I have configured it.
I initially thought "OK, well I can solve this problem by redirecting everyone to Site/Index (where the main file is stored) and using inline templates". Well, this works well, until you consider that
The HTML file containing index.html is going to get ridiculously large and contain every template
This breaks escaped_fragment crawling
So right now I am at a loss of how to make this work; I could use inlining, but that would make web pages load slowly and break SEO. I could stick with what I have.. but that will break any bookmarks that end-users create.
Making template calls a ChildActionOnly worked well until the fact that ChildActionOnly will return a server 500 (rather than a redirect), and UI-Router appears to not qualify as a "Child Action" as requesting the template through templateUrl also triggered the server 500.
I did come across this question, however it doesn't express how exactly to solve the template situation.
Another avenue I have just pursued is having a templates area that contains all of my templates, and an api area that contains all of my api details (/templates/ and /api/ respectively). This solves the whole reloading page problem, though I am still unsure of how to approach the escaped_fragment crawling from this point of view.
I've accomplished this by creating two Areas in MVC - one for API that routes to /api/ and one for Templates that routes to /templates/. AngularJS will make calls to /template/{controller}/{action} which will return a plain HTML view, and make RESTful calls to /api/{controller} for retrieving data.
It's not a perfect solution but it works.
I have been unable to find any articles on Backbone.Marionette using pushstate withOUT Node.js, or grunt, or require where a serious discussion is made about URL handeling. A user should be able to send a link to an internal page to her grandmother and have the link work, for example. The pushstate functionality seems uniquely ill-suited to the real world. Can someone comment and perhaps provide some links to serious articles on the subject?
Generally speaking, there's really just 2 things you need to do...
Tell Backbone to use pushstate by calling Backbone.history.start({ pushState: true });. You may also need to add something like root: 'myApp' to that parameter if your Marionette app is served from http://example.com/myApp instead of directly on http://example.com.
Configure your web server so that no matter what URL the user requests, the same content (your Marionette app) is returned (without doing a redirect).
The catch is that #2 is implemented differently depending on what type of web server you are using. And you haven't told us what stack you're on. In ASP.NET, for example, this can be handled by setting up a 'catch all' route by adding something like this to your RouteConfig.cs file:
// all requests (except those explicitly handled by another route)
// go to HomeController.Index and then the Backbone router examines
// the URL client-side to determine client-side what to do
routes.MapRoute(
name: "Default",
url: "{*clientRoute}", // this is wildcard which captures the entire URL
defaults: new { controller = "Home", action = "Index" }
);
Other web server stacks will have different ways of achieving the same type of thing.