I'm writing a single-page application with angular2 and MVC5. I'm new to both, though, and I'm having trouble with the routing.
I'd like to match URLs as:
/ -> go to my index page, which bootstraps angular
/api/{controller}/{id?} -> REST API
/{*anythingelse} -> if a file exists there, return it as static content; otherwise if angular can route it, have angular route it; otherwise return 404.
The second point's easy enough, and I can get the client-side routing working if I'm willing to give up 404 returns, but I can't seem to reconcile it all.
It seems like this ought to work:
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "api",
template: "api/{controller}/{id?}");
routes.MapRoute(
name: "spa",
template: "{*anythingelse}",
defaults: new { controller = "Home", action = "Index" });
});
and:
#RouteConfig([
{ path: "/", name: 'Splash', component: SplashView },
{ path: '/accounts/login', name: 'Login', component: LoginView },
{ path: '/accounts/register', name: 'Registration', component: RegistrationView },
{ path: '/home/...', name: 'Home', component: HomeView },
])
But that just serves Index.cshtml for every request that isn't a static file.
I feel like this must already be a solved problem, but I haven't been able to find anything online about it. How does one do this properly?
I'm using "HTML5"-style paths rather than hash-style.
So there are two ways to go about doing it. If you are using the HashLocationStrategy I would strongly encourage you to do this on your server side implementation as I have found it much easier to deal with.
Otherwise you could make your own RouterOutlet component that handled the exceptions. I am not 100% clear on how you could get it to work with your RouterConfig as I have not delved that deep into the routing aspect, but I bet you could see if there exists a route then go there otherwise 404 error. Here is my code that deals with seeing if a user is logged in with Json Web tokens.
import {Directive, Attribute, ElementRef, DynamicComponentLoader} from 'angular2/core';
import {Router, RouterOutlet, ComponentInstruction} from 'angular2/router';
#Directive({
selector: 'router-outlet'
})
export class LoggedInRouterOutlet extends RouterOutlet {
publicRoutes: any;
private parentRouter: Router;
constructor(_elementRef: ElementRef, _loader: DynamicComponentLoader,
_parentRouter: Router, #Attribute('name') nameAttr: string) {
super(_elementRef, _loader, _parentRouter, nameAttr);
this.parentRouter = _parentRouter;
}
activate(instruction: ComponentInstruction) {
if (!localStorage.getItem('jwt') || !tokenNotExpired('jwt')) {//Public Routes does not work with Hash Location Strategy, need to come up with something else.
// todo: redirect to Login, may be there is a better way?
if(localStorage.getItem('jwt')){
localStorage.removeItem('jwt');
}
this.parentRouter.navigate(['Login']);
}
return super.activate(instruction);
}
}
As you can see I handle my checking for the Token, and if they don't have a token they can only go to my login page. Then in your app.component or your bootstrapped component just use this as your router-outlet instead of the original.
Sorry I can't be more helpful but I hope this gets you pointed in the right direction!
I think you're looking for a regex route constraint:
routes.MapRoute("app", "{*anything}",
new { controller = "Home", action = "Index" },
new {anything = new RegexRouteConstraint("^(?!api\\/).+") });
This will prevent your catch all route from mapping to any request that begins with "api/"
Related
I want to open my subdomain admin.example.com and get directly to the admin login page, without the index.html page that comes before and tells me the page is in production environment, to improve usability to the customer and avoid something stupid like admin.example.com/admin.
Maybe I could do it with middlewares, but I'm clueless.
I'm using heroku.
Thank you
This is currently not supported.
Currently we don't support serving the admin from the root, added as a feature request
See https://github.com/strapi/strapi/issues/9302
Workaround
You can redirect / to /admin with a custom middleware.
Create the middleware
You may need to create the directory (mkdir -p middlewares/redirect/).
// middlewares/redirect/index.js
module.exports = () => {
return {
initialize() {
strapi.router.get('/', (ctx) => {
ctx.redirect(strapi.config.get('server.admin.url', '/admin'))
})
},
};
};
Enable it
// config/middleware.js
module.exports = {
settings: {
redirect: {
enabled: true
}
}
}
I made an application where a user can create some papers and see all data in a template. Every paper has a joincode which is generated at its creation.
I defined the join route in my web.php like this:
Route::get('/conceptPaper/lobby/{joincode}', 'App\Http\Controllers\ConceptPaperController#join');
and the join function in the controller the following way:
public function join($joincode)
{
try {
$conceptPaper = ConceptPaper::where('join_code', $joincode)->firstOrFail();
return response()->json($conceptPaper);
} catch(ModelNotFoundException $e)
{
return view('errors.404');
}
}
I used firstOrFail to check if the join code exists. If it exists it should return a response, otherwise it should redirect the user to a custom 404 page.
I created a custom component which gets the join code as a route param and shows the concept paper
{
path: '/conceptPaper/lobby/:joincode',
name: 'conceptPaper',
component: () => import('./views/ConceptPaper.vue')
},
So when the user joins a lobby with the right code he gets redirected to the page with all data from the corresponding paper:
showPaper: async function (conceptPaper) {
const joinCode = conceptPaper.join_code;
this.$router.push({ name: "conceptPaper", params: { joincode: joinCode }, });
},
My problem is that when the user types in the wrong code he still gets redirected to the view. When I check the response in the network tab its shows the 404 page.
I think I built it fundamentally wrong. Can anyone tell me how to do it the right way? When the suer types in the correct join code he should see the ConceptPaper.vue view. When the code is wrong he should be redirected to the 404 page.
From your code I'm assuming that you're using VueJs as an SPA and you're retrieving the data from your laravel backend API.
Based on that, your join function is supposed to return json data that you use in your frontend, but in case the ConceptPaper was not found, you return a view instead of json, which won't change much because you're just changing the data that your front-end receives, but the front-end component is not changed.
What I'd do is remove the try catch block, which will return a 404 response from the API, and handle the 404 case in vue, and create a NotFound view in vue.
Laravel
public function join($joincode)
{
$conceptPaper = ConceptPaper::where('join_code', $joincode)->firstOrFail();
return response()->json($conceptPaper);
}
Vue
router/index.js
const routes = [
// previous routes
{
path: '/not-found',
name: 'NotFound',
component: () => import('../views/NotFound.vue')
}
]
NotFound.vue
<template>
// page here
</template>
export {
name: 'NotFound'
}
And finally handle the not found API call, if you are using axios
axios.get('/conceptPaper/lobby/-1000')
.catch(function (error) {
if (error.response.status === 404) {
this.$router.push('NotFound');
}
});
I am building a Vue.JS & Spring Boot app which I am running through a docker container. The dist folder for Vue is copied to resources/public path and served through the Spring Boot service.
I have set up routes using vue router, but all of these routes return 404 - Not found when entered directly into the browser (but work fine when accesses through the Vue app).
The vue router:
export default new Router({
mode: 'history',
routes: [{
path: '/',
name: 'home',
component: Home
},
{
path: '/result',
name: 'result',
component: Result,
props: true
},
{
path: '/result/:userid',
name: 'autoResult',
component: Result,
props: true
}
]
})
I need the /result/userid to not return a 404 - instead it should get the userid & render the result page. Is this possible to get to work?
Another thing I want to do is to redirect all 404-pages that are not mapped to any api / vue page to return to the start page. I have tried using the spring boot implements ErrorController but I cannot get the redirect to work.
Edit:
I tried adding the following Controller Advice:
#ControllerAdvice
public class WebConfig {
#ExceptionHandler(NoHandlerFoundException.class)
public String renderDefaultPage(NoHandlerFoundException e) {
return "classpath:public/index.html";
}
}
And the following properties:
spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false
spring.mvc.static-path-pattern=/static/**
spring.resources.static-locations=classpath:public/static/
But when I try to access the frontpage now (or any other URL) i get the stackOverflowException and the server starts doing an infinite loop saying this:
2019-03-05 13:26:24.298 WARN 22044 --- [nio-8080-exec-1] o.s.web.servlet.PageNotFound : No mapping for GET /classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/classpath:public/index.html
Solved with the following code:
#Controller
public class RoutesController implements ErrorController {
private static final String PATH = "/error";
#RequestMapping(value = PATH)
public String error() {
return "forward:/";
}
#Override
public String getErrorPath() {
return PATH;
}
}
What's happening in your case is that Spring boot is taking the requests and, because nothing is mapped to the URL is giving you a 404. What you want to happen instead is to allow your Vue.js app to handle the unmapped URL's (IE, redirect any unmapped URL's to your index.html).
The first thing you need to do is adding this to your routes configuration in your router:
export default new Router({
mode: 'history',
routes: [{
path: '/',
name: 'home',
component: Home
},
{
path: '/result',
name: 'result',
component: Result,
props: true
},
{
path: '/result/:userid',
name: 'autoResult',
component: Result,
props: true
},
{
path: '*',
component: NotFound
}
]
})
Here we added an extra route as the last path (because routes are matched sequentially) that renders a component.
After that, you need to make Spring boot redirect every unmatched request to index.html, in order to do that, you want spring to throw an exception when it finds and unmapped route and in the handler of the exception redirect to your index.html.
First, add this line to your application.properties:
spring.mvc.throw-exception-if-no-handler-found=true
And add a ControllerAdvice to handle the thrown Exception, like this:
//In some controller or inside a #ControllerAdvice annotated class
#ExceptionHandler(NoHandlerFoundException.class)
String noHandlerFound(NoHandlerFoundException ex){
return "classpath:index.html";
}
Here you can find a little bit more info on making Spring boot redirect unmapped requests to your index.html
Unfortunately none of the above answers worked for me, luckily I've found this answer in another thread:
https://stackoverflow.com/a/59290035/1254782
It is simple and short and works like a charm for my use case, which might slightly differ from the original question, but I think it is quite similar. It might be useful for someone looking for serving vuejs SPA from spring boot backend.
I'm trying to get ASP.NET Core 2 MVC to route the action based on the HTTP verb via the following code in Startup.cs:
app.UseMvc(routes =>
{
routes.MapRoute(
name: "post",
template: "api/{controller}/{id?}",
defaults: new { action = "Post" },
constraints: new RouteValueDictionary(new { httpMethod = new HttpMethodRouteConstraint("POST") })
);
routes.MapRoute(
name: "delete",
template: "api/{controller}/{id?}",
defaults: new { action = "Delete" },
constraints: new RouteValueDictionary(new { httpMethod = new HttpMethodRouteConstraint("DELETE") })
);
routes.MapRoute(
name: "default",
template: "api/{controller}/{action=Get}/{id?}");
});
I.e.,
If the client calls GET http://example.com/api/foo, that runs the Get() method on my FooController : Controller class.
If they call GET http://example.com/api/foo/123, that runs the Get(int id) method on my FooController : Controller class.
If they call POST http://example.com/api/foo, that runs the Post([FromBody] T postedItem) method on my FooController<T> : Controller class.
If they call POST http://example.com/api/foo/123, that runs the Post(int id, [FromBody] T postedItem) method on my FooController<T> : Controller class.
If they call DELETE http://example.com/api/foo/123, that runs the Delete(int id) method on my FooController : Controller
When I run the project, it doesn't seem to run any of my controllers. I have some Razor pages that respond but all of the controller-based routes just return 404. Not even the default route seems to work.
I've been using https://github.com/ardalis/AspNetCoreRouteDebugger to try and help me narrow the issue down but I'm still not finding the problem. It shows the methods on the controllers as available actions but doesn't list any of the names, templates or constraints added via MapRoute. I'd be glad to know of any other helpful tools as well.
FWIW, I'm trying to use the same verb constraints as here:
https://github.com/aspnet/Routing/blob/2.0.1/src/Microsoft.AspNetCore.Routing/RequestDelegateRouteBuilderExtensions.cs#L252-L268
So I don't recall exactly what the problem turned out to be but the meta-solution is that you can debug routing problems by increasing the log level from "Information" to "Debug". E.g., via appsettings.json:
{
"Logging": {
"Debug": {
"LogLevel": {
"Default": "Debug"
}
},
"Console": {
"LogLevel": {
"Default": "Debug"
}
}
}
}
...then you'll get messages like this in e.g., the Application Output pane of Visual Studio:
[40m[37mdbug[39m[22m[49m: Microsoft.AspNetCore.Routing.RouteConstraintMatcher[1]
Route value '(null)' with key 'httpMethod' did not match the constraint 'Microsoft.AspNetCore.Routing.Constraints.HttpMethodRouteConstraint'.
Microsoft.AspNetCore.Routing.RouteConstraintMatcher:Debug: Route value '(null)' with key 'httpMethod' did not match the constraint 'Microsoft.AspNetCore.Routing.Constraints.HttpMethodRouteConstraint'.
[40m[37mdbug[39m[22m[49m: Microsoft.AspNetCore.Routing.RouteBase[1]
Request successfully matched the route with name 'get' and template 'api/{controller}/{id?}'.
Microsoft.AspNetCore.Routing.RouteBase:Debug: Request successfully matched the route with name 'get' and template 'api/{controller}/{id?}'.
[40m[37mdbug[39m[22m[49m: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
Executing action Contoso.Media.ServiceHost.Controllers.MediaController.Get (Contoso.Media.ServiceHost)
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Debug: Executing action Contoso.Media.ServiceHost.Controllers.MediaController.Get (Contoso.Media.ServiceHost)
I have 2 questions.
1. How can I catch undefined routes and redirect to 404 page?
2. How I can use Vuex actions to get data from api? I know that vuex mutations must be sync and actions async. But I can get data with mutations and can use then promise. but can't in actions or I do anything mistake. Please give me beautiful example for that(component type). I use vuex in laravel mix in Laravel project. Thanks...
Generally speaking, you shouldn't be getting undefined routes if you're defining all of the routes within an app. You can define a redirect in your routes configuration as such:
[
{
path: 'admin/posts/list',
name: 'post-list',
},
{
path: 'admin/posts',
redirect: 'admin/posts/list'
}
]
// you can also redirect to a named route instead
// of a path like this:
{
path: 'admin/posts',
redirect: { name: 'post-list' }
}
If you wanted to do a "catch all" that grabs any paths not matched and redirect to your 404 component/view, then you could do it like this:
{
path: '*',
redirect: '/404'
}
Make sure that is at the bottom of your routes definition as the last route because it will catch any routes in the tree it is above.
As for your question about mutations/actions, asynchronous API requests like fetching data from an API only every happen within actions when you're using Vuex.Taken from the documentation on actions:
actions: {
actionA ({ commit }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('someMutation')
resolve()
}, 1000)
})
}
}