 .../block_content/block_content.routing.yml        | 51 ----------------
 .../block_content/src/Entity/BlockContent.php      |  7 ++-
 .../src/Entity/BlockContentHtmlRouteProvider.php   | 70 ++++++++++++++++++++++
 core/modules/comment/comment.routing.yml           | 27 ---------
 core/modules/comment/src/Entity/Comment.php        |  5 +-
 .../src/Entity/CommentHtmlRouteProvider.php        | 24 ++++++++
 core/modules/shortcut/shortcut.routing.yml         | 35 -----------
 .../shortcut/src/Controller/ShortcutController.php |  3 +
 core/modules/shortcut/src/Entity/Shortcut.php      |  6 +-
 .../src/Entity/ShortcutHtmlRouteProvider.php       | 62 +++++++++++++++++++
 .../taxonomy/src/Controller/TaxonomyController.php |  3 +
 core/modules/taxonomy/src/Entity/Term.php          |  8 ++-
 .../taxonomy/src/Entity/TermHtmlRouteProvider.php  | 37 ++++++++++++
 core/modules/taxonomy/taxonomy.routing.yml         | 30 ----------
 14 files changed, 221 insertions(+), 147 deletions(-)

diff --git a/core/modules/block_content/block_content.routing.yml b/core/modules/block_content/block_content.routing.yml
index 75ea9b2..e3e4a84 100644
--- a/core/modules/block_content/block_content.routing.yml
+++ b/core/modules/block_content/block_content.routing.yml
@@ -6,26 +6,6 @@ entity.block_content_type.collection:
   requirements:
     _permission: 'administer blocks'
 
-block_content.add_page:
-  path: '/block/add'
-  defaults:
-    _controller: '\Drupal\block_content\Controller\BlockContentController::add'
-    _title: 'Add custom block'
-  options:
-    _admin_route: TRUE
-  requirements:
-    _permission: 'administer blocks'
-
-block_content.add_form:
-  path: '/block/add/{block_content_type}'
-  defaults:
-    _controller: '\Drupal\block_content\Controller\BlockContentController::addForm'
-    _title_callback: 'Drupal\block_content\Controller\BlockContentController::getAddFormTitle'
-  options:
-    _admin_route: TRUE
-  requirements:
-    _permission: 'administer blocks'
-
 entity.block_content_type.delete_form:
   path: '/admin/structure/block/block-content/manage/{block_content_type}/delete'
   defaults:
@@ -36,37 +16,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'
-    block_content: \d+
-
-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'
-    block_content: \d+
-
-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: \d+
-
 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 9e0531e..20d0f41 100644
--- a/core/modules/block_content/src/Entity/BlockContent.php
+++ b/core/modules/block_content/src/Entity/BlockContent.php
@@ -29,13 +29,18 @@
  *       "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\block_content\Entity\BlockContentHtmlRouteProvider",
+ *     },
  *   },
  *   admin_permission = "administer blocks",
  *   base_table = "block_content",
  *   revision_table = "block_content_revision",
  *   data_table = "block_content_field_data",
  *   links = {
+ *     "add-page" = "/block/add",
+ *     "add-form" = "/block/add/{block_content_type}",
  *     "canonical" = "/block/{block_content}",
  *     "delete-form" = "/block/{block_content}/delete",
  *     "edit-form" = "/block/{block_content}",
diff --git a/core/modules/block_content/src/Entity/BlockContentHtmlRouteProvider.php b/core/modules/block_content/src/Entity/BlockContentHtmlRouteProvider.php
new file mode 100644
index 0000000..12534b8
--- /dev/null
+++ b/core/modules/block_content/src/Entity/BlockContentHtmlRouteProvider.php
@@ -0,0 +1,70 @@
+<?php
+
+namespace Drupal\block_content\Entity;
+
+use Drupal\block_content\Controller\BlockContentController;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\Routing\AdminHtmlRouteProvider;
+
+/**
+ * Provides HTML routes for block contents.
+ */
+class BlockContentHtmlRouteProvider extends AdminHtmlRouteProvider {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRoutes(EntityTypeInterface $entity_type) {
+    $collection = parent::getRoutes($entity_type);
+
+    // Rename the add page and add form routes for backwards compatibility.
+    $entity_type_id = $entity_type->id();
+    if ($add_page_route = $collection->get("entity.{$entity_type_id}.add_page")) {
+      $collection->add('block_content.add_page', $add_page_route);
+      $collection->remove("entity.{$entity_type_id}.add_page");
+    }
+    if ($add_form_route = $collection->get("entity.{$entity_type_id}.add_form")) {
+      $collection->add('block_content.add_form', $add_form_route);
+      $collection->remove("entity.{$entity_type_id}.add_form");
+    }
+
+    return $collection;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getAddPageRoute(EntityTypeInterface $entity_type) {
+    if ($route = parent::getAddPageRoute($entity_type)) {
+      // Set a custom controller which sets a dedicated theme function for
+      // backwards compatibility.
+      $route->setDefault('_controller', BlockContentController::class . '::add');
+      return $route;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getAddFormRoute(EntityTypeInterface $entity_type) {
+    if ($route = parent::getAddFormRoute($entity_type)) {
+      $route
+        // Depending on a request parameter the theme of the block is being
+        // updated which requires a custom controller.
+        ->setDefault('_controller', BlockContentController::class . '::addForm')
+        // Provide a more explicit title for backwards compatibility.
+        ->setDefault('_title_callback', BlockContentController::class . '::getAddFormTitle');
+      return $route;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getCanonicalRoute(EntityTypeInterface $entity_type) {
+    // Modules like Content Translation require a canonical route, but there is
+    // no view page for custom blocks, so we just reuse the edit form.
+    return $this->getEditFormRoute($entity_type);
+  }
+
+}
diff --git a/core/modules/comment/comment.routing.yml b/core/modules/comment/comment.routing.yml
index 3d698b8..df3c3d2 100644
--- a/core/modules/comment/comment.routing.yml
+++ b/core/modules/comment/comment.routing.yml
@@ -16,15 +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: \d+
-
 comment.approve:
   path: '/comment/{comment}/approve'
   defaults:
@@ -36,24 +27,6 @@ comment.approve:
     _csrf_token: 'TRUE'
     comment: \d+
 
-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'
-    comment: \d+
-
-entity.comment.delete_form:
-  path: '/comment/{comment}/delete'
-  defaults:
-    _title: 'Delete'
-    _entity_form: 'comment.delete'
-  requirements:
-    _entity_access: 'comment.delete'
-    comment: \d+
-
 comment.reply:
   path: '/comment/reply/{entity_type}/{entity}/{field_name}/{pid}'
   defaults:
diff --git a/core/modules/comment/src/Entity/Comment.php b/core/modules/comment/src/Entity/Comment.php
index d626be3..28009e1 100644
--- a/core/modules/comment/src/Entity/Comment.php
+++ b/core/modules/comment/src/Entity/Comment.php
@@ -32,7 +32,10 @@
  *       "default" = "Drupal\comment\CommentForm",
  *       "delete" = "Drupal\comment\Form\DeleteForm"
  *     },
- *     "translation" = "Drupal\comment\CommentTranslationHandler"
+ *     "translation" = "Drupal\comment\CommentTranslationHandler",
+ *     "route_provider" = {
+ *       "html" = "Drupal\comment\Entity\CommentHtmlRouteProvider",
+ *     },
  *   },
  *   base_table = "comment",
  *   data_table = "comment_field_data",
diff --git a/core/modules/comment/src/Entity/CommentHtmlRouteProvider.php b/core/modules/comment/src/Entity/CommentHtmlRouteProvider.php
new file mode 100644
index 0000000..65524bd
--- /dev/null
+++ b/core/modules/comment/src/Entity/CommentHtmlRouteProvider.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Drupal\comment\Entity;
+
+use Drupal\comment\Controller\CommentController;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider;
+
+/**
+ * Provides HTML routes for comments.
+ */
+class CommentHtmlRouteProvider extends DefaultHtmlRouteProvider {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getCanonicalRoute(EntityTypeInterface $entity_type) {
+    if ($route = parent::getCanonicalRoute($entity_type)) {
+      $route->setDefault('_controller', CommentController::class . '::commentPermalink');
+      return $route;
+    }
+  }
+
+}
diff --git a/core/modules/shortcut/shortcut.routing.yml b/core/modules/shortcut/shortcut.routing.yml
index 1133357..a052ce0 100644
--- a/core/modules/shortcut/shortcut.routing.yml
+++ b/core/modules/shortcut/shortcut.routing.yml
@@ -46,32 +46,6 @@ entity.shortcut_set.customize_form:
   requirements:
     _entity_access: 'shortcut_set.update'
 
-shortcut.link_add:
-  path: '/admin/config/user-interface/shortcut/manage/{shortcut_set}/add-link'
-  defaults:
-    _controller: '\Drupal\shortcut\Controller\ShortcutController::addForm'
-    _title: 'Add link'
-  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'
-    shortcut: \d+
-
-entity.shortcut.edit_form:
-  path: '/admin/config/user-interface/shortcut/link/{shortcut}'
-  defaults:
-    _entity_form: 'shortcut.default'
-    _title: 'Edit'
-  requirements:
-    _entity_access: 'shortcut.update'
-    shortcut: \d+
-
 entity.shortcut.link_delete_inline:
   path: '/admin/config/user-interface/shortcut/link/{shortcut}/delete-inline'
   defaults:
@@ -81,15 +55,6 @@ entity.shortcut.link_delete_inline:
     _csrf_token: 'TRUE'
     shortcut: \d+
 
-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: \d+
-
 shortcut.set_switch:
   path: '/user/{user}/shortcuts'
   defaults:
diff --git a/core/modules/shortcut/src/Controller/ShortcutController.php b/core/modules/shortcut/src/Controller/ShortcutController.php
index ff0e184..0dae46f 100644
--- a/core/modules/shortcut/src/Controller/ShortcutController.php
+++ b/core/modules/shortcut/src/Controller/ShortcutController.php
@@ -19,6 +19,9 @@ class ShortcutController extends ControllerBase {
    *
    * @return array
    *   The shortcut add form.
+   *
+   * @deprecated in 8.2.x, will be removed in 9.x. Use
+   *   '_entity_form: shortcut.add' in route declarations instead.
    */
   public function addForm(ShortcutSetInterface $shortcut_set) {
     $shortcut = $this->entityManager()->getStorage('shortcut')->create(array('shortcut_set' => $shortcut_set->id()));
diff --git a/core/modules/shortcut/src/Entity/Shortcut.php b/core/modules/shortcut/src/Entity/Shortcut.php
index 6ddaf05..25d7197 100644
--- a/core/modules/shortcut/src/Entity/Shortcut.php
+++ b/core/modules/shortcut/src/Entity/Shortcut.php
@@ -26,7 +26,10 @@
  *       "edit" = "Drupal\shortcut\ShortcutForm",
  *       "delete" = "Drupal\shortcut\Form\ShortcutDeleteForm"
  *     },
- *     "translation" = "Drupal\content_translation\ContentTranslationHandler"
+ *     "translation" = "Drupal\content_translation\ContentTranslationHandler",
+ *     "route_provider" = {
+ *       "html" = "Drupal\shortcut\Entity\ShortcutHtmlRouteProvider",
+ *     },
  *   },
  *   base_table = "shortcut",
  *   data_table = "shortcut_field_data",
@@ -39,6 +42,7 @@
  *     "langcode" = "langcode",
  *   },
  *   links = {
+ *     "add-form" = "/admin/config/user-interface/shortcut/manage/{shortcut_set}/add-link",
  *     "canonical" = "/admin/config/user-interface/shortcut/link/{shortcut}",
  *     "delete-form" = "/admin/config/user-interface/shortcut/link/{shortcut}/delete",
  *     "edit-form" = "/admin/config/user-interface/shortcut/link/{shortcut}",
diff --git a/core/modules/shortcut/src/Entity/ShortcutHtmlRouteProvider.php b/core/modules/shortcut/src/Entity/ShortcutHtmlRouteProvider.php
new file mode 100644
index 0000000..6f9ea13
--- /dev/null
+++ b/core/modules/shortcut/src/Entity/ShortcutHtmlRouteProvider.php
@@ -0,0 +1,62 @@
+<?php
+
+namespace Drupal\shortcut\Entity;
+
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider;
+
+/**
+ * Provides HTML routes for block contents.
+ */
+class ShortcutHtmlRouteProvider extends DefaultHtmlRouteProvider {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRoutes(EntityTypeInterface $entity_type) {
+    $collection = parent::getRoutes($entity_type);
+
+    // Rename the add form route for backwards compatibility.
+    $entity_type_id = $entity_type->id();
+    if ($add_form_route = $collection->get("entity.{$entity_type_id}.add_form")) {
+      $collection->add('shortcut.link_add', $add_form_route);
+      $collection->remove("entity.{$entity_type_id}.add_form");
+    }
+
+    return $collection;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getAddFormRoute(EntityTypeInterface $entity_type) {
+    if ($route = parent::getAddFormRoute($entity_type)) {
+      $route
+        ->setDefault('_title_callback', FALSE)
+        ->setDefault('_title', 'Add link');
+      return $route;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getCanonicalRoute(EntityTypeInterface $entity_type) {
+    // Modules like Content Translation require a canonical route, but there is
+    // no view page for shortcuts, so we just reuse the edit form.
+    return $this->getEditFormRoute($entity_type);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getEditFormRoute(EntityTypeInterface $entity_type) {
+    if ($route = parent::getEditFormRoute($entity_type)) {
+      $route
+        ->setDefault('_title_callback', FALSE)
+        ->setDefault('_title', 'Edit');
+      return $route;
+    }
+  }
+
+}
diff --git a/core/modules/taxonomy/src/Controller/TaxonomyController.php b/core/modules/taxonomy/src/Controller/TaxonomyController.php
index 843cdd7..d55dc50 100644
--- a/core/modules/taxonomy/src/Controller/TaxonomyController.php
+++ b/core/modules/taxonomy/src/Controller/TaxonomyController.php
@@ -20,6 +20,9 @@ class TaxonomyController extends ControllerBase {
    *
    * @return array
    *   The taxonomy term add form.
+   *
+   * @deprecated in 8.2.x, will be removed in 9.x. Use
+   *   '_entity_form: taxonomy_term.add' in route declarations instead.
    */
   public function addForm(VocabularyInterface $taxonomy_vocabulary) {
     $term = $this->entityManager()->getStorage('taxonomy_term')->create(array('vid' => $taxonomy_vocabulary->id()));
diff --git a/core/modules/taxonomy/src/Entity/Term.php b/core/modules/taxonomy/src/Entity/Term.php
index 2e41e2d..1e405bc 100644
--- a/core/modules/taxonomy/src/Entity/Term.php
+++ b/core/modules/taxonomy/src/Entity/Term.php
@@ -23,10 +23,15 @@
  *     "access" = "Drupal\taxonomy\TermAccessControlHandler",
  *     "views_data" = "Drupal\taxonomy\TermViewsData",
  *     "form" = {
+ *       "add" = "Drupal\taxonomy\TermForm",
+ *       "edit" = "Drupal\taxonomy\TermForm",
  *       "default" = "Drupal\taxonomy\TermForm",
  *       "delete" = "Drupal\taxonomy\Form\TermDeleteForm"
  *     },
- *     "translation" = "Drupal\taxonomy\TermTranslationHandler"
+ *     "translation" = "Drupal\taxonomy\TermTranslationHandler",
+ *     "route_provider" = {
+ *       "html" = "Drupal\taxonomy\Entity\TermHtmlRouteProvider",
+ *     },
  *   },
  *   base_table = "taxonomy_term_data",
  *   data_table = "taxonomy_term_field_data",
@@ -43,6 +48,7 @@
  *   field_ui_base_route = "entity.taxonomy_vocabulary.overview_form",
  *   common_reference_target = TRUE,
  *   links = {
+ *     "add-form" = "/admin/structure/taxonomy/manage/{taxonomy_vocabulary}/add",
  *     "canonical" = "/taxonomy/term/{taxonomy_term}",
  *     "delete-form" = "/taxonomy/term/{taxonomy_term}/delete",
  *     "edit-form" = "/taxonomy/term/{taxonomy_term}/edit",
diff --git a/core/modules/taxonomy/src/Entity/TermHtmlRouteProvider.php b/core/modules/taxonomy/src/Entity/TermHtmlRouteProvider.php
new file mode 100644
index 0000000..e0bc9ea
--- /dev/null
+++ b/core/modules/taxonomy/src/Entity/TermHtmlRouteProvider.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace Drupal\taxonomy\Entity;
+
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\Routing\AdminHtmlRouteProvider;
+
+/**
+ * Provides HTML routes for block contents.
+ */
+class TermHtmlRouteProvider extends AdminHtmlRouteProvider {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getAddFormRoute(EntityTypeInterface $entity_type) {
+    if ($route = parent::getAddFormRoute($entity_type)) {
+      $route
+        ->setDefault('_title_callback', FALSE)
+        ->setDefault('_title', 'Add term');
+      return $route;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getEditFormRoute(EntityTypeInterface $entity_type) {
+    if ($route = parent::getEditFormRoute($entity_type)) {
+      $route
+        ->setDefault('_title_callback', FALSE)
+        ->setDefault('_title', 'Edit term');
+      return $route;
+    }
+  }
+
+}
diff --git a/core/modules/taxonomy/taxonomy.routing.yml b/core/modules/taxonomy/taxonomy.routing.yml
index 8a3bd1a..9377d92 100644
--- a/core/modules/taxonomy/taxonomy.routing.yml
+++ b/core/modules/taxonomy/taxonomy.routing.yml
@@ -6,36 +6,6 @@ entity.taxonomy_vocabulary.collection:
   requirements:
     _permission: 'administer taxonomy'
 
-entity.taxonomy_term.add_form:
-  path: '/admin/structure/taxonomy/manage/{taxonomy_vocabulary}/add'
-  defaults:
-    _controller: '\Drupal\taxonomy\Controller\TaxonomyController::addForm'
-    _title: 'Add term'
-  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'
-    taxonomy_term: \d+
-
-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'
-    taxonomy_term: \d+
-
 entity.taxonomy_vocabulary.add_form:
   path: '/admin/structure/taxonomy/add'
   defaults:
