Маршрутизация MVC6 в одностраничное приложение без потери 404

Я пишу одностраничное приложение с angular2 и MVC5. Однако я новичок в обоих, и у меня проблемы с маршрутизацией.

Я хотел бы сопоставить URL-адреса как:

  • / -> перейти на мою индексную страницу, которая загружает angular
  • /api/{controller}/{id?} -> REST API
  • /{*anythingelse} -> если там есть файл, вернуть его как статическое содержимое; в противном случае, если angular может его направить, используйте угловой маршрут; в противном случае вернуть 404.

Второй пункт достаточно прост, и я могу заставить маршрутизацию на стороне клиента работать, если я готов отказаться от возврата 404, но я не могу согласовать все это.

Кажется, это должно работать:

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" });
});

и:

@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 },
])

Но это просто обслуживает Index.cshtml для каждого запроса, который не является статическим файлом.

Я чувствую, что это уже должна быть решенная проблема, но я не смог найти ничего об этом в Интернете. Как это сделать правильно?

Я использую пути в стиле «HTML5», а не в стиле хэш.


person Matt Tsōnto    schedule 20.02.2016    source источник
comment
Использует ли ваш REST API WebApi? WebApi — это отдельная от MVC структура, имеющая собственную отдельную конфигурацию маршрута. Если вы используете MVC (не WebApi), вы не настроили его правильно — вам нужно указать действие по умолчанию defaults: new { action = "Index" } или действие в URL-адресе api/{controller}/{action}/{id?}.   -  person NightOwl888    schedule 22.02.2016
comment
Кроме того, ваше предположение о порядке маршрута невозможно. Angular работает в браузере, поэтому он будет маршрутизироваться первым. В противном случае он попытается использовать настроенные вами маршруты именно в том порядке, в котором вы их настроили на стороне сервера. Как только запрос попадает на сервер, вы не можете снова вернуть управление Angular.   -  person NightOwl888    schedule 22.02.2016
comment
Ах, мои извинения. Я указал MVC5, но на самом деле имел в виду MVC6 в ASP.NET 5. В ASP.NET 5 маршрутизаторы WebAPI и MVC были объединены.   -  person Matt Tsōnto    schedule 22.02.2016
comment
Навигация From-Angular не должна быть проблемой, поскольку я бы обеспечивал навигацию только к существующим страницам и, следовательно, не нуждался бы в 404. Я в основном рассматриваю случай, когда пользователь напрямую вводит URI. Там сервер получает первый взлом и может вернуть 404, если бы знал, какие URI примет клиент. Может быть, я смогу каким-то образом сообщить об этом серверу с помощью инструментов.   -  person Matt Tsōnto    schedule 22.02.2016


Ответы (2)


Итак, есть два способа сделать это. Если вы используете HashLocationStrategy, я настоятельно рекомендую вам сделать это на вашей серверной реализации, так как я обнаружил, что с этим намного проще иметь дело.

В противном случае вы могли бы создать свой собственный компонент RouterOutlet, обрабатывающий исключения. Я не на 100% понимаю, как вы можете заставить его работать с вашим RouterConfig, поскольку я не углублялся в аспект маршрутизации, но я уверен, что вы могли бы увидеть, существует ли маршрут, а затем перейти туда, иначе ошибка 404. Вот мой код, который определяет, вошел ли пользователь в систему с помощью токенов Json Web.

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);
  }
}

Как видите, я сам проверяю токен, и если у них нет токена, они могут перейти только на мою страницу входа. Затем в вашем app.component или вашем загрузочном компоненте просто используйте это как ваш маршрутизатор-выход вместо оригинала.

Извините, я не могу быть более полезным, но я надеюсь, что это направит вас в правильном направлении!

person Morgan G    schedule 21.02.2016
comment
Возможно, я просто туплю, но я не понимаю, как вы переходите от переопределенного RouterOutlet (на стороне клиента) к 404 (на стороне сервера). - person Matt Tsōnto; 22.02.2016
comment
Итак, я пытался показать вам, что вы можете обрабатывать if does not exist in RouterConfig route 404 с такой же функциональностью, потому что каждый полученный URL-адрес будет проходить через ваш новый маршрутизатор-выход и выполнять проверку. - person Morgan G; 22.02.2016
comment
Возможно, вы говорите о ненайденной странице, которая просто говорит 404? Я ищу фактический код ответа HTTP 404 с веб-сервера. - person Matt Tsōnto; 24.02.2016
comment
Ох, хорошо. Возможно, я бы посмотрел на рендеринг на стороне сервера Angular2. Я не очень углублялся в это, но вы могли бы просто обработать 404 на стороне сервера, а затем отправить его в angular. - person Morgan G; 25.02.2016

Я думаю, вы ищете ограничение маршрута регулярного выражения:

routes.MapRoute("app", "{*anything}", 
    new { controller = "Home", action = "Index" }, 
    new {anything = new RegexRouteConstraint("^(?!api\\/).+") });

Это предотвратит сопоставление вашего маршрута перехвата всех запросов с любым запросом, начинающимся с «api/».

person Eric B    schedule 07.08.2016