diff --git a/core/lib/Drupal/Core/Entity/Controller/EntityController.php b/core/lib/Drupal/Core/Entity/Controller/EntityController.php
new file mode 100644
index 0000000..919cb4d
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/Controller/EntityController.php
@@ -0,0 +1,135 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\Entity\Controller\EntityController.
+ */
+
+namespace Drupal\Core\Entity\Controller;
+
+use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides generic entity controllers, especially for titles.
+ */
+class EntityController implements ContainerInjectionInterface {
+
+  use StringTranslationTrait;
+
+  /**
+   * The entity manager.
+   *
+   * @var \Drupal\Core\Entity\EntityManagerInterface
+   */
+  protected $entityManager;
+
+  /**
+   * Constructs a new EntityController.
+   *
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
+   *   The entity manager.
+   * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
+   *   The string translation.
+   */
+  public function __construct(EntityManagerInterface $entity_manager, TranslationInterface $string_translation) {
+    $this->entityManager = $entity_manager;
+    $this->stringTranslation = $string_translation;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('entity.manager'),
+      $container->get('string_translation')
+    );
+  }
+
+  /**
+   * Provides a generic title callback for a single entity.
+   *
+   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
+   *   The route match.
+   * @param \Drupal\Core\Entity\EntityInterface $_entity
+   *   (optional) A entity, passed in directly from the request attributes.
+   *
+   * @return string
+   *   The title.
+   */
+  public function title(RouteMatchInterface $route_match, EntityInterface $_entity = NULL) {
+    if ($entity = $this->doGetEntity($route_match, $_entity)) {
+      return $entity->label();
+    }
+  }
+
+  /**
+   * Provides a generic edit title callback.
+   *
+   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
+   *   The route match.
+   * @param \Drupal\Core\Entity\EntityInterface $_entity
+   *   (optional) A entity, passed in directly from the request attributes.
+   *
+   * @return string
+   *   The 'edit entity' title.
+   */
+  public function editTitle(RouteMatchInterface $route_match, EntityInterface $_entity = NULL) {
+    if ($entity = $this->doGetEntity($route_match, $_entity)) {
+      return $this->t('Edit %label', ['%label' => $entity->label()]);
+    }
+  }
+
+  /**
+   * Provides a generic delete title callback.
+   *
+   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
+   *   The route match.
+   * @param \Drupal\Core\Entity\EntityInterface $_entity
+   *   (optional) A entity, passed in directly from the request attributes.
+   *
+   * @return string
+   *   The 'delete entity' title.
+   */
+  public function deleteTitle(RouteMatchInterface $route_match, EntityInterface $_entity = NULL) {
+    if ($entity = $this->doGetEntity($route_match, $_entity)) {
+      return $this->t('Delete %entity_type_label', ['@entity_type_label' => $entity->getEntityType()->getLabel()]);
+    }
+  }
+
+  /**
+   * Determines the entity.
+   *
+   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
+   *   The route match.
+   * @param \Drupal\Core\Entity\EntityInterface $_entity
+   *   (optional) The entity.
+   *
+   * @return \Drupal\Core\Entity\EntityInterface|NULL
+   *   Returns the entity, otherwise NULL.
+   */
+  protected function doGetEntity(RouteMatchInterface $route_match, EntityInterface $_entity = NULL) {
+    if ($_entity) {
+      $entity = $_entity;
+    }
+    else {
+      // Let's look up in the route object for the name of upcasted values.
+      foreach ($route_match->getParameters() as $parameter) {
+        if ($parameter instanceof EntityInterface) {
+          $entity = $parameter;
+          break;
+        }
+      }
+    }
+    if ($entity) {
+      return $this->entityManager->getTranslationFromContext($entity);
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/Routing/AdminHtmlRouteProvider.php b/core/lib/Drupal/Core/Entity/Routing/AdminHtmlRouteProvider.php
new file mode 100644
index 0000000..4e6c197
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/Routing/AdminHtmlRouteProvider.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\Entity\Routing\AdminHtmlRouteProvider.
+ */
+
+namespace Drupal\Core\Entity\Routing;
+
+use Drupal\Core\Entity\EntityTypeInterface;
+
+/**
+ * Provides routes for the taxonomy_term entity.
+ */
+class AdminHtmlRouteProvider extends DefaultHtmlRouteProvider {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getEditFormRoute(EntityTypeInterface $entity_type) {
+    if ($route = parent::getEditFormRoute($entity_type)) {
+      $route->setOption('_admin_route', TRUE);
+      return $route;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getDeleteFormRoute(EntityTypeInterface $entity_type) {
+    if ($route = parent::getEditFormRoute($entity_type)) {
+      $route->setOption('_admin_route', TRUE);
+      return $route;
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/Routing/DefaultHtmlRouteProvider.php b/core/lib/Drupal/Core/Entity/Routing/DefaultHtmlRouteProvider.php
new file mode 100644
index 0000000..fac1598
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/Routing/DefaultHtmlRouteProvider.php
@@ -0,0 +1,130 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider.
+ */
+
+namespace Drupal\Core\Entity\Routing;
+
+use Drupal\Core\Entity\EntityTypeInterface;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * Provides a default implementation of an HTML route provider.
+ *
+ * It provides:
+ * - A view route with title callback.
+ * - An edit route with title callback.
+ * - A delete route with title callback.
+ */
+class DefaultHtmlRouteProvider implements EntityRouteProviderInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRoutes(EntityTypeInterface $entity_type) {
+    $collection = new RouteCollection();
+
+    $entity_type_id = $entity_type->id();
+
+    if ($route = $this->getCanonicalRoute($entity_type)) {
+      $collection->add("entity.{$entity_type_id}.canonical", $route);
+    }
+
+    if ($route = $this->getEditFormRoute($entity_type)) {
+      $collection->add("entity.{$entity_type_id}.edit_form", $route);
+    }
+
+    if ($route = $this->getDeleteFormRoute($entity_type)) {
+      $collection->add("entity.{$entity_type_id}.delete_form", $route);
+    }
+
+    return $collection;
+  }
+
+  /**
+   * Gets the canonical route.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The entity type.
+   *
+   * @return \Symfony\Component\Routing\Route|null
+   *   The generated route, if available.
+   */
+  protected function getCanonicalRoute(EntityTypeInterface $entity_type) {
+    if ($entity_type->hasLinkTemplate('canonical') && $entity_type->hasViewBuilderClass()) {
+      $entity_type_id = $entity_type->id();
+      $route = (new Route($entity_type->getLinkTemplate('canonical')));
+      $route
+        ->addDefaults([
+          '_entity_view' => "{$entity_type_id}.full",
+          '_title_callback' => '\Drupal\Core\Entity\Controller\EntityController::title',
+        ])
+        ->setRequirement('_entity_access', "{$entity_type_id}.view")
+        ->setOption('parameters', [
+          $entity_type_id => ['type' => 'entity:' . $entity_type_id],
+        ]);
+      return $route;
+    }
+  }
+
+  /**
+   * Gets the edit-form route.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The entity type.
+   *
+   * @return \Symfony\Component\Routing\Route|null
+   *   The generated route, if available.
+   */
+  protected function getEditFormRoute(EntityTypeInterface $entity_type) {
+    if ($entity_type->hasLinkTemplate('edit-form')) {
+      $entity_type_id = $entity_type->id();
+      $route = (new Route($entity_type->getLinkTemplate('edit-form')));
+      // Use the "edit" form handler, otherwise default.
+      $operation = 'default';
+      if ($entity_type->getFormClass('edit')) {
+        $operation = 'edit';
+      }
+      $route
+        ->setDefaults([
+          '_entity_form' => "{$entity_type_id}.{$operation}",
+          '_title_callback' => 'Drupal\Core\Entity\Controller\EntityController::editTitle'
+        ])
+        ->setRequirement('_entity_access', "{$entity_type_id}.update")
+        ->setOption('parameters', [
+          $entity_type_id => ['type' => 'entity:' . $entity_type_id],
+        ]);
+      return $route;
+    }
+  }
+
+  /**
+   * Gets the delete-form route.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The entity type.
+   *
+   * @return \Symfony\Component\Routing\Route|null
+   *   The generated route, if available.
+   */
+  protected function getDeleteFormRoute(EntityTypeInterface $entity_type) {
+    if ($entity_type->hasLinkTemplate('delete-form')) {
+      $entity_type_id = $entity_type->id();
+      $route = (new Route($entity_type->getLinkTemplate('delete-form')));
+      $route
+        ->addDefaults([
+          '_entity_form' => "{$entity_type_id}.delete",
+          '_title_callback' => 'Drupal\Core\Entity\Controller\EntityController::deleteTitle',
+        ])
+        ->setRequirement('_entity_access', "{$entity_type_id}.delete")
+        ->setOption('parameters', [
+          $entity_type_id => ['type' => 'entity:' . $entity_type_id],
+        ]);
+      return $route;
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Core/EventSubscriber/EntityRouteProviderSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/EntityRouteProviderSubscriber.php
index d5c4def..9674bf3 100644
--- a/core/lib/Drupal/Core/EventSubscriber/EntityRouteProviderSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/EntityRouteProviderSubscriber.php
@@ -48,12 +48,14 @@ public function onDynamicRouteEvent(RouteBuildEvent $event) {
         foreach ($this->entityManager->getRouteProviders($entity_type->id()) as $route_provider) {
           // Allow to both return an array of routes or a route collection,
           // like route_callbacks in the routing.yml file.
+
           $routes = $route_provider->getRoutes($entity_type);
           if ($routes instanceof RouteCollection) {
-            $route_collection->addCollection($routes);
+            $routes = $routes->all();
           }
-          elseif (is_array($routes)) {
-            foreach ($routes as $route_name => $route) {
+          foreach ($routes as $route_name => $route) {
+            // Don't override static defined routes.
+            if (!$route_collection->get($route_name)) {
               $route_collection->add($route_name, $route);
             }
           }
diff --git a/core/modules/aggregator/aggregator.routing.yml b/core/modules/aggregator/aggregator.routing.yml
index 3109e9e..d4cf78c 100644
--- a/core/modules/aggregator/aggregator.routing.yml
+++ b/core/modules/aggregator/aggregator.routing.yml
@@ -49,34 +49,6 @@ aggregator.feed_add:
   options:
     _admin_route: TRUE
 
-entity.aggregator_feed.canonical:
-  path: '/aggregator/sources/{aggregator_feed}'
-  defaults:
-    _entity_view: 'aggregator_feed'
-    _title_callback: '\Drupal\aggregator\Controller\AggregatorController::feedTitle'
-  requirements:
-    _permission: 'access news feeds'
-
-entity.aggregator_feed.edit_form:
-  path: '/aggregator/sources/{aggregator_feed}/configure'
-  defaults:
-    _entity_form: 'aggregator_feed.default'
-    _title: 'Configure'
-  requirements:
-    _permission: 'administer news feeds'
-  options:
-    _admin_route: TRUE
-
-entity.aggregator_feed.delete_form:
-  path: '/aggregator/sources/{aggregator_feed}/delete'
-  defaults:
-    _entity_form: 'aggregator_feed.delete'
-    _title: 'Delete feed'
-  requirements:
-    _permission: 'administer news feeds'
-  options:
-    _admin_route: TRUE
-
 aggregator.page_last:
   path: '/aggregator'
   defaults:
diff --git a/core/modules/aggregator/src/Entity/Feed.php b/core/modules/aggregator/src/Entity/Feed.php
index a5ed081..8442517 100644
--- a/core/modules/aggregator/src/Entity/Feed.php
+++ b/core/modules/aggregator/src/Entity/Feed.php
@@ -30,7 +30,10 @@
  *       "default" = "Drupal\aggregator\FeedForm",
  *       "delete" = "Drupal\aggregator\Form\FeedDeleteForm",
  *       "delete_items" = "Drupal\aggregator\Form\FeedItemsDeleteForm",
- *     }
+ *     },
+ *     "route_provider" = {
+ *       "html" = "Drupal\aggregator\FeedHtmlRouteProvider",
+ *     },
  *   },
  *   links = {
  *     "canonical" = "/aggregator/sources/{aggregator_feed}",
diff --git a/core/modules/aggregator/src/FeedHtmlRouteProvider.php b/core/modules/aggregator/src/FeedHtmlRouteProvider.php
new file mode 100644
index 0000000..6ccfa7c
--- /dev/null
+++ b/core/modules/aggregator/src/FeedHtmlRouteProvider.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\aggregator\FeedHtmlRouteProvider.
+ */
+
+namespace Drupal\aggregator;
+
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\Routing\AdminHtmlRouteProvider;
+
+/**
+ * Provides HTML routes for the feed entity type.
+ */
+class FeedHtmlRouteProvider extends AdminHtmlRouteProvider {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getEditFormRoute(EntityTypeInterface $entity_type) {
+    $route = parent::getEditFormRoute($entity_type);
+
+    $route->setDefault('_title', 'Configure');
+
+    return $route;
+  }
+
+}
diff --git a/core/modules/block_content/block_content.links.task.yml b/core/modules/block_content/block_content.links.task.yml
index 2b88171..fc2b024 100644
--- a/core/modules/block_content/block_content.links.task.yml
+++ b/core/modules/block_content/block_content.links.task.yml
@@ -12,14 +12,14 @@ entity.block_content_type.collection:
   parent_id: entity.block_content.collection
   weight: 1
 
-entity.block_content.canonical:
+entity.block_content.edit_form:
   title: Edit
-  route_name: entity.block_content.canonical
-  base_route: entity.block_content.canonical
+  route_name: entity.block_content.edit_form
+  base_route: entity.block_content.edit_form
 entity.block_content.delete_form:
   title: Delete
   route_name: entity.block_content.delete_form
-  base_route: entity.block_content.canonical
+  base_route: entity.block_content.edit_form
 
 # Default tab for custom block type editing.
 entity.block_content_type.edit_form:
diff --git a/core/modules/block_content/block_content.routing.yml b/core/modules/block_content/block_content.routing.yml
index 3542ca8..e9a4747 100644
--- a/core/modules/block_content/block_content.routing.yml
+++ b/core/modules/block_content/block_content.routing.yml
@@ -36,34 +36,6 @@ entity.block_content_type.delete_form:
   options:
     _admin_route: TRUE
 
-entity.block_content.canonical:
-  path: '/block/{block_content}'
-  defaults:
-    _entity_form: 'block_content.edit'
-  options:
-    _admin_route: TRUE
-  requirements:
-    _entity_access: 'block_content.update'
-
-entity.block_content.edit_form:
-  path: '/block/{block_content}'
-  defaults:
-    _entity_form: 'block_content.edit'
-  options:
-    _admin_route: TRUE
-  requirements:
-    _entity_access: 'block_content.update'
-
-entity.block_content.delete_form:
-  path: '/block/{block_content}/delete'
-  defaults:
-    _entity_form: 'block_content.delete'
-    _title: 'Delete'
-  options:
-    _admin_route: TRUE
-  requirements:
-    _entity_access: 'block_content.delete'
-
 block_content.type_add:
   path: '/admin/structure/block/block-content/types/add'
   defaults:
diff --git a/core/modules/block_content/src/Entity/BlockContent.php b/core/modules/block_content/src/Entity/BlockContent.php
index 82e16f5..f1dce0a 100644
--- a/core/modules/block_content/src/Entity/BlockContent.php
+++ b/core/modules/block_content/src/Entity/BlockContent.php
@@ -34,7 +34,10 @@
  *       "delete" = "Drupal\block_content\Form\BlockContentDeleteForm",
  *       "default" = "Drupal\block_content\BlockContentForm"
  *     },
- *     "translation" = "Drupal\block_content\BlockContentTranslationHandler"
+ *     "translation" = "Drupal\block_content\BlockContentTranslationHandler",
+ *     "route_provider" = {
+ *       "html" = "Drupal\Core\Entity\Routing\AdminHtmlRouteProvider",
+ *     },
  *   },
  *   admin_permission = "administer blocks",
  *   base_table = "block_content",
@@ -43,7 +46,7 @@
  *   links = {
  *     "canonical" = "/block/{block_content}",
  *     "delete-form" = "/block/{block_content}/delete",
- *     "edit-form" = "/block/{block_content}",
+ *     "edit-form" = "/block/{block_content}/edit",
  *     "collection" = "/admin/structure/block/block-content",
  *   },
  *   translatable = TRUE,
diff --git a/core/modules/block_content/src/Tests/PageEditTest.php b/core/modules/block_content/src/Tests/PageEditTest.php
index fdd1743..b38ed09 100644
--- a/core/modules/block_content/src/Tests/PageEditTest.php
+++ b/core/modules/block_content/src/Tests/PageEditTest.php
@@ -37,7 +37,7 @@ public function testPageEdit() {
     $this->assertTrue($block, 'Custom block found in database.');
 
     // Load the edit page.
-    $this->drupalGet('block/' . $block->id());
+    $this->drupalGet($block->urlInfo('edit-form'));
     $this->assertFieldByName($title_key, $edit[$title_key], 'Title field displayed.');
     $this->assertFieldByName($body_key, $edit[$body_key], 'Body field displayed.');
 
@@ -49,7 +49,7 @@ public function testPageEdit() {
     $this->drupalPostForm(NULL, $edit, t('Save'));
 
     // Edit the same block, creating a new revision.
-    $this->drupalGet("block/" . $block->id());
+    $this->drupalGet($block->urlInfo('edit-form'));
     $edit = array();
     $edit['info[0][value]'] = $this->randomMachineName(8);
     $edit[$body_key] = $this->randomMachineName(16);
@@ -62,7 +62,7 @@ public function testPageEdit() {
     $this->assertNotIdentical($block->getRevisionId(), $revised_block->getRevisionId(), 'A new revision has been created.');
 
     // Test deleting the block.
-    $this->drupalGet("block/" . $revised_block->id());
+    $this->drupalGet($block->urlInfo('edit-form'));
     $this->clickLink(t('Delete'));
     $this->assertText(format_string('Are you sure you want to delete the custom block !label?', array('!label' => $revised_block->label())));
   }
diff --git a/core/modules/comment/comment.routing.yml b/core/modules/comment/comment.routing.yml
index 967cb3f..a8256d6 100644
--- a/core/modules/comment/comment.routing.yml
+++ b/core/modules/comment/comment.routing.yml
@@ -16,14 +16,6 @@ comment.admin_approval:
   requirements:
     _permission: 'administer comments'
 
-entity.comment.edit_form:
-  path: '/comment/{comment}/edit'
-  defaults:
-    _title: 'Edit'
-    _entity_form: 'comment.default'
-  requirements:
-    _entity_access: 'comment.update'
-
 comment.approve:
   path: '/comment/{comment}/approve'
   defaults:
@@ -34,22 +26,6 @@ comment.approve:
     _entity_access: 'comment.approve'
     _csrf_token: 'TRUE'
 
-entity.comment.canonical:
-  path: '/comment/{comment}'
-  defaults:
-    _title_callback: '\Drupal\comment\Controller\CommentController::commentPermalinkTitle'
-    _controller: '\Drupal\comment\Controller\CommentController::commentPermalink'
-  requirements:
-    _entity_access: 'comment.view'
-
-entity.comment.delete_form:
-  path: '/comment/{comment}/delete'
-  defaults:
-    _title: 'Delete'
-    _entity_form: 'comment.delete'
-  requirements:
-    _entity_access: 'comment.delete'
-
 comment.reply:
   path: '/comment/reply/{entity_type}/{entity}/{field_name}/{pid}'
   defaults:
@@ -88,16 +64,6 @@ entity.comment_type.collection:
   options:
     _admin_route: TRUE
 
-entity.comment_type.delete_form:
-  path: '/admin/structure/comment/manage/{comment_type}/delete'
-  defaults:
-    _entity_form: 'comment_type.delete'
-    _title: 'Delete'
-  requirements:
-    _entity_access: 'comment_type.delete'
-  options:
-    _admin_route: TRUE
-
 entity.comment_type.add_form:
   path: '/admin/structure/comment/types/add'
   defaults:
@@ -107,13 +73,3 @@ entity.comment_type.add_form:
     _permission: 'administer comment types'
   options:
     _admin_route: TRUE
-
-entity.comment_type.edit_form:
-  path: '/admin/structure/comment/manage/{comment_type}'
-  defaults:
-    _entity_form: 'comment_type.edit'
-    _title: 'Edit'
-  requirements:
-    _entity_access: 'comment_type.update'
-  options:
-    _admin_route: TRUE
diff --git a/core/modules/comment/src/CommentHtmlRouteProvider.php b/core/modules/comment/src/CommentHtmlRouteProvider.php
new file mode 100644
index 0000000..d2d3c66
--- /dev/null
+++ b/core/modules/comment/src/CommentHtmlRouteProvider.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\comment\CommentHtmlRouteProvider.
+ */
+
+namespace Drupal\comment;
+use Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider;
+use Drupal\Core\Entity\EntityTypeInterface;
+
+/**
+ * Provides HTML routes for the comment entity type.
+ */
+class CommentHtmlRouteProvider extends DefaultHtmlRouteProvider {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getCanonicalRoute(EntityTypeInterface $entity_type) {
+    $route = parent::getCanonicalRoute($entity_type);
+
+    $route->setDefaults([
+      '_title_callback' => '\Drupal\comment\Controller\CommentController::commentPermalinkTitle',
+      '_controller' => '\Drupal\comment\Controller\CommentController::commentPermalink',
+    ]);
+
+    return $route;
+  }
+
+}
diff --git a/core/modules/comment/src/Entity/Comment.php b/core/modules/comment/src/Entity/Comment.php
index bd3fbfc..e188be8 100644
--- a/core/modules/comment/src/Entity/Comment.php
+++ b/core/modules/comment/src/Entity/Comment.php
@@ -35,7 +35,10 @@
  *       "default" = "Drupal\comment\CommentForm",
  *       "delete" = "Drupal\comment\Form\DeleteForm"
  *     },
- *     "translation" = "Drupal\comment\CommentTranslationHandler"
+ *     "translation" = "Drupal\comment\CommentTranslationHandler",
+ *     "route_provider" = {
+ *       "html" = "Drupal\comment\CommentHtmlRouteProvider",
+ *     },
  *   },
  *   base_table = "comment",
  *   data_table = "comment_field_data",
diff --git a/core/modules/comment/src/Entity/CommentType.php b/core/modules/comment/src/Entity/CommentType.php
index 94fd193..2b0951f 100644
--- a/core/modules/comment/src/Entity/CommentType.php
+++ b/core/modules/comment/src/Entity/CommentType.php
@@ -24,7 +24,10 @@
  *       "edit" = "Drupal\comment\CommentTypeForm",
  *       "delete" = "Drupal\comment\Form\CommentTypeDeleteForm"
  *     },
- *     "list_builder" = "Drupal\comment\CommentTypeListBuilder"
+ *     "list_builder" = "Drupal\comment\CommentTypeListBuilder",
+ *     "route_provider" = {
+ *       "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
+ *     },
  *   },
  *   admin_permission = "administer comment types",
  *   config_prefix = "type",
diff --git a/core/modules/content_translation/src/Tests/ContentTestTranslationUITest.php b/core/modules/content_translation/src/Tests/ContentTestTranslationUITest.php
index 097ff0e..ed3845c 100644
--- a/core/modules/content_translation/src/Tests/ContentTestTranslationUITest.php
+++ b/core/modules/content_translation/src/Tests/ContentTestTranslationUITest.php
@@ -34,7 +34,7 @@ protected function setUp() {
    * Overrides \Drupal\content_translation\Tests\ContentTranslationUITestBase::getTranslatorPermission().
    */
   protected function getTranslatorPermissions() {
-    return array_merge(parent::getTranslatorPermissions(), array('administer entity_test content'));
+    return array_merge(parent::getTranslatorPermissions(), array('administer entity_test content', 'view test entity'));
   }
 
 }
diff --git a/core/modules/content_translation/src/Tests/ContentTranslationUITestBase.php b/core/modules/content_translation/src/Tests/ContentTranslationUITestBase.php
index bf781fb..1b99552 100644
--- a/core/modules/content_translation/src/Tests/ContentTranslationUITestBase.php
+++ b/core/modules/content_translation/src/Tests/ContentTranslationUITestBase.php
@@ -56,8 +56,6 @@ protected function doTestBasicTranslation() {
     $this->entityId = $this->createEntity($values[$default_langcode], $default_langcode);
     $entity = entity_load($this->entityTypeId, $this->entityId, TRUE);
     $this->assertTrue($entity, 'Entity found in the database.');
-    $this->drupalGet($entity->urlInfo());
-    $this->assertResponse(200, 'Entity URL is valid.');
     $this->drupalGet($entity->urlInfo('drupal:content-translation-overview'));
     $this->assertNoText('Source language', 'Source language column correctly hidden.');
 
diff --git a/core/modules/language/src/Tests/LanguageConfigurationElementTest.php b/core/modules/language/src/Tests/LanguageConfigurationElementTest.php
index 1d4ee27..2192640 100644
--- a/core/modules/language/src/Tests/LanguageConfigurationElementTest.php
+++ b/core/modules/language/src/Tests/LanguageConfigurationElementTest.php
@@ -235,7 +235,7 @@ public function testTaxonomyVocabularyUpdate() {
       'default_language[langcode]' => 'current_interface',
       'default_language[language_alterable]' => TRUE,
     );
-    $this->drupalPostForm('admin/structure/taxonomy/manage/country', $edit, t('Save'));
+    $this->drupalPostForm($vocabulary->urlInfo('edit-form'), $edit, t('Save'));
 
     // Check the language default configuration.
     $configuration = ContentLanguageSettings::loadByEntityTypeBundle('taxonomy_term', 'country');
@@ -246,7 +246,7 @@ public function testTaxonomyVocabularyUpdate() {
     $edit = array(
       'vid' => 'nation'
     );
-    $this->drupalPostForm('admin/structure/taxonomy/manage/country', $edit, t('Save'));
+    $this->drupalPostForm($vocabulary->urlInfo('edit-form'), $edit, t('Save'));
     // Check that we still have the settings for the new vocabulary.
     $configuration = ContentLanguageSettings::loadByEntityTypeBundle('taxonomy_term', 'nation');
     $this->assertEqual($configuration->getDefaultLangcode(), 'current_interface', 'The default language configuration has been kept on the new Country vocabulary.');
diff --git a/core/modules/menu_link_content/menu_link_content.routing.yml b/core/modules/menu_link_content/menu_link_content.routing.yml
index 8c00ee0..cb5a3c6 100644
--- a/core/modules/menu_link_content/menu_link_content.routing.yml
+++ b/core/modules/menu_link_content/menu_link_content.routing.yml
@@ -5,27 +5,3 @@ entity.menu.add_link_form:
     _title: 'Add menu link'
   requirements:
     _entity_create_access: 'menu_link_content'
-
-entity.menu_link_content.canonical:
-  path: '/admin/structure/menu/item/{menu_link_content}/edit'
-  defaults:
-    _entity_form: 'menu_link_content.default'
-    _title: 'Edit menu link'
-  requirements:
-    _entity_access: 'menu_link_content.update'
-
-entity.menu_link_content.edit_form:
-  path: '/admin/structure/menu/item/{menu_link_content}/edit'
-  defaults:
-    _entity_form: 'menu_link_content.default'
-    _title: 'Edit menu link'
-  requirements:
-    _entity_access: 'menu_link_content.update'
-
-entity.menu_link_content.delete_form:
-  path: '/admin/structure/menu/item/{menu_link_content}/delete'
-  defaults:
-    _entity_form: 'menu_link_content.delete'
-    _title: 'Delete menu link'
-  requirements:
-    _entity_access: 'menu_link_content.delete'
diff --git a/core/modules/menu_link_content/src/Entity/MenuLinkContent.php b/core/modules/menu_link_content/src/Entity/MenuLinkContent.php
index abbc694..28dc4d3 100644
--- a/core/modules/menu_link_content/src/Entity/MenuLinkContent.php
+++ b/core/modules/menu_link_content/src/Entity/MenuLinkContent.php
@@ -31,7 +31,10 @@
  *     "form" = {
  *       "default" = "Drupal\menu_link_content\Form\MenuLinkContentForm",
  *       "delete" = "Drupal\menu_link_content\Form\MenuLinkContentDeleteForm"
- *     }
+ *     },
+ *     "route_provider" = {
+ *       "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
+ *     },
  *   },
  *   admin_permission = "administer menu",
  *   base_table = "menu_link_content",
@@ -45,7 +48,7 @@
  *     "bundle" = "bundle"
  *   },
  *   links = {
- *     "canonical" = "/admin/structure/menu/item/{menu_link_content}/edit",
+ *     "canonical" = "/admin/structure/menu/item/{menu_link_content}",
  *     "edit-form" = "/admin/structure/menu/item/{menu_link_content}/edit",
  *     "delete-form" = "/admin/structure/menu/item/{menu_link_content}/delete",
  *   }
diff --git a/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php b/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php
index 747c1fd..c4cbdf3 100644
--- a/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php
+++ b/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php
@@ -127,7 +127,7 @@ public function save(array $form, FormStateInterface $form_state) {
     if ($saved) {
       drupal_set_message($this->t('The menu link has been saved.'));
       $form_state->setRedirect(
-        'entity.menu_link_content.canonical',
+        'entity.menu_link_content.edit_form',
         array('menu_link_content' => $menu_link->id())
       );
     }
diff --git a/core/modules/menu_link_content/src/Plugin/Menu/MenuLinkContent.php b/core/modules/menu_link_content/src/Plugin/Menu/MenuLinkContent.php
index f120b61..5b559ec 100644
--- a/core/modules/menu_link_content/src/Plugin/Menu/MenuLinkContent.php
+++ b/core/modules/menu_link_content/src/Plugin/Menu/MenuLinkContent.php
@@ -187,7 +187,7 @@ public function getDeleteRoute() {
    * {@inheritdoc}
    */
   public function getEditRoute() {
-    return $this->getEntity()->urlInfo();
+    return $this->getEntity()->urlInfo('edit-form');
   }
 
   /**
diff --git a/core/modules/menu_link_content/src/Tests/MenuLinkContentTranslationUITest.php b/core/modules/menu_link_content/src/Tests/MenuLinkContentTranslationUITest.php
index b068808..8dc4421 100644
--- a/core/modules/menu_link_content/src/Tests/MenuLinkContentTranslationUITest.php
+++ b/core/modules/menu_link_content/src/Tests/MenuLinkContentTranslationUITest.php
@@ -90,7 +90,7 @@ function testTranslationLinkTheme() {
     $this->drupalPostForm('admin/appearance', $edit, t('Save configuration'));
     $this->drupalGet('admin/structure/menu/item/' . $entityId . '/edit');
     $this->assertRaw('core/themes/seven/css/base/elements.css', 'Edit uses admin theme.');
-    $this->drupalGet('admin/structure/menu/item/' . $entityId . '/edit/translations');
+    $this->drupalGet('admin/structure/menu/item/' . $entityId . '/translations');
     $this->assertRaw('core/themes/seven/css/base/elements.css', 'Translation uses admin theme as well.');
   }
 
diff --git a/core/modules/node/node.routing.yml b/core/modules/node/node.routing.yml
index 9f3af90..420db4b 100644
--- a/core/modules/node/node.routing.yml
+++ b/core/modules/node/node.routing.yml
@@ -32,7 +32,7 @@ entity.node.preview:
   path: '/node/preview/{node_preview}/{view_mode_id}'
   defaults:
     _controller: '\Drupal\node\Controller\NodePreviewController::view'
-    _title_callback: '\Drupal\node\Controller\NodePreviewController::title'
+    _title_callback: '\Drupal\node\Controller\NodePreviewController::previewTitle'
   requirements:
     _node_preview_access: '{node_preview}'
   options:
diff --git a/core/modules/node/src/Controller/NodePreviewController.php b/core/modules/node/src/Controller/NodePreviewController.php
index 9fb0e7b..aafac91 100644
--- a/core/modules/node/src/Controller/NodePreviewController.php
+++ b/core/modules/node/src/Controller/NodePreviewController.php
@@ -63,7 +63,7 @@ public function view(EntityInterface $node_preview, $view_mode_id = 'full', $lan
    * @return string
    *   The page title.
    */
-  public function title(EntityInterface $node_preview) {
+  public function previewTitle(EntityInterface $node_preview) {
     return SafeMarkup::checkPlain($this->entityManager->getTranslationFromContext($node_preview)->label());
   }
 
diff --git a/core/modules/node/src/Entity/Node.php b/core/modules/node/src/Entity/Node.php
index 6dd1229..cf39268 100644
--- a/core/modules/node/src/Entity/Node.php
+++ b/core/modules/node/src/Entity/Node.php
@@ -36,7 +36,7 @@
  *       "edit" = "Drupal\node\NodeForm"
  *     },
  *     "route_provider" = {
- *       "html" = "Drupal\node\Entity\NodeRouteProvider",
+ *       "html" = "Drupal\node\Entity\NodeHtmlRouteProvider",
  *     },
  *     "list_builder" = "Drupal\node\NodeListBuilder",
  *     "translation" = "Drupal\node\NodeTranslationHandler"
diff --git a/core/modules/node/src/Entity/NodeHtmlRouteProvider.php b/core/modules/node/src/Entity/NodeHtmlRouteProvider.php
new file mode 100644
index 0000000..72b7a74
--- /dev/null
+++ b/core/modules/node/src/Entity/NodeHtmlRouteProvider.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\node\Entity\NodeHtmlRouteProvider.
+ */
+
+namespace Drupal\node\Entity;
+
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider;
+use Drupal\Core\Entity\Routing\EntityRouteProviderInterface;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * Provides HTML routes for the node entity type.
+ */
+class NodeHtmlRouteProvider extends DefaultHtmlRouteProvider {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRoutes(EntityTypeInterface $entity_type) {
+    $collection = parent::getRoutes($entity_type);
+
+    $collection->get('entity.node.canonical')
+      ->setDefault('_entity_view', NULL)
+      ->setDefault('_controller', '\Drupal\node\Controller\NodeViewController::view');
+
+    $collection->get('entity.node.delete_form')
+      ->setOption('_node_operation_route', TRUE);
+
+    $collection->get('entity.node.edit_form')
+      ->setOption('_node_operation_route', TRUE);
+
+    return $collection;
+  }
+
+}
diff --git a/core/modules/node/src/Entity/NodeRouteProvider.php b/core/modules/node/src/Entity/NodeRouteProvider.php
deleted file mode 100644
index fa1c02d..0000000
--- a/core/modules/node/src/Entity/NodeRouteProvider.php
+++ /dev/null
@@ -1,51 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\node\Entity\NodeRouteProvider.
- */
-
-namespace Drupal\node\Entity;
-
-use Drupal\Core\Entity\EntityTypeInterface;
-use Drupal\Core\Entity\Routing\EntityRouteProviderInterface;
-use Symfony\Component\Routing\Route;
-use Symfony\Component\Routing\RouteCollection;
-
-/**
- * Provides routes for nodes.
- */
-class NodeRouteProvider implements EntityRouteProviderInterface {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getRoutes( EntityTypeInterface $entity_type) {
-    $route_collection = new RouteCollection();
-    $route = (new Route('/node/{node}'))
-      ->addDefaults([
-        '_controller' => '\Drupal\node\Controller\NodeViewController::view',
-        '_title_callback' => '\Drupal\node\Controller\NodeViewController::title',
-      ])
-      ->setRequirement('_entity_access', 'node.view');
-    $route_collection->add('entity.node.canonical', $route);
-
-    $route = (new Route('/node/{node}/delete'))
-      ->addDefaults([
-        '_entity_form' => 'node.delete',
-        '_title' => 'Delete',
-      ])
-      ->setRequirement('_entity_access', 'node.delete')
-      ->setOption('_node_operation_route', TRUE);
-    $route_collection->add('entity.node.delete_form', $route);
-
-    $route = (new Route('/node/{node}/edit'))
-      ->setDefault('_entity_form', 'node.edit')
-      ->setRequirement('_entity_access', 'node.update')
-      ->setOption('_node_operation_route', TRUE);
-    $route_collection->add('entity.node.edit_form', $route);
-
-    return $route_collection;
-  }
-
-}
diff --git a/core/modules/rest/src/Routing/ResourceRoutes.php b/core/modules/rest/src/Routing/ResourceRoutes.php
index 484c48e..ce6c9a9 100644
--- a/core/modules/rest/src/Routing/ResourceRoutes.php
+++ b/core/modules/rest/src/Routing/ResourceRoutes.php
@@ -71,6 +71,7 @@ protected function alterRoutes(RouteCollection $collection) {
 
     // Iterate over all enabled resource plugins.
     foreach ($enabled_resources as $id => $enabled_methods) {
+      /** @var \Drupal\rest\Plugin\ResourceInterface $plugin */
       $plugin = $this->manager->getInstance(array('id' => $id));
 
       foreach ($plugin->routes() as $name => $route) {
diff --git a/core/modules/rest/src/Tests/ResourceTest.php b/core/modules/rest/src/Tests/ResourceTest.php
index 5144ae3..102baf5 100644
--- a/core/modules/rest/src/Tests/ResourceTest.php
+++ b/core/modules/rest/src/Tests/ResourceTest.php
@@ -39,6 +39,10 @@ protected function setUp() {
    * Tests that a resource without formats cannot be enabled.
    */
   public function testFormats() {
+    $account = $this->drupalCreateUser(['view test entity']);
+    $account->save();
+    $this->drupalLogin($account);
+
     $settings = array(
       'entity:entity_test' => array(
         'GET' => array(
@@ -83,6 +87,8 @@ public function testAuthentication() {
     $this->config->save();
     $this->rebuildCache();
 
+    $account = $this->drupalCreateUser(['view test entity']);
+    \Drupal::service('account_switcher')->switchTo($account);
     // Verify that accessing the resource returns 401.
     $response = $this->httpRequest($this->entity->urlInfo(), 'GET', NULL, $this->defaultMimeType);
     // AcceptHeaderMatcher considers the canonical, non-REST route a match, but
diff --git a/core/modules/shortcut/shortcut.routing.yml b/core/modules/shortcut/shortcut.routing.yml
index d6c0e1a..df295a7 100644
--- a/core/modules/shortcut/shortcut.routing.yml
+++ b/core/modules/shortcut/shortcut.routing.yml
@@ -54,22 +54,6 @@ shortcut.link_add:
   requirements:
     _entity_create_access: 'shortcut:{shortcut_set}'
 
-entity.shortcut.canonical:
-  path: '/admin/config/user-interface/shortcut/link/{shortcut}'
-  defaults:
-    _entity_form: 'shortcut.default'
-    _title: 'Edit'
-  requirements:
-    _entity_access: 'shortcut.update'
-
-entity.shortcut.edit_form:
-  path: '/admin/config/user-interface/shortcut/link/{shortcut}'
-  defaults:
-    _entity_form: 'shortcut.default'
-    _title: 'Edit'
-  requirements:
-    _entity_access: 'shortcut.update'
-
 entity.shortcut.link_delete_inline:
   path: '/admin/config/user-interface/shortcut/link/{shortcut}/delete-inline'
   defaults:
@@ -78,14 +62,6 @@ entity.shortcut.link_delete_inline:
     _entity_access: 'shortcut.delete'
     _csrf_token: 'TRUE'
 
-entity.shortcut.delete_form:
-  path: '/admin/config/user-interface/shortcut/link/{shortcut}/delete'
-  defaults:
-    _entity_form: 'shortcut.delete'
-    _title: 'Delete'
-  requirements:
-    _entity_access: 'shortcut.delete'
-
 shortcut.set_switch:
   path: '/user/{user}/shortcuts'
   defaults:
diff --git a/core/modules/shortcut/src/Entity/Shortcut.php b/core/modules/shortcut/src/Entity/Shortcut.php
index a2179f9..4b74b70 100644
--- a/core/modules/shortcut/src/Entity/Shortcut.php
+++ b/core/modules/shortcut/src/Entity/Shortcut.php
@@ -31,7 +31,10 @@
  *       "edit" = "Drupal\shortcut\ShortcutForm",
  *       "delete" = "Drupal\shortcut\Form\ShortcutDeleteForm"
  *     },
- *     "translation" = "Drupal\content_translation\ContentTranslationHandler"
+ *     "translation" = "Drupal\content_translation\ContentTranslationHandler",
+ *     "route_provider" = {
+ *       "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
+ *     },
  *   },
  *   base_table = "shortcut",
  *   data_table = "shortcut_field_data",
@@ -45,8 +48,8 @@
  *   },
  *   links = {
  *     "canonical" = "/admin/config/user-interface/shortcut/link/{shortcut}",
+ *     "edit-form" = "/admin/config/user-interface/shortcut/link/{shortcut}/edit",
  *     "delete-form" = "/admin/config/user-interface/shortcut/link/{shortcut}/delete",
- *     "edit-form" = "/admin/config/user-interface/shortcut/link/{shortcut}",
  *   },
  *   list_cache_tags = { "config:shortcut_set_list" },
  *   bundle_entity_type = "shortcut_set"
diff --git a/core/modules/shortcut/src/Tests/ShortcutLinksTest.php b/core/modules/shortcut/src/Tests/ShortcutLinksTest.php
index 3b5811f..ba28c23 100644
--- a/core/modules/shortcut/src/Tests/ShortcutLinksTest.php
+++ b/core/modules/shortcut/src/Tests/ShortcutLinksTest.php
@@ -166,7 +166,7 @@ public function testShortcutLinkRename() {
 
     $shortcuts = $set->getShortcuts();
     $shortcut = reset($shortcuts);
-    $this->drupalPostForm('admin/config/user-interface/shortcut/link/' . $shortcut->id(), array('title[0][value]' => $new_link_name), t('Save'));
+    $this->drupalPostForm($shortcut->url('edit-form'), array('title[0][value]' => $new_link_name), t('Save'));
     $saved_set = ShortcutSet::load($set->id());
     $titles = $this->getShortcutInformation($saved_set, 'title');
     $this->assertTrue(in_array($new_link_name, $titles), 'Shortcut renamed: ' . $new_link_name);
@@ -185,7 +185,7 @@ public function testShortcutLinkChangePath() {
 
     $shortcuts = $set->getShortcuts();
     $shortcut = reset($shortcuts);
-    $this->drupalPostForm('admin/config/user-interface/shortcut/link/' . $shortcut->id(), array('title[0][value]' => $shortcut->getTitle(), 'link[0][uri]' => $new_link_path), t('Save'));
+    $this->drupalPostForm($shortcut->url('edit-form'), array('title[0][value]' => $shortcut->getTitle(), 'link[0][uri]' => $new_link_path), t('Save'));
     $saved_set = ShortcutSet::load($set->id());
     $paths = $this->getShortcutInformation($saved_set, 'link');
     $this->assertTrue(in_array('internal:' . $new_link_path, $paths), 'Shortcut path changed: ' . $new_link_path);
diff --git a/core/modules/system/src/Tests/Entity/EntityFormTest.php b/core/modules/system/src/Tests/Entity/EntityFormTest.php
index e4365eb..e6d0bd3 100644
--- a/core/modules/system/src/Tests/Entity/EntityFormTest.php
+++ b/core/modules/system/src/Tests/Entity/EntityFormTest.php
@@ -26,7 +26,7 @@ class EntityFormTest extends WebTestBase {
 
   protected function setUp() {
     parent::setUp();
-    $web_user = $this->drupalCreateUser(array('administer entity_test content'));
+    $web_user = $this->drupalCreateUser(array('administer entity_test content', 'view test entity'));
     $this->drupalLogin($web_user);
 
     // Add a language.
diff --git a/core/modules/system/src/Tests/Entity/EntityRevisionsTest.php b/core/modules/system/src/Tests/Entity/EntityRevisionsTest.php
index 3009307..d8bfd8b 100644
--- a/core/modules/system/src/Tests/Entity/EntityRevisionsTest.php
+++ b/core/modules/system/src/Tests/Entity/EntityRevisionsTest.php
@@ -37,6 +37,7 @@ protected function setUp() {
     // Create and login user.
     $this->webUser = $this->drupalCreateUser(array(
       'administer entity_test content',
+      'view test entity',
     ));
     $this->drupalLogin($this->webUser);
   }
diff --git a/core/modules/system/src/Tests/Entity/EntityViewControllerTest.php b/core/modules/system/src/Tests/Entity/EntityViewControllerTest.php
index aa2d800..daaa6a8 100644
--- a/core/modules/system/src/Tests/Entity/EntityViewControllerTest.php
+++ b/core/modules/system/src/Tests/Entity/EntityViewControllerTest.php
@@ -41,6 +41,8 @@ protected function setUp() {
       $this->entities[] = $entity_test;
     }
 
+    $account = $this->drupalCreateUser(['view test entity']);
+    $this->drupalLogin($account);
   }
 
   /**
diff --git a/core/modules/system/tests/modules/entity_test/src/Controller/EntityTestController.php b/core/modules/system/tests/modules/entity_test/src/Controller/EntityTestController.php
index 1d6e4e8..b44c7eb 100644
--- a/core/modules/system/tests/modules/entity_test/src/Controller/EntityTestController.php
+++ b/core/modules/system/tests/modules/entity_test/src/Controller/EntityTestController.php
@@ -63,26 +63,6 @@ public function testAdd($entity_type_id) {
   }
 
   /**
-   * Displays the 'Edit existing entity_test' form.
-   *
-   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
-   *   The route match object to get entity type from.
-   * @param string $entity_type_id
-   *   The entity type ID.
-   *
-   * @return array
-   *   The processed form for the edited entity.
-   *
-   * @see \Drupal\entity_test\Routing\EntityTestRoutes::routes()
-   */
-  public function testEdit(RouteMatchInterface $route_match, $entity_type_id) {
-    $entity = $route_match->getParameter($entity_type_id);
-    $form = $this->entityFormBuilder()->getForm($entity);
-    $form['#title'] = $entity->label();
-    return $form;
-  }
-
-  /**
    * Returns an empty page.
    *
    * @see \Drupal\entity_test\Routing\EntityTestRoutes::routes()
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTest.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTest.php
index 89b4c20..53d6632 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTest.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTest.php
@@ -28,10 +28,14 @@
  *       "default" = "Drupal\entity_test\EntityTestForm",
  *       "delete" = "Drupal\entity_test\EntityTestDeleteForm"
  *     },
+ *     "route_provider" = {
+ *       "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
+ *     },
  *     "translation" = "Drupal\content_translation\ContentTranslationHandler",
  *     "views_data" = "Drupal\entity_test\EntityTestViewsData"
  *   },
  *   base_table = "entity_test",
+ *   admin_permission = "administer entity_test content",
  *   persistent_cache = FALSE,
  *   list_cache_contexts = { "entity_test_view_grants" },
  *   entity_keys = {
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestBaseFieldDisplay.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestBaseFieldDisplay.php
index 210eb53..1f70d97 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestBaseFieldDisplay.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestBaseFieldDisplay.php
@@ -21,16 +21,22 @@
  *     "form" = {
  *       "default" = "Drupal\entity_test\EntityTestForm"
  *     },
- *     "translation" = "Drupal\content_translation\ContentTranslationHandler"
+ *     "translation" = "Drupal\content_translation\ContentTranslationHandler",
+ *     "route_provider" = {
+ *       "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
+ *     },
  *   },
  *   base_table = "entity_test",
+ *   admin_permission = "administer entity_test content",
  *   entity_keys = {
  *     "id" = "id",
  *     "uuid" = "uuid",
  *     "bundle" = "type"
  *   },
  *   links = {
+ *     "canonical" = "/entity_test_base_field_display/{entity_test_base_field_display}",
  *     "edit-form" = "/entity_test_base_field_display/manage/{entity_test_base_field_display}",
+ *     "delete-form" = "/entity_test/delete/entity_test_base_field_display/{entity_test_base_field_display}",
  *   },
  *   field_ui_base_route = "entity.entity_test_base_field_display.admin_form",
  * )
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMul.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMul.php
index 334ff94..c965452 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMul.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMul.php
@@ -25,10 +25,14 @@
  *       "delete" = "Drupal\entity_test\EntityTestDeleteForm"
  *     },
  *     "translation" = "Drupal\content_translation\ContentTranslationHandler",
- *     "views_data" = "Drupal\views\EntityViewsData"
+ *     "views_data" = "Drupal\views\EntityViewsData",
+ *     "route_provider" = {
+ *       "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
+ *     },
  *   },
  *   base_table = "entity_test_mul",
  *   data_table = "entity_test_mul_property_data",
+ *   admin_permission = "administer entity_test content",
  *   translatable = TRUE,
  *   entity_keys = {
  *     "id" = "id",
@@ -38,7 +42,7 @@
  *     "langcode" = "langcode",
  *   },
  *   links = {
- *     "canonical" = "/entity_test_mul/manage/{entity_test_mul}",
+ *     "canonical" = "/entity_test_mul/{entity_test_mul}",
  *     "edit-form" = "/entity_test_mul/manage/{entity_test_mul}",
  *     "delete-form" = "/entity_test/delete/entity_test_mul/{entity_test_mul}",
  *   },
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulDefaultValue.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulDefaultValue.php
index 4bee585..d8ffe16 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulDefaultValue.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulDefaultValue.php
@@ -37,7 +37,7 @@
  *     "langcode" = "langcode"
  *   },
  *   links = {
- *     "canonical" = "/entity_test_mul_default_value/manage/{entity_test_mul_default_value}",
+ *     "canonical" = "/entity_test_mul_default_value/{entity_test_mul_default_value}",
  *     "edit-form" = "/entity_test_mul_default_value/manage/{entity_test_mul_default_value}",
  *     "delete-form" = "/entity_test/delete/entity_test_mul_default_value/{entity_test_mul_default_value}",
  *   },
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulLangcodeKey.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulLangcodeKey.php
index c50f2c7..1245c3e 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulLangcodeKey.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulLangcodeKey.php
@@ -23,10 +23,14 @@
  *       "delete" = "Drupal\entity_test\EntityTestDeleteForm"
  *     },
  *     "translation" = "Drupal\content_translation\ContentTranslationHandler",
- *     "views_data" = "Drupal\views\EntityViewsData"
+ *     "views_data" = "Drupal\views\EntityViewsData",
+ *     "route_provider" = {
+ *       "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
+ *     },
  *   },
  *   base_table = "entity_test_mul_langcode_key",
  *   data_table = "entity_test_mul_langcode_key_field_data",
+ *   admin_permission = "administer entity_test content",
  *   translatable = TRUE,
  *   entity_keys = {
  *     "id" = "id",
@@ -37,7 +41,7 @@
  *     "default_langcode" = "custom_default_langcode_key",
  *   },
  *   links = {
- *     "canonical" = "/entity_test_mul_langcode_key/manage/{entity_test_mul_langcode_key}",
+ *     "canonical" = "/entity_test_mul_langcode_key/{entity_test_mul_langcode_key}",
  *     "edit-form" = "/entity_test_mul_langcode_key/manage/{entity_test_mul_langcode_key}",
  *     "delete-form" = "/entity_test/delete/entity_test_mul_langcode_key/{entity_test_mul_langcode_key}",
  *   },
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php
index 83dd0ed..e220cc8 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php
@@ -25,12 +25,16 @@
  *       "delete" = "Drupal\entity_test\EntityTestDeleteForm"
  *     },
  *     "translation" = "Drupal\content_translation\ContentTranslationHandler",
- *     "views_data" = "Drupal\views\EntityViewsData"
+ *     "views_data" = "Drupal\views\EntityViewsData",
+ *     "route_provider" = {
+ *       "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
+ *     },
  *   },
  *   base_table = "entity_test_mulrev",
  *   data_table = "entity_test_mulrev_property_data",
  *   revision_table = "entity_test_mulrev_revision",
  *   revision_data_table = "entity_test_mulrev_property_revision",
+ *   admin_permission = "administer entity_test content",
  *   translatable = TRUE,
  *   entity_keys = {
  *     "id" = "id",
@@ -41,7 +45,7 @@
  *     "langcode" = "langcode",
  *   },
  *   links = {
- *     "canonical" = "/entity_test_mulrev/manage/{entity_test_mulrev}",
+ *     "canonical" = "/entity_test_mulrev/{entity_test_mulrev}",
  *     "delete-form" = "/entity_test/delete/entity_test_mulrev/{entity_test_mulrev}",
  *     "edit-form" = "/entity_test_mulrev/manage/{entity_test_mulrev}",
  *     "revision" = "/entity_test_mulrev/{entity_test_mulrev}/revision/{entity_test_mulrev_revision}/view",
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php
index 7914032..aa7e2e7 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php
@@ -19,16 +19,21 @@
  *   label = @Translation("Test entity - revisions"),
  *   handlers = {
  *     "access" = "Drupal\entity_test\EntityTestAccessControlHandler",
+ *     "view_builder" = "Drupal\entity_test\EntityTestViewBuilder",
  *     "form" = {
  *       "default" = "Drupal\entity_test\EntityTestForm",
  *       "delete" = "Drupal\entity_test\EntityTestDeleteForm"
  *     },
  *     "view_builder" = "Drupal\entity_test\EntityTestViewBuilder",
  *     "translation" = "Drupal\content_translation\ContentTranslationHandler",
- *     "views_data" = "Drupal\views\EntityViewsData"
+ *     "views_data" = "Drupal\views\EntityViewsData",
+ *     "route_provider" = {
+ *       "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
+ *     },
  *   },
  *   base_table = "entity_test_rev",
  *   revision_table = "entity_test_rev_revision",
+ *   admin_permission = "administer entity_test content",
  *   entity_keys = {
  *     "id" = "id",
  *     "uuid" = "uuid",
@@ -38,7 +43,7 @@
  *     "langcode" = "langcode",
  *   },
  *   links = {
- *     "canonical" = "/entity_test_rev/manage/{entity_test_rev}",
+ *     "canonical" = "/entity_test_rev/{entity_test_rev}",
  *     "delete-form" = "/entity_test/delete/entity_test_rev/{entity_test_rev}",
  *     "edit-form" = "/entity_test_rev/manage/{entity_test_rev}",
  *     "revision" = "/entity_test_rev/{entity_test_rev}/revision/{entity_test_rev_revision}/view",
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestStringId.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestStringId.php
index 7e057d4..62a42e6 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestStringId.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestStringId.php
@@ -20,16 +20,20 @@
  *     "form" = {
  *       "default" = "Drupal\entity_test\EntityTestForm"
  *     },
- *     "translation" = "Drupal\content_translation\ContentTranslationHandler"
+ *     "translation" = "Drupal\content_translation\ContentTranslationHandler",
+ *     "route_provider" = {
+ *       "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
+ *     },
  *   },
  *   base_table = "entity_test_string",
+ *   admin_permission = "administer entity_test content",
  *   entity_keys = {
  *     "id" = "id",
  *     "uuid" = "uuid",
  *     "bundle" = "type"
  *   },
  *   links = {
- *     "canonical" = "/entity_test_string_id/manage/{entity_test_string_id}",
+ *     "canonical" = "/entity_test_string_id/{entity_test_string_id}",
  *     "edit-form" = "/entity_test_string_id/manage/{entity_test_string_id}",
  *   },
  *   field_ui_base_route = "entity.entity_test_string_id.admin_form",
diff --git a/core/modules/system/tests/modules/entity_test/src/Routing/EntityTestRoutes.php b/core/modules/system/tests/modules/entity_test/src/Routing/EntityTestRoutes.php
index 53c1978..fcaefca 100644
--- a/core/modules/system/tests/modules/entity_test/src/Routing/EntityTestRoutes.php
+++ b/core/modules/system/tests/modules/entity_test/src/Routing/EntityTestRoutes.php
@@ -33,30 +33,6 @@ public function routes() {
         array('_permission' => 'administer entity_test content')
       );
 
-      $routes["entity.$entity_type_id.canonical"] = new Route(
-        $entity_type_id . '/manage/{' . $entity_type_id . '}',
-        array('_controller' => '\Drupal\entity_test\Controller\EntityTestController::testEdit', 'entity_type_id' => $entity_type_id),
-        array('_permission' => 'administer entity_test content'),
-        array('parameters' => array(
-          $entity_type_id => array('type' => 'entity:' . $entity_type_id),
-        ))
-      );
-
-      $routes["entity.$entity_type_id.edit_form"] = new Route(
-        $entity_type_id . '/manage/{' . $entity_type_id . '}',
-        array('_controller' => '\Drupal\entity_test\Controller\EntityTestController::testEdit', 'entity_type_id' => $entity_type_id),
-        array('_permission' => 'administer entity_test content'),
-        array('parameters' => array(
-          $entity_type_id => array('type' => 'entity:' . $entity_type_id),
-        ))
-      );
-
-      $routes["entity.$entity_type_id.delete_form"] = new Route(
-        'entity_test/delete/' . $entity_type_id . '/{' . $entity_type_id . '}',
-        array('_entity_form' => $entity_type_id . '.delete'),
-        array('_permission' => 'administer entity_test content')
-      );
-
       $routes["entity.$entity_type_id.admin_form"] = new Route(
         "$entity_type_id/structure/{bundle}",
         array('_controller' => '\Drupal\entity_test\Controller\EntityTestController::testAdmin'),
diff --git a/core/modules/taxonomy/src/Entity/Term.php b/core/modules/taxonomy/src/Entity/Term.php
index a88c3a4..2e6f5e5 100644
--- a/core/modules/taxonomy/src/Entity/Term.php
+++ b/core/modules/taxonomy/src/Entity/Term.php
@@ -31,7 +31,10 @@
  *       "default" = "Drupal\taxonomy\TermForm",
  *       "delete" = "Drupal\taxonomy\Form\TermDeleteForm"
  *     },
- *     "translation" = "Drupal\taxonomy\TermTranslationHandler"
+ *     "translation" = "Drupal\taxonomy\TermTranslationHandler",
+ *     "route_provider" = {
+ *       "html" = "Drupal\Core\Entity\Routing\AdminHtmlRouteProvider",
+ *     },
  *   },
  *   base_table = "taxonomy_term_data",
  *   data_table = "taxonomy_term_field_data",
diff --git a/core/modules/taxonomy/src/Entity/Vocabulary.php b/core/modules/taxonomy/src/Entity/Vocabulary.php
index 2855c65..fb2f3f2 100644
--- a/core/modules/taxonomy/src/Entity/Vocabulary.php
+++ b/core/modules/taxonomy/src/Entity/Vocabulary.php
@@ -24,7 +24,10 @@
  *       "default" = "Drupal\taxonomy\VocabularyForm",
  *       "reset" = "Drupal\taxonomy\Form\VocabularyResetForm",
  *       "delete" = "Drupal\taxonomy\Form\VocabularyDeleteForm"
- *     }
+ *     },
+ *     "route_provider" = {
+ *       "html" = "Drupal\taxonomy\Entity\VocabularyRouteProvider",
+ *     },
  *   },
  *   admin_permission = "administer taxonomy",
  *   config_prefix = "vocabulary",
@@ -35,11 +38,12 @@
  *     "weight" = "weight"
  *   },
  *   links = {
+ *     "canonical" = "/admin/structure/taxonomy/manage/{taxonomy_vocabulary}",
  *     "add-form" = "/admin/structure/taxonomy/manage/{taxonomy_vocabulary}/add",
+ *     "edit-form" = "/admin/structure/taxonomy/manage/{taxonomy_vocabulary}",
  *     "delete-form" = "/admin/structure/taxonomy/manage/{taxonomy_vocabulary}/delete",
  *     "reset-form" = "/admin/structure/taxonomy/manage/{taxonomy_vocabulary}/reset",
  *     "overview-form" = "/admin/structure/taxonomy/manage/{taxonomy_vocabulary}/overview",
- *     "edit-form" = "/admin/structure/taxonomy/manage/{taxonomy_vocabulary}",
  *     "collection" = "/admin/structure/taxonomy",
  *   },
  *   config_export = {
diff --git a/core/modules/taxonomy/src/Entity/VocabularyRouteProvider.php b/core/modules/taxonomy/src/Entity/VocabularyRouteProvider.php
new file mode 100644
index 0000000..bc073d3
--- /dev/null
+++ b/core/modules/taxonomy/src/Entity/VocabularyRouteProvider.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\taxonomy\Entity\VocabularyRouteProvider.
+ */
+
+namespace Drupal\taxonomy\Entity;
+
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider;
+
+/**
+ * Provides HTML routes for the taxonomy_vocabulary entity type.
+ */
+class VocabularyRouteProvider extends DefaultHtmlRouteProvider {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getEditFormRoute(EntityTypeInterface $entity_type) {
+    $route = parent::getEditFormRoute($entity_type);
+
+    // Vocabularies use the "$vocabulary->label()" instead of
+    // "Edit $vocabulary->label()".
+    $route->setDefault('_title_callback', '\Drupal\taxonomy\Controller\TaxonomyController::vocabularyTitle');
+
+    return $route;
+  }
+
+}
diff --git a/core/modules/taxonomy/src/Tests/TermLanguageTest.php b/core/modules/taxonomy/src/Tests/TermLanguageTest.php
index c679204..7a736f9 100644
--- a/core/modules/taxonomy/src/Tests/TermLanguageTest.php
+++ b/core/modules/taxonomy/src/Tests/TermLanguageTest.php
@@ -49,7 +49,7 @@ function testTermLanguage() {
     $edit = array(
       'default_language[language_alterable]' => TRUE,
     );
-    $this->drupalPostForm('admin/structure/taxonomy/manage/' . $this->vocabulary->id(), $edit, t('Save'));
+    $this->drupalPostForm($this->vocabulary->urlInfo('edit-form'), $edit, t('Save'));
 
     // Add a term.
     $this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/add');
@@ -85,7 +85,7 @@ function testDefaultTermLanguage() {
       'default_language[langcode]' => 'bb',
       'default_language[language_alterable]' => TRUE,
     );
-    $this->drupalPostForm('admin/structure/taxonomy/manage/' . $this->vocabulary->id(), $edit, t('Save'));
+    $this->drupalPostForm($this->vocabulary->urlInfo('edit-form'), $edit, t('Save'));
     $this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/add');
     $this->assertOptionSelected('edit-langcode-0-value', 'bb', 'The expected langcode was selected.');
 
@@ -94,7 +94,7 @@ function testDefaultTermLanguage() {
       'default_language[langcode]' => 'current_interface',
       'default_language[language_alterable]' => TRUE,
     );
-    $this->drupalPostForm('admin/structure/taxonomy/manage/' . $this->vocabulary->id(), $edit, t('Save'));
+    $this->drupalPostForm($this->vocabulary->urlInfo('edit-form'), $edit, t('Save'));
     $this->drupalGet('aa/admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/add');
     $this->assertOptionSelected('edit-langcode-0-value', 'aa', "The expected langcode, 'aa', was selected.");
     $this->drupalGet('bb/admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/add');
@@ -107,7 +107,7 @@ function testDefaultTermLanguage() {
       'default_language[langcode]' => LanguageInterface::LANGCODE_SITE_DEFAULT,
       'default_language[language_alterable]' => TRUE,
     );
-    $this->drupalPostForm('admin/structure/taxonomy/manage/' . $this->vocabulary->id(), $edit, t('Save'));
+    $this->drupalPostForm($this->vocabulary->urlInfo('edit-form'), $edit, t('Save'));
     $this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary->id() . '/add');
     $this->assertOptionSelected('edit-langcode-0-value', 'cc', "The expected langcode, 'cc', was selected.");
   }
diff --git a/core/modules/taxonomy/src/Tests/VocabularyLanguageTest.php b/core/modules/taxonomy/src/Tests/VocabularyLanguageTest.php
index 7987502..467ddda 100644
--- a/core/modules/taxonomy/src/Tests/VocabularyLanguageTest.php
+++ b/core/modules/taxonomy/src/Tests/VocabularyLanguageTest.php
@@ -10,6 +10,7 @@
 use Drupal\Component\Utility\Unicode;
 use Drupal\language\Entity\ConfigurableLanguage;
 use Drupal\language\Entity\ContentLanguageSettings;
+use Drupal\taxonomy\Entity\Vocabulary;
 
 /**
  * Tests the language functionality for vocabularies.
@@ -55,8 +56,10 @@ function testVocabularyLanguage() {
     $edit['vid'] = $vid;
     $this->drupalPostForm(NULL, $edit, t('Save'));
 
+    $vocabulary = Vocabulary::load($vid);
+
     // Check the language on the edit page.
-    $this->drupalGet('admin/structure/taxonomy/manage/' . $vid);
+    $this->drupalGet($vocabulary->urlInfo('edit-form'));
     $this->assertOptionSelected('edit-langcode', $edit['langcode'], 'The vocabulary language was correctly selected.');
 
     // Change the language and save again.
@@ -65,7 +68,7 @@ function testVocabularyLanguage() {
     $this->drupalPostForm(NULL, $edit, t('Save'));
 
     // Check again the language on the edit page.
-    $this->drupalGet('admin/structure/taxonomy/manage/' . $vid);
+    $this->drupalGet($vocabulary->urlInfo('edit-form'));
     $this->assertOptionSelected('edit-langcode', $edit['langcode'], 'The vocabulary language was correctly selected.');
   }
 
@@ -84,8 +87,10 @@ function testVocabularyDefaultLanguageForTerms() {
     $vid = $edit['vid'];
     $this->drupalPostForm('admin/structure/taxonomy/add', $edit, t('Save'));
 
+    $vocabulary = Vocabulary::load($vid);
+
     // Check that the vocabulary was actually created.
-    $this->drupalGet('admin/structure/taxonomy/manage/' . $edit['vid']);
+    $this->drupalGet($vocabulary->urlInfo('edit-form'));
     $this->assertResponse(200, 'The vocabulary has been created.');
 
     // Check that the language settings were saved.
@@ -102,14 +107,14 @@ function testVocabularyDefaultLanguageForTerms() {
       'default_language[langcode]' => 'aa',
       'default_language[language_alterable]' => FALSE,
     );
-    $this->drupalPostForm('admin/structure/taxonomy/manage/' . $vid, $edit, t('Save'));
+    $this->drupalPostForm($vocabulary->urlInfo('edit-form'), $edit, t('Save'));
 
     // And check again the settings and also the interface.
     $language_settings = ContentLanguageSettings::loadByEntityTypeBundle('taxonomy_term', $vid);
     $this->assertEqual($language_settings->getDefaultLangcode(), 'aa', 'The langcode was saved.');
     $this->assertFalse($language_settings->isLanguageAlterable(), 'The visibility setting was saved.');
 
-    $this->drupalGet('admin/structure/taxonomy/manage/' . $vid);
+    $this->drupalGet($vocabulary->urlInfo('edit-form'));
     $this->assertOptionSelected('edit-default-language-langcode', 'aa', 'The correct default language for the terms of this vocabulary is selected.');
     $this->assertNoFieldChecked('edit-default-language-language-alterable', 'Show language selection option is not checked.');
 
@@ -119,7 +124,7 @@ function testVocabularyDefaultLanguageForTerms() {
       'default_language[langcode]' => 'authors_default',
       'default_language[language_alterable]' => FALSE,
     );
-    $this->drupalPostForm('admin/structure/taxonomy/manage/' . $vid, $edit, t('Save'));
+    $this->drupalPostForm($vocabulary->urlInfo('edit-form'), $edit, t('Save'));
 
     // Check that we have the new settings.
     $new_settings = ContentLanguageSettings::loadByEntityTypeBundle('taxonomy_term', $vid);
diff --git a/core/modules/taxonomy/src/Tests/VocabularyUiTest.php b/core/modules/taxonomy/src/Tests/VocabularyUiTest.php
index 3a34c2e..f57ab4b 100644
--- a/core/modules/taxonomy/src/Tests/VocabularyUiTest.php
+++ b/core/modules/taxonomy/src/Tests/VocabularyUiTest.php
@@ -145,7 +145,7 @@ function testTaxonomyAdminDeletingVocabulary() {
     $this->assertTrue($vocabulary, 'Vocabulary found.');
 
     // Delete the vocabulary.
-    $this->drupalGet('admin/structure/taxonomy/manage/' . $vocabulary->id());
+    $this->drupalGet($vocabulary->urlInfo('edit-form'));
     $this->clickLink(t('Delete'));
     $this->assertRaw(t('Are you sure you want to delete the vocabulary %name?', array('%name' => $vocabulary->label())), '[confirm deletion] Asks for confirmation.');
     $this->assertText(t('Deleting a vocabulary will delete all the terms in it. This action cannot be undone.'), '[confirm deletion] Inform that all terms will be deleted.');
diff --git a/core/modules/taxonomy/taxonomy.routing.yml b/core/modules/taxonomy/taxonomy.routing.yml
index a7a75f3..3cdc382 100644
--- a/core/modules/taxonomy/taxonomy.routing.yml
+++ b/core/modules/taxonomy/taxonomy.routing.yml
@@ -14,26 +14,6 @@ entity.taxonomy_term.add_form:
   requirements:
     _entity_create_access: 'taxonomy_term:{taxonomy_vocabulary}'
 
-entity.taxonomy_term.edit_form:
-  path: '/taxonomy/term/{taxonomy_term}/edit'
-  defaults:
-    _entity_form: 'taxonomy_term.default'
-    _title: 'Edit term'
-  options:
-    _admin_route: TRUE
-  requirements:
-    _entity_access: 'taxonomy_term.update'
-
-entity.taxonomy_term.delete_form:
-  path: '/taxonomy/term/{taxonomy_term}/delete'
-  defaults:
-    _entity_form: 'taxonomy_term.delete'
-    _title: 'Delete term'
-  options:
-    _admin_route: TRUE
-  requirements:
-    _entity_access: 'taxonomy_term.delete'
-
 entity.taxonomy_vocabulary.add_form:
   path: '/admin/structure/taxonomy/add'
   defaults:
@@ -42,21 +22,6 @@ entity.taxonomy_vocabulary.add_form:
   requirements:
     _entity_create_access: 'taxonomy_vocabulary'
 
-entity.taxonomy_vocabulary.edit_form:
-  path: '/admin/structure/taxonomy/manage/{taxonomy_vocabulary}'
-  defaults:
-    _entity_form: 'taxonomy_vocabulary.default'
-    _title_callback: '\Drupal\taxonomy\Controller\TaxonomyController::vocabularyTitle'
-  requirements:
-    _entity_access: 'taxonomy_vocabulary.update'
-
-entity.taxonomy_vocabulary.delete_form:
-  path: '/admin/structure/taxonomy/manage/{taxonomy_vocabulary}/delete'
-  defaults:
-    _entity_form: 'taxonomy_vocabulary.delete'
-    _title: 'Delete vocabulary'
-  requirements:
-    _entity_access: 'taxonomy_vocabulary.delete'
 
 entity.taxonomy_vocabulary.reset_form:
   path: '/admin/structure/taxonomy/manage/{taxonomy_vocabulary}/reset'
@@ -70,15 +35,7 @@ entity.taxonomy_vocabulary.overview_form:
   path: '/admin/structure/taxonomy/manage/{taxonomy_vocabulary}/overview'
   defaults:
     _form: 'Drupal\taxonomy\Form\OverviewTerms'
-    _title_callback: 'Drupal\taxonomy\Controller\TaxonomyController::vocabularyTitle'
+    _title_callback: 'Drupal\Core\Entity\Controller\EntityController::title'
   requirements:
     _entity_access: 'taxonomy_vocabulary.view'
 
-entity.taxonomy_term.canonical:
-  path: '/taxonomy/term/{taxonomy_term}'
-  defaults:
-    _entity_view: 'taxonomy_term.full'
-    _title: 'Taxonomy term'
-    _title_callback: '\Drupal\taxonomy\Controller\TaxonomyController::termTitle'
-  requirements:
-    _entity_access: 'taxonomy_term.view'
diff --git a/core/modules/user/src/Entity/Role.php b/core/modules/user/src/Entity/Role.php
index 930c04a..14daf5f 100644
--- a/core/modules/user/src/Entity/Role.php
+++ b/core/modules/user/src/Entity/Role.php
@@ -24,8 +24,11 @@
  *     "list_builder" = "Drupal\user\RoleListBuilder",
  *     "form" = {
  *       "default" = "Drupal\user\RoleForm",
- *       "delete" = "Drupal\Core\Entity\EntityDeleteForm"
- *     }
+ *       "delete" = "Drupal\Core\Entity\EntityDeleteForm",
+ *     },
+ *     "route_provider" = {
+ *       "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
+ *     },
  *   },
  *   admin_permission = "administer permissions",
  *   config_prefix = "role",
diff --git a/core/modules/user/src/Entity/User.php b/core/modules/user/src/Entity/User.php
index 05b642a..c798e89 100644
--- a/core/modules/user/src/Entity/User.php
+++ b/core/modules/user/src/Entity/User.php
@@ -32,7 +32,7 @@
  *     "list_builder" = "Drupal\user\UserListBuilder",
  *     "views_data" = "Drupal\user\UserViewsData",
  *     "route_provider" = {
- *       "html" = "Drupal\user\Entity\UserRouteProvider",
+ *       "html" = "Drupal\user\Entity\UserHtmlRouteProvider",
  *     },
  *     "form" = {
  *       "default" = "Drupal\user\ProfileForm",
diff --git a/core/modules/user/src/Entity/UserHtmlRouteProvider.php b/core/modules/user/src/Entity/UserHtmlRouteProvider.php
new file mode 100644
index 0000000..d4d56a8
--- /dev/null
+++ b/core/modules/user/src/Entity/UserHtmlRouteProvider.php
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\user\Entity\UserHtmlRouteProvider.
+ */
+
+namespace Drupal\user\Entity;
+
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\Routing\AdminHtmlRouteProvider;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Provides HTML routes for the user entity type.
+ */
+class UserHtmlRouteProvider extends AdminHtmlRouteProvider {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRoutes(EntityTypeInterface $entity_type) {
+    $collection = parent::getRoutes($entity_type);
+
+    $collection->get('entity.user.canonical')->setDefault('_title_callback', 'Drupal\user\Controller\UserController::userTitle');
+
+    $collection->get('entity.user.edit_form')
+      ->setDefault('_title_callback', 'Drupal\user\Controller\UserController::userTitle');
+
+    $collection->remove('entity.user.delete');
+
+    $route = (new Route('/user/{user}/cancel'))
+      ->setDefaults([
+        '_title' => 'Cancel account',
+        '_entity_form' => 'user.cancel',
+      ])
+      ->setOption('_admin_route', TRUE)
+      ->setRequirement('_entity_access', 'user.delete');
+    $collection->add('entity.user.cancel_form', $route);
+
+    return $collection;
+  }
+
+}
diff --git a/core/modules/user/src/Entity/UserRouteProvider.php b/core/modules/user/src/Entity/UserRouteProvider.php
deleted file mode 100644
index f4cfd79..0000000
--- a/core/modules/user/src/Entity/UserRouteProvider.php
+++ /dev/null
@@ -1,54 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\user\Entity\UserRouteProvider.
- */
-
-namespace Drupal\user\Entity;
-
-use Drupal\Core\Entity\EntityTypeInterface;
-use Drupal\Core\Entity\Routing\EntityRouteProviderInterface;
-use Symfony\Component\Routing\Route;
-use Symfony\Component\Routing\RouteCollection;
-
-/**
- * Provides routes for the user entity.
- */
-class UserRouteProvider implements EntityRouteProviderInterface {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getRoutes(EntityTypeInterface $entity_type) {
-    $route_collection = new RouteCollection();
-    $route = (new Route('/user/{user}'))
-      ->setDefaults([
-        '_entity_view' => 'user.full',
-        '_title_callback' => 'Drupal\user\Controller\UserController::userTitle',
-      ])
-      ->setRequirement('_entity_access', 'user.view');
-    $route_collection->add('entity.user.canonical', $route);
-
-    $route = (new Route('/user/{user}/edit'))
-      ->setDefaults([
-        '_entity_form' => 'user.default',
-        '_title_callback' => 'Drupal\user\Controller\UserController::userTitle',
-      ])
-      ->setOption('_admin_route', TRUE)
-      ->setRequirement('_entity_access', 'user.update');
-    $route_collection->add('entity.user.edit_form', $route);
-
-    $route = (new Route('/user/{user}/cancel'))
-      ->setDefaults([
-        '_title' => 'Cancel account',
-        '_entity_form' => 'user.cancel',
-      ])
-      ->setOption('_admin_route', TRUE)
-      ->setRequirement('_entity_access', 'user.delete');
-    $route_collection->add('entity.user.cancel_form', $route);
-
-    return $route_collection;
-  }
-
-}
diff --git a/core/modules/user/user.routing.yml b/core/modules/user/user.routing.yml
index 6dc4fdb..f82ea9e 100644
--- a/core/modules/user/user.routing.yml
+++ b/core/modules/user/user.routing.yml
@@ -88,22 +88,6 @@ user.role_add:
   requirements:
     _permission: 'administer permissions'
 
-entity.user_role.edit_form:
-  path: '/admin/people/roles/manage/{user_role}'
-  defaults:
-    _entity_form: user_role.default
-    _title: 'Edit role'
-  requirements:
-    _entity_access: user_role.update
-
-entity.user_role.delete_form:
-  path: '/admin/people/roles/manage/{user_role}/delete'
-  defaults:
-    _entity_form: user_role.delete
-    _title: 'Delete role'
-  requirements:
-    _entity_access: user_role.delete
-
 user.pass:
   path: '/user/password'
   defaults:
diff --git a/core/modules/views_ui/src/ViewHtmlRouteProvider.php b/core/modules/views_ui/src/ViewHtmlRouteProvider.php
new file mode 100644
index 0000000..6559a74
--- /dev/null
+++ b/core/modules/views_ui/src/ViewHtmlRouteProvider.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\views_ui\ViewHtmlRouteProvider.
+ */
+
+namespace Drupal\views_ui;
+
+
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider;
+
+/**
+ * Provides HTML routes for the view entity type.
+ */
+class ViewHtmlRouteProvider extends DefaultHtmlRouteProvider {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getEditFormRoute(EntityTypeInterface $entity_type) {
+    $route = parent::getEditFormRoute($entity_type);
+
+    // Replace the _entity_form with a custom _controller.
+    $route
+      ->setDefault('_entity_form', NULL)
+      ->setDefault('_controller', '\Drupal\views_ui\Controller\ViewsUIController::edit')
+      ->setOptions(['parameters' => ['view' => ['tempstore' => TRUE, 'type' => 'entity:view']]]);
+
+    return $route;
+  }
+
+}
diff --git a/core/modules/views_ui/views_ui.module b/core/modules/views_ui/views_ui.module
index 6712c5d..6e51b60 100644
--- a/core/modules/views_ui/views_ui.module
+++ b/core/modules/views_ui/views_ui.module
@@ -53,6 +53,7 @@ function views_ui_entity_type_build(array &$entity_types) {
     ->setFormClass('delete', 'Drupal\Core\Entity\EntityDeleteForm')
     ->setFormClass('break_lock', 'Drupal\views_ui\Form\BreakLockForm')
     ->setListBuilderClass('Drupal\views_ui\ViewListBuilder')
+    ->setHandlerClass('route_provider', ['html' => 'Drupal\views_ui\ViewHtmlRouteProvider'])
     ->setLinkTemplate('edit-form', '/admin/structure/views/view/{view}')
     ->setLinkTemplate('edit-display-form', '/admin/structure/views/view/{view}/edit/{display_id}')
     ->setLinkTemplate('preview-form', '/admin/structure/views/view/{view}/preview/{display_id}')
diff --git a/core/modules/views_ui/views_ui.routing.yml b/core/modules/views_ui/views_ui.routing.yml
index 0b95812..66b8cfa 100644
--- a/core/modules/views_ui/views_ui.routing.yml
+++ b/core/modules/views_ui/views_ui.routing.yml
@@ -72,14 +72,6 @@ entity.view.duplicate_form:
   requirements:
     _entity_access: view.duplicate
 
-entity.view.delete_form:
-  path: '/admin/structure/views/view/{view}/delete'
-  defaults:
-    _entity_form: 'view.delete'
-    _title: 'Delete view'
-  requirements:
-    _entity_access: view.delete
-
 views_ui.autocomplete:
   path: '/admin/views/ajax/autocomplete/tag'
   defaults:
@@ -87,18 +79,6 @@ views_ui.autocomplete:
   requirements:
     _permission: 'administer views'
 
-entity.view.edit_form:
-  path: '/admin/structure/views/view/{view}'
-  options:
-    parameters:
-      view:
-        tempstore: TRUE
-        type: entity:view
-  defaults:
-    _controller: '\Drupal\views_ui\Controller\ViewsUIController::edit'
-  requirements:
-    _entity_access: view.update
-
 entity.view.edit_display_form:
   path: '/admin/structure/views/view/{view}/edit/{display_id}'
   options:
