 core/includes/entity.api.php                       |    2 +
 core/includes/entity.inc                           |   96 ++++++-----
 .../Drupal/Core/Entity/EntityRenderController.php  |  145 ++++++++++++++++
 .../Entity/EntityRenderControllerInterface.php     |   74 ++++++++
 core/modules/comment/comment.module                |  117 +------------
 .../modules/comment/lib/Drupal/comment/Comment.php |    5 +
 .../lib/Drupal/comment/CommentRenderController.php |  111 ++++++++++++
 core/modules/field/field.multilingual.inc          |    1 -
 core/modules/file/file.module                      |    6 +
 .../node/lib/Drupal/node/NodeRenderController.php  |   93 ++++++++++
 .../lib/Drupal/node/Tests/NodeBuildContentTest.php |    7 +-
 core/modules/node/node.module                      |  177 +++-----------------
 .../search/Tests/SearchMultilingualEntityTest.php  |    4 +-
 core/modules/search/search.api.php                 |    2 +-
 .../lib/Drupal/taxonomy/TermRenderController.php   |   55 ++++++
 core/modules/taxonomy/taxonomy.module              |  132 ++-------------
 core/modules/taxonomy/taxonomy.pages.inc           |    4 +-
 .../user/lib/Drupal/user/UserRenderController.php  |   30 ++++
 core/modules/user/user.module                      |   57 ++-----
 19 files changed, 645 insertions(+), 473 deletions(-)

diff --git a/core/includes/entity.api.php b/core/includes/entity.api.php
index 9c9d123..e9fb855 100644
--- a/core/includes/entity.api.php
+++ b/core/includes/entity.api.php
@@ -27,6 +27,8 @@
  *     The class has to implement the
  *     Drupal\Core\Entity\EntityStorageControllerInterface interface. Leave blank
  *     to use the Drupal\Core\Entity\DatabaseStorageController implementation.
+ *   - render controller class: The name of the class that is used to render
+ *     the entities. Deafaults to Drupal\Core\Entity\EntityRenderController.
  *   - form controller class: An associative array where the keys are the names
  *     of the different form operations (such as creation, editing or deletion)
  *     and the values are the names of the controller classes. To facilitate
diff --git a/core/includes/entity.inc b/core/includes/entity.inc
index 2b504aa..cab3b30 100644
--- a/core/includes/entity.inc
+++ b/core/includes/entity.inc
@@ -48,6 +48,7 @@ function entity_get_info($entity_type = NULL) {
           'entity class' => 'Drupal\Core\Entity\Entity',
           'controller class' => 'Drupal\Core\Entity\DatabaseStorageController',
           'list controller class' => 'Drupal\Core\Entity\EntityListController',
+          'render controller class' => 'Drupal\Core\Entity\EntityRenderController',
           'form controller class' => array(
             'default' => 'Drupal\Core\Entity\EntityFormController',
           ),
@@ -304,47 +305,6 @@ function entity_get_controller($entity_type) {
 }
 
 /**
- * Invokes hook_entity_prepare_view().
- *
- * If adding a new entity similar to nodes, comments or users, you should
- * invoke this function during the ENTITY_build_content() or
- * ENTITY_view_multiple() phases of rendering to allow other modules to alter
- * the objects during this phase. This is needed for situations where
- * information needs to be loaded outside of ENTITY_load() - particularly
- * when loading entities into one another - i.e. a user object into a node, due
- * to the potential for unwanted side-effects such as caching and infinite
- * recursion. By convention, entity_prepare_view() is called after
- * field_attach_prepare_view() to allow entity level hooks to act on content
- * loaded by field API.
- *
- * @param $entity_type
- *   The type of entity, i.e. 'node', 'user'.
- * @param $entities
- *   The entity objects which are being prepared for view, keyed by object ID.
- *
- * @see hook_entity_prepare_view()
- */
-function entity_prepare_view($entity_type, $entities) {
-  // To ensure hooks are only run once per entity, check for an
-  // entity_view_prepared flag and only process items without it.
-  // @todo: resolve this more generally for both entity and field level hooks.
-  $prepare = array();
-  foreach ($entities as $id => $entity) {
-    if (empty($entity->entity_view_prepared)) {
-      // Add this entity to the items to be prepared.
-      $prepare[$id] = $entity;
-
-      // Mark this item as prepared.
-      $entity->entity_view_prepared = TRUE;
-    }
-  }
-
-  if (!empty($prepare)) {
-    module_invoke_all('entity_prepare_view', $prepare, $entity_type);
-  }
-}
-
-/**
  * Returns the label of an entity.
  *
  * This is a wrapper for Drupal\Core\Entity\EntityInterface::label(). This function
@@ -549,3 +509,57 @@ function entity_list_controller($entity_type) {
   $class = $entity_info['list controller class'];
   return new $class($entity_type, $storage);
 }
+
+/**
+ * Returns an entity render controller for a given entity type.
+ *
+ * @param string $entity_type
+ *   The type of the entity.
+ *
+ * @return Drupal\Core\Entity\EntityRenderControllerInterface
+ *   An entity render controller.
+ *
+ * @see hook_entity_info()
+ */
+function entity_render_controller($entity_type) {
+  $info = entity_get_info($entity_type);
+  $class = $info['render controller class'];
+  return new $class($entity_type);
+}
+
+/**
+ * Returns the render array for an entity.
+ *
+ * @param Drupal\Core\Entity\EntityInterface $entity
+ *   The entity to be rendered.
+ * @param string $view_mode
+ *   The view mode that should be used to display the entity.
+ * @param string $langcode
+ *   (optional) For which language the entity should be rendered, defaults to
+ *   the current content language.
+ *
+ * @return array
+ *   A render array for the entity.
+ */
+function entity_view(EntityInterface $entity, $view_mode, $langcode = NULL) {
+  return entity_render_controller($entity->entityType())->view($entity, $view_mode, $langcode);
+}
+
+/**
+ * Returns the render array for the provided entities.
+ *
+ * @param array $entities
+ *   The entities to be rendered, must be of the same type.
+ * @param string $view_mode
+ *   The view mode that should be used to display the entity.
+ * @param string $langcode
+ *   (optional) For which language the entity should be rendered, defaults to
+ *   the current content language.
+ *
+ * @return array
+ *   A render array for the entities, indexed by the same keys as the
+ *   entities array passed in $entities.
+ */
+function entity_view_multiple(array $entities, $view_mode, $langcode = NULL) {
+  return entity_render_controller(reset($entities)->entityType())->viewMultiple($entities, $view_mode, $langcode);
+}
diff --git a/core/lib/Drupal/Core/Entity/EntityRenderController.php b/core/lib/Drupal/Core/Entity/EntityRenderController.php
new file mode 100644
index 0000000..9101217
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/EntityRenderController.php
@@ -0,0 +1,145 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Entity\EntityRenderController.
+ */
+
+namespace Drupal\Core\Entity;
+
+/**
+ * Base class for entity view controllers.
+ */
+class EntityRenderController implements EntityRenderControllerInterface {
+
+  /**
+   * The type of entities for which this controller is instantiated.
+   *
+   * @var string
+   */
+  protected $entityType;
+
+  public function __construct($entity_type) {
+    $this->entityType = $entity_type;
+  }
+
+  /**
+   * Implements Drupal\Core\Entity\EntityRenderControllerInterface::buildContent().
+   */
+  public function buildContent(array $entities = array(), $view_mode = 'full', $langcode = NULL) {
+    // Allow modules to change the view mode.
+    $context = array('langcode' => $langcode);
+
+    $prepare = array();
+    foreach ($entities as $key => $entity) {
+      // Remove previously built content, if exists.
+      $entity->content = array();
+
+      drupal_alter('entity_view_mode', $view_mode, $entity, $context);
+      $entity->content['#view_mode'] = $view_mode;
+      $prepare[$view_mode][$key] = $entity;
+    }
+
+    // Prepare and build field content, grouped by view mode.
+    foreach ($prepare as $view_mode => $prepare_entities) {
+      $call = array();
+      // To ensure hooks are only run once per entity, check for an
+      // entity_view_prepared flag and only process items without it.
+      foreach ($prepare_entities as $entity) {
+        if (empty($entity->entity_view_prepared)) {
+          // Add this entity to the items to be prepared.
+          $call[$entity->id()] = $entity;
+
+          // Mark this item as prepared.
+          $entity->entity_view_prepared = TRUE;
+        }
+      }
+
+      if (!empty($call)) {
+        field_attach_prepare_view($this->entityType, $call, $view_mode, $langcode);
+        module_invoke_all('entity_prepare_view', $call, $this->entityType);
+      }
+      foreach ($entities as $entity) {
+        $entity->content += field_attach_view($this->entityType, $entity, $view_mode, $langcode);
+      }
+    }
+  }
+
+  /**
+   * Provides entity-specific defaults to the build process.
+   *
+   * @param Drupal\Core\Entity\EntityInterface $entity
+   *   The entity for which the defaults should be provided.
+   * @param string $view_mode
+   *   The view mode that should be used.
+   * @param string $langcode
+   *   (optional) For which language the entity should be prepared, defaults to
+   *   the current content language.
+   *
+   * @return array
+   */
+  protected function getBuildDefaults(EntityInterface $entity, $view_mode, $langcode) {
+    $return = array(
+      '#theme' => $this->entityType,
+      "#{$this->entityType}" => $entity,
+      '#view_mode' => $view_mode,
+      '#langcode' => $langcode,
+    );
+    return $return;
+  }
+
+  /**
+   * Specific per-entity building.
+   *
+   * @param array $build
+   *   The render array that is being created.
+   * @param Drupal\Core\Entity\EntityInterface $entity
+   *   The entity to be prepared.
+   * @param string $view_mode
+   *   The view mode that should be used to prepare the entity.
+   * @param string $langcode
+   *   (optional) For which language the entity should be prepared, defaults to
+   *   the current content language.
+   */
+  protected function alterBuild(array &$build, EntityInterface $entity, $view_mode, $langcode = NULL) { }
+
+  /**
+   * Implements Drupal\Core\Entity\EntityRenderControllerInterface::view().
+   */
+  public function view(EntityInterface $entity, $view_mode = 'full', $langcode = NULL) {
+    $buildList = $this->viewMultiple(array($entity), $view_mode, $langcode);
+    return $buildList[0];
+  }
+
+  /**
+   * Implements Drupal\Core\Entity\EntityRenderControllerInterface::viewMultiple().
+   */
+  public function viewMultiple(array $entities = array(), $view_mode = 'full', $langcode = NULL) {
+    if (!isset($langcode)) {
+      $langcode = language(LANGUAGE_TYPE_CONTENT)->langcode;
+    }
+    $this->buildContent($entities, $view_mode, $langcode);
+
+    $view_hook = "{$this->entityType}_view";
+    $build = array('#sorted' => TRUE);
+    $weight = 0;
+    foreach ($entities as $key => $entity) {
+      $entity_view_mode = isset($entity->content['#view_mode']) ? $entity->content['#view_mode'] : $view_mode;
+      module_invoke_all($view_hook, $entity, $entity_view_mode, $langcode);
+      module_invoke_all('entity_view', $entity, $entity_view_mode, $langcode);
+
+      $build[$key] = $entity->content;
+      // We don't need duplicate rendering info in $entity->content.
+      unset($entity->content);
+
+      $build[$key] += $this->getBuildDefaults($entity, $entity_view_mode, $langcode);
+      $this->alterBuild($build[$key], $entity, $entity_view_mode, $langcode);
+      $build[$key]['#weight'] = $weight++;
+
+      // Allow modules to modify the structured comment.
+      drupal_alter(array($view_hook, 'entity_view'), $build[$key], $entity);
+    }
+
+    return $build;
+  }
+}
diff --git a/core/lib/Drupal/Core/Entity/EntityRenderControllerInterface.php b/core/lib/Drupal/Core/Entity/EntityRenderControllerInterface.php
new file mode 100644
index 0000000..c24ffaa
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/EntityRenderControllerInterface.php
@@ -0,0 +1,74 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Entity\EntityRenderControllerInterface.
+ */
+
+namespace Drupal\Core\Entity;
+
+/**
+ * Defines a common interface for entity view controller classes.
+ */
+interface EntityRenderControllerInterface {
+  /**
+   * Build the structured $content property on the entity.
+   *
+   * @param array $entities
+   *   The entities, implementing EntityInterface, whose content is being built.
+   * @param string $view_mode
+   *   (optional) The view mode that should be used to build the entity.
+   * @param string $langcode
+   *   (optional) For which language the entity should be build, defaults to
+   *   the current content language.
+   *
+   * @return array
+   *   The content array.
+   */
+  public function buildContent(array $entities = array(), $view_mode = 'full', $langcode = NULL);
+
+  /**
+   * Returns the render array for the provided entity.
+   *
+   * @param Drupal\Core\Entity\EntityInterface $entity
+   *   The entity to render.
+   * @param string $view_mode
+   *   (optional) The view mode that should be used to render the entity.
+   * @param string $langcode
+   *   (optional) For which language the entity should be rendered, defaults to
+   *   the current content language.
+   *
+   * @return array
+   *   A render array for the entity.
+   *
+   * @throws \InvalidArgumentException
+   *   Can be thrown when the set of parameters is inconsistent, like when
+   *   trying to view a Comment and passing a Node which is not the one the
+   *   comment belongs to, or not passing one, and having the comment node not
+   *   be available for loading.
+   */
+  public function view(EntityInterface $entity, $view_mode = 'full', $langcode = NULL);
+
+  /**
+   * Returns the render array for the provided entities.
+   *
+   * @param array $entities
+   *   An array of entities implementing EntityInterface to view.
+   * @param string $view_mode
+   *   (optional) The view mode that should be used to render the entity.
+   * @param string $langcode
+   *   (optional) For which language the entity should be rendered, defaults to
+   *   the current content language.
+   *
+   * @return
+   *   A render array for the entities, indexed by the same keys as the
+   *   entities array passed in $entities.
+   *
+   * @throws \InvalidArgumentException
+   *   Can be thrown when the set of parameters is inconsistent, like when
+   *   trying to view Comments and passing a Node which is not the one the
+   *   comments belongs to, or not passing one, and having the comments node not
+   *   be available for loading.
+   */
+  public function viewMultiple(array $entities = array(), $view_mode = 'full', $langcode = NULL);
+}
diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module
index 44b4c04..0cb707e 100644
--- a/core/modules/comment/comment.module
+++ b/core/modules/comment/comment.module
@@ -119,6 +119,7 @@ function comment_entity_info() {
         'uuid' => 'uuid',
       ),
       'bundles' => array(),
+      'render controller class' => 'Drupal\comment\CommentRenderController',
       'view modes' => array(
         'full' => array(
           'label' => t('Full comment'),
@@ -980,104 +981,8 @@ function comment_prepare_thread(&$comments) {
  *   An array as expected by drupal_render().
  */
 function comment_view(Comment $comment, Node $node, $view_mode = 'full', $langcode = NULL) {
-  if (!isset($langcode)) {
-    $langcode = language(LANGUAGE_TYPE_CONTENT)->langcode;
-  }
-
-  // Populate $comment->content with a render() array.
-  comment_build_content($comment, $node, $view_mode, $langcode);
-
-  $build = $comment->content;
-  // We don't need duplicate rendering info in comment->content.
-  unset($comment->content);
-
-  $build += array(
-    '#theme' => 'comment__node_' . $node->type,
-    '#comment' => $comment,
-    '#node' => $node,
-    '#view_mode' => $view_mode,
-    '#language' => $langcode,
-  );
-
-  if (empty($comment->in_preview)) {
-    $prefix = '';
-    $is_threaded = isset($comment->divs) && variable_get('comment_default_mode_' . $node->type, COMMENT_MODE_THREADED) == COMMENT_MODE_THREADED;
-
-    // Add 'new' anchor if needed.
-    if (!empty($comment->first_new)) {
-      $prefix .= "<a id=\"new\"></a>\n";
-    }
-
-    // Add indentation div or close open divs as needed.
-    if ($is_threaded) {
-      $prefix .= $comment->divs <= 0 ? str_repeat('</div>', abs($comment->divs)) : "\n" . '<div class="indented">';
-    }
-
-    // Add anchor for each comment.
-    $prefix .= "<a id=\"comment-$comment->cid\"></a>\n";
-    $build['#prefix'] = $prefix;
-
-    // Close all open divs.
-    if ($is_threaded && !empty($comment->divs_final)) {
-      $build['#suffix'] = str_repeat('</div>', $comment->divs_final);
-    }
-  }
-
-  // Allow modules to modify the structured comment.
-  drupal_alter(array('comment_view', 'entity_view'), $build, $comment);
-
-  return $build;
-}
-
-/**
- * Builds a structured array representing the comment's content.
- *
- * The content built for the comment (field values, comments, file attachments
- * or other comment components) will vary depending on the $view_mode parameter.
- *
- * @param Drupal\comment\Comment $comment
- *   A comment object.
- * @param Drupal\node\Node $node
- *   The node the comment is attached to.
- * @param $view_mode
- *   View mode, e.g. 'full', 'teaser'...
- * @param $langcode
- *   (optional) A language code to use for rendering. Defaults to the global
- *   content language of the current request.
- */
-function comment_build_content(Comment $comment, Node $node, $view_mode = 'full', $langcode = NULL) {
-  if (!isset($langcode)) {
-    $langcode = language(LANGUAGE_TYPE_CONTENT)->langcode;
-  }
-
-  // Remove previously built content, if exists.
-  $comment->content = array();
-
-  // Allow modules to change the view mode.
-  $context = array('langcode' => $langcode);
-  drupal_alter('entity_view_mode', $view_mode, $comment, $context);
-
-  // Build fields content.
-  field_attach_prepare_view('comment', array($comment->cid => $comment), $view_mode, $langcode);
-  entity_prepare_view('comment', array($comment->cid => $comment), $langcode);
-  $comment->content += field_attach_view('comment', $comment, $view_mode, $langcode);
-
-  $comment->content['links'] = array(
-    '#theme' => 'links__comment',
-    '#pre_render' => array('drupal_pre_render_links'),
-    '#attributes' => array('class' => array('links', 'inline')),
-  );
-  if (empty($comment->in_preview)) {
-    $comment->content['links']['comment'] = array(
-      '#theme' => 'links__comment__comment',
-      '#links' => comment_links($comment, $node),
-      '#attributes' => array('class' => array('links', 'inline')),
-    );
-  }
-
-  // Allow modules to make their own additions to the comment.
-  module_invoke_all('comment_view', $comment, $view_mode, $langcode);
-  module_invoke_all('entity_view', $comment, $view_mode, $langcode);
+  $comment->node = $node;
+  return entity_view($comment, $view_mode, $langcode);
 }
 
 /**
@@ -1147,7 +1052,7 @@ function comment_links(Comment $comment, Node $node) {
  * @param $comments
  *   An array of comments as returned by comment_load_multiple().
  * @param Drupal\node\Node $node
- *   The node the comments are attached to.
+ *   The single node the comments are attached to.
  * @param $view_mode
  *   View mode, e.g. 'full', 'teaser'...
  * @param $weight
@@ -1162,18 +1067,8 @@ function comment_links(Comment $comment, Node $node) {
  * @see drupal_render()
  */
 function comment_view_multiple($comments, Node $node, $view_mode = 'full', $weight = 0, $langcode = NULL) {
-  field_attach_prepare_view('comment', $comments, $view_mode, $langcode);
-  entity_prepare_view('comment', $comments, $langcode);
-
-  $build = array(
-    '#sorted' => TRUE,
-  );
-  foreach ($comments as $comment) {
-    $build[$comment->cid] = comment_view($comment, $node, $view_mode, $langcode);
-    $build[$comment->cid]['#weight'] = $weight;
-    $weight++;
-  }
-  return $build;
+  reset($comments)->node = $node;
+  return entity_view_multiple($comments, $view_mode, $weight, $langcode);
 }
 
 /**
diff --git a/core/modules/comment/lib/Drupal/comment/Comment.php b/core/modules/comment/lib/Drupal/comment/Comment.php
index 9b507e7..ef6c16a 100644
--- a/core/modules/comment/lib/Drupal/comment/Comment.php
+++ b/core/modules/comment/lib/Drupal/comment/Comment.php
@@ -37,6 +37,11 @@ class Comment extends Entity implements ContentEntityInterface {
   public $pid;
 
   /**
+   * The ID of the node to which the comment is attached.
+   */
+  public $nid;
+
+  /**
    * The comment language code.
    *
    * @var string
diff --git a/core/modules/comment/lib/Drupal/comment/CommentRenderController.php b/core/modules/comment/lib/Drupal/comment/CommentRenderController.php
new file mode 100644
index 0000000..abb79f4
--- /dev/null
+++ b/core/modules/comment/lib/Drupal/comment/CommentRenderController.php
@@ -0,0 +1,111 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\comment\CommentRenderController.
+ */
+
+namespace Drupal\comment;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityRenderController;
+
+/**
+ * Render controller for comments.
+ */
+class CommentRenderController extends EntityRenderController {
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityRenderController::buildContent().
+   *
+   * In addition to modifying the content key on entities, this implementation
+   * will also set the node key which all comments carry.
+   */
+  public function buildContent(array $entities = array(), $view_mode = 'full', $langcode = NULL) {
+    $return = array();
+    if (empty($entities)) {
+      return $return;
+    }
+
+    // Array is known not be empty, and all comments apply to the same node,
+    // so we can just fetch the node from the first comment.
+    $entity = reset($entities);
+    if (isset($entity->node)) {
+      $node = $entity->node;
+    }
+    else {
+      $node = node_load($entity->nid);
+      if (empty($node)) {
+        throw new \InvalidArgumentException(t('Invalid node for comment.'));
+      }
+    }
+
+    foreach ($entities as $entity) {
+      if (!isset($entity->node)) {
+        $entity->node = $node;
+      }
+    }
+
+    parent::buildContent($entities, $view_mode, $langcode);
+
+    foreach ($entities as $entity) {
+      $entity->content['links'] = array(
+        '#theme' => 'links__comment',
+        '#pre_render' => array('drupal_pre_render_links'),
+        '#attributes' => array('class' => array('links', 'inline')),
+      );
+      if (empty($entity->in_preview)) {
+        $entity->content['links'][$this->entityType] = array(
+          '#theme' => 'links__comment__comment',
+          // The "node" property is specified to be present, so no need to check.
+          '#links' => comment_links($entity, $entity->node),
+          '#attributes' => array('class' => array('links', 'inline')),
+        );
+      }
+    }
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityRenderController::getBuildDefaults().
+   */
+  protected function getBuildDefaults(EntityInterface $entity, $view_mode, $langcode) {
+    $return = parent::getBuildDefaults($entity, $view_mode, $langcode);
+    // @todo Accessing $node on an EntityInterface is not clean. Maybe we want
+    // to define some extended interface exposing node.
+    $node = $entity->node;
+    $return['#theme'] = 'comment__node_' . $node->bundle();
+    $return['#node'] = $node;
+    return $return;
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityRenderController::alterBuild().
+   */
+  protected function alterBuild(array &$build, EntityInterface $comment, $view_mode, $langcode = NULL) {
+    parent::alterBuild($build, $comment, $view_mode, $langcode);
+    if (empty($comment->in_preview)) {
+      $prefix = '';
+      $is_threaded = isset($comment->divs)
+        && variable_get('comment_default_mode_' . $comment->bundle(), COMMENT_MODE_THREADED) == COMMENT_MODE_THREADED;
+
+      // Add 'new' anchor if needed.
+      if (!empty($comment->first_new)) {
+        $prefix .= "<a id=\"new\"></a>\n";
+      }
+
+      // Add indentation div or close open divs as needed.
+      if ($is_threaded) {
+        $prefix .= $comment->divs <= 0 ? str_repeat('</div>', abs($comment->divs)) : "\n" . '<div class="indented">';
+      }
+
+      // Add anchor for each comment.
+      $prefix .= "<a id=\"comment-$comment->cid\"></a>\n";
+      $build['#prefix'] = $prefix;
+
+      // Close all open divs.
+      if ($is_threaded && !empty($comment->divs_final)) {
+        $build['#suffix'] = str_repeat('</div>', $comment->divs_final);
+      }
+    }
+  }
+}
diff --git a/core/modules/field/field.multilingual.inc b/core/modules/field/field.multilingual.inc
index f84b30a..9161999 100644
--- a/core/modules/field/field.multilingual.inc
+++ b/core/modules/field/field.multilingual.inc
@@ -304,7 +304,6 @@ function field_language($entity_type, $entity, $field_name = NULL, $langcode = N
   $id = $entity->bundle();
   $bundle = $entity->bundle();
   $langcode = field_valid_language($langcode, FALSE);
-
   if (!isset($display_langcodes[$entity_type][$id][$langcode])) {
     $display_langcode = array();
 
diff --git a/core/modules/file/file.module b/core/modules/file/file.module
index 0f6cb64..62c8fce 100644
--- a/core/modules/file/file.module
+++ b/core/modules/file/file.module
@@ -100,6 +100,12 @@ function file_entity_info() {
         'label' => 'filename',
         'uuid' => 'uuid',
       ),
+      'view modes' => array(
+        'full' => array(
+          'label' => t('File default'),
+          'custom settings' => FALSE,
+        ),
+      ),
       'static cache' => FALSE,
     ),
   );
diff --git a/core/modules/node/lib/Drupal/node/NodeRenderController.php b/core/modules/node/lib/Drupal/node/NodeRenderController.php
new file mode 100644
index 0000000..129f325
--- /dev/null
+++ b/core/modules/node/lib/Drupal/node/NodeRenderController.php
@@ -0,0 +1,93 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\node\NodeRenderController.
+ */
+
+namespace Drupal\node;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityRenderController;
+
+/**
+ * Render controller for nodes.
+ */
+class NodeRenderController extends EntityRenderController {
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityRenderController::buildContent().
+   */
+  public function buildContent(array $entities = array(), $view_mode = 'full', $langcode = NULL) {
+    $return = array();
+    if (empty($entities)) {
+      return $return;
+    }
+    parent::buildContent($entities, $view_mode, $langcode);
+
+    foreach ($entities as $key => $entity) {
+      $entity_view_mode = $entity->content['#view_mode'];
+
+      // The 'view' hook can be implemented to overwrite the default function
+      // to display nodes.
+      if (node_hook($entity->bundle(), 'view')) {
+        $entity = node_invoke($entity, 'view', $entity_view_mode, $langcode);
+      }
+
+      $entity->content['links'] = array(
+        '#theme' => 'links__node',
+        '#pre_render' => array('drupal_pre_render_links'),
+        '#attributes' => array('class' => array('links', 'inline')),
+      );
+
+      // Always display a read more link on teasers because we have no way
+      // to know when a teaser view is different than a full view.
+      $links = array();
+      if ($entity_view_mode == 'teaser') {
+        $node_title_stripped = strip_tags($entity->label());
+        $links['node-readmore'] = array(
+          'title' => t('Read more<span class="element-invisible"> about @title</span>', array(
+            '@title' => $node_title_stripped,
+          )),
+          'href' => 'node/' . $entity->nid,
+          'html' => TRUE,
+          'attributes' => array(
+            'rel' => 'tag',
+            'title' => $node_title_stripped,
+          ),
+        );
+      }
+
+      $entity->content['links']['node'] = array(
+        '#theme' => 'links__node__node',
+        '#links' => $links,
+        '#attributes' => array('class' => array('links', 'inline')),
+      );
+
+      // Add Language field text element to node render array.
+      $entity->content['language'] = array(
+        '#type' => 'item',
+        '#title' => t('Language'),
+        '#markup' => language_name($langcode),
+        '#weight' => 0,
+        '#prefix' => '<div id="field-language-display">',
+        '#suffix' => '</div>'
+      );
+    }
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityRenderController::alterBuild().
+   */
+  protected function alterBuild(array &$build, EntityInterface $entity, $view_mode, $langcode = NULL) {
+    parent::alterBuild($build, $entity, $view_mode, $langcode);
+    // Add contextual links for this node, except when the node is already being
+    // displayed on its own page. Modules may alter this behavior (for example,
+    // to restrict contextual links to certain view modes) by implementing
+    // hook_node_view_alter().
+    if (!empty($entity->nid) && !($view_mode == 'full' && node_is_page($entity))) {
+      $build['#contextual_links']['node'] = array('node', array($entity->nid));
+    }
+  }
+
+}
diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeBuildContentTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeBuildContentTest.php
index 2b0dbeb..11afd66 100644
--- a/core/modules/node/lib/Drupal/node/Tests/NodeBuildContentTest.php
+++ b/core/modules/node/lib/Drupal/node/Tests/NodeBuildContentTest.php
@@ -27,8 +27,11 @@ class NodeBuildContentTest extends NodeTestBase {
     $node = $this->drupalCreateNode();
 
     // Set a property in the content array so we can test for its existence later on.
-    $node->content['test_content_property'] = array('#value' => $this->randomString());
-    $content = node_build_content($node);
+    $node->content['test_content_property'] = array(
+      '#value' => $this->randomString(),
+    );
+    $nodes = array($node);
+    $content = entity_render_controller('node')->buildContent($nodes);
 
     // If the property doesn't exist it means the node->content was rebuilt.
     $this->assertFalse(isset($content['test_content_property']), t('Node content was emptied prior to being built.'));
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index 8a41887..e27ecd5 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -216,6 +216,7 @@ function node_entity_info() {
         'bundle' => 'type',
       ),
       'bundles' => array(),
+      'render controller class' => 'Drupal\node\NodeRenderController',
       'view modes' => array(
         'full' => array(
           'label' => t('Full content'),
@@ -1149,145 +1150,6 @@ function node_revision_delete($revision_id) {
 }
 
 /**
- * Generates an array for rendering the given node.
- *
- * @param Drupal\node\Node $node
- *   A node entity.
- * @param $view_mode
- *   (optional) View mode, e.g., 'full', 'teaser'... Defaults to 'full.'
- * @param $langcode
- *   (optional) A language code to use for rendering. Defaults to the global
- *   content language of the current request.
- *
- * @return
- *   An array as expected by drupal_render().
- */
-function node_view(Node $node, $view_mode = 'full', $langcode = NULL) {
-  if (!isset($langcode)) {
-    $langcode = language(LANGUAGE_TYPE_CONTENT)->langcode;
-  }
-
-  // Populate $node->content with a render() array.
-  node_build_content($node, $view_mode, $langcode);
-
-  $build = $node->content;
-  // We don't need duplicate rendering info in node->content.
-  unset($node->content);
-
-  $build += array(
-    '#theme' => 'node',
-    '#node' => $node,
-    '#view_mode' => $view_mode,
-    '#langcode' => $langcode,
-  );
-
-  // Add contextual links for this node, except when the node is already being
-  // displayed on its own page. Modules may alter this behavior (for example,
-  // to restrict contextual links to certain view modes) by implementing
-  // hook_node_view_alter().
-  if (!empty($node->nid) && !($view_mode == 'full' && node_is_page($node))) {
-    $build['#contextual_links']['node'] = array('node', array($node->nid));
-  }
-
-  // Allow modules to modify the structured node.
-  drupal_alter(array('node_view', 'entity_view'), $build, $node);
-
-  return $build;
-}
-
-/**
- * Builds a structured array representing the node's content.
- *
- * The content built for the node (field values, comments, file attachments or
- * other node components) will vary depending on the $view_mode parameter.
- *
- * Drupal core defines the following view modes for nodes, with the following
- * default use cases:
- *   - full (default): node is being displayed on its own page (node/123)
- *   - teaser: node is being displayed on the default home page listing, or on
- *     taxonomy listing pages.
- *   - rss: node displayed in an RSS feed.
- *   If search.module is enabled:
- *   - search_index: node is being indexed for search.
- *   - search_result: node is being displayed as a search result.
- *   If book.module is enabled:
- *   - print: node is being displayed in print-friendly mode.
- * Contributed modules might define additional view modes, or use existing
- * view modes in additional contexts.
- *
- * @param Drupal\node\Node $node
- *   A node entity.
- * @param $view_mode
- *   (optional) View mode, e.g., 'full', 'teaser'... Defaults to 'full.'
- * @param $langcode
- *   (optional) A language code to use for rendering. Defaults to the global
- *   content language of the current request.
- */
-function node_build_content(Node $node, $view_mode = 'full', $langcode = NULL) {
-  if (!isset($langcode)) {
-    $langcode = language(LANGUAGE_TYPE_CONTENT)->langcode;
-  }
-
-  // Remove previously built content, if exists.
-  $node->content = array();
-
-  // Allow modules to change the view mode.
-  $context = array('langcode' => $langcode);
-  drupal_alter('entity_view_mode', $view_mode, $node, $context);
-
-  // The 'view' hook can be implemented to overwrite the default function
-  // to display nodes.
-  if (node_hook($node->type, 'view')) {
-    $node = node_invoke($node, 'view', $view_mode, $langcode);
-  }
-
-  // Build fields content.
-  // In case of a multiple view, node_view_multiple() already ran the
-  // 'prepare_view' step. An internal flag prevents the operation from running
-  // twice.
-  field_attach_prepare_view('node', array($node->nid => $node), $view_mode, $langcode);
-  entity_prepare_view('node', array($node->nid => $node), $langcode);
-  $node->content += field_attach_view('node', $node, $view_mode, $langcode);
-
-  // Always display a read more link on teasers because we have no way
-  // to know when a teaser view is different than a full view.
-  $links = array();
-  $node->content['links'] = array(
-    '#theme' => 'links__node',
-    '#pre_render' => array('drupal_pre_render_links'),
-    '#attributes' => array('class' => array('links', 'inline')),
-  );
-  if ($view_mode == 'teaser') {
-    $node_title_stripped = strip_tags($node->label());
-    $links['node-readmore'] = array(
-      'title' => t('Read more<span class="element-invisible"> about @title</span>', array('@title' => $node_title_stripped)),
-      'href' => 'node/' . $node->nid,
-      'html' => TRUE,
-      'attributes' => array('rel' => 'tag', 'title' => $node_title_stripped),
-    );
-  }
-  $node->content['links']['node'] = array(
-    '#theme' => 'links__node__node',
-    '#links' => $links,
-    '#attributes' => array('class' => array('links', 'inline')),
-  );
-
-  // Add Language field text element to node render array.
-  $node->content['language'] = array(
-    '#type' => 'item',
-    '#title' => t('Language'),
-    '#markup' => language_name($langcode),
-    '#weight' => 0,
-    '#prefix' => '<div id="field-language-display">',
-    '#suffix' => '</div>'
-  );
-
-  // Allow modules to make their own additions to the node.
-  module_invoke_all('node_view', $node, $view_mode, $langcode);
-  module_invoke_all('entity_view', $node, $view_mode, $langcode);
-}
-
-/**
  * Page callback: Generates an array which displays a node detail page.
  *
  * @param Drupal\node\Node $node
@@ -1307,7 +1169,7 @@ function node_show(Node $node, $message = FALSE) {
   }
 
   // For markup consistency with other pages, use node_view_multiple() rather than node_view().
-  $nodes = node_view_multiple(array($node->nid => $node), 'full');
+  $nodes = array('nodes' => node_view_multiple(array($node->nid => $node), 'full'));
 
   // Update the history table, stating that this user viewed this node.
   node_tag_new($node);
@@ -2512,6 +2374,24 @@ function node_feed($nids = FALSE, $channel = array()) {
   return new Response($output, 200, array('Content-Type' =>  'application/rss+xml; charset=utf-8'));
 }
 
+ /**
+  * Generates an array for rendering the given node.
+  *
+ * @param Drupal\node\Node $node
+ *   A node entity.
+ * @param $view_mode
+ *   (optional) View mode, e.g., 'full', 'teaser'... Defaults to 'full.'
+ * @param $langcode
+ *   (optional) A language code to use for rendering. Defaults to the global
+ *   content language of the current request.
+ *
+ * @return
+ *   An array as expected by drupal_render().
+ */
+function node_view(Node $node, $view_mode = 'full', $langcode = NULL) {
+  return entity_view($node, $view_mode, $langcode);
+}
+
 /**
  * Constructs a drupal_render() style array from an array of loaded nodes.
  *
@@ -2519,8 +2399,6 @@ function node_feed($nids = FALSE, $channel = array()) {
  *   An array of nodes as returned by node_load_multiple().
  * @param $view_mode
  *   (optional) View mode, e.g., 'full', 'teaser'... Defaults to 'teaser.'
- * @param $weight
- *   (optional) Integer representing the weight of the first node in the list.
  * @param $langcode
  *   (optional) A language code to use for rendering. Defaults to the global
  *   content language of the current request.
@@ -2528,17 +2406,8 @@ function node_feed($nids = FALSE, $channel = array()) {
  * @return
  *   An array in the format expected by drupal_render().
  */
-function node_view_multiple($nodes, $view_mode = 'teaser', $weight = 0, $langcode = NULL) {
-  field_attach_prepare_view('node', $nodes, $view_mode, $langcode);
-  entity_prepare_view('node', $nodes, $langcode);
-  $build = array();
-  foreach ($nodes as $node) {
-    $build['nodes'][$node->nid] = node_view($node, $view_mode, $langcode);
-    $build['nodes'][$node->nid]['#weight'] = $weight;
-    $weight++;
-  }
-  $build['nodes']['#sorted'] = TRUE;
-  return $build;
+function node_view_multiple($nodes, $view_mode = 'teaser', $langcode = NULL) {
+  return entity_view_multiple($nodes, $view_mode, $langcode);
 }
 
 /**
@@ -2562,7 +2431,7 @@ function node_page_default() {
 
   if (!empty($nids)) {
     $nodes = node_load_multiple($nids);
-    $build = node_view_multiple($nodes);
+    $build['nodes'] = node_view_multiple($nodes);
 
     // 'rss.xml' is a path, not a file, registered in node_menu().
     drupal_add_feed('rss.xml', $site_config->get('name') . ' ' . t('RSS'));
diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchMultilingualEntityTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchMultilingualEntityTest.php
index b423c75..e534971 100644
--- a/core/modules/search/lib/Drupal/search/Tests/SearchMultilingualEntityTest.php
+++ b/core/modules/search/lib/Drupal/search/Tests/SearchMultilingualEntityTest.php
@@ -121,7 +121,9 @@ class SearchMultilingualEntityTest extends SearchTestBase {
       $body_language_variant = end($node->body);
       $search_result = node_search_execute($body_language_variant[0]['value']);
       // See whether we get the same node as a result.
-      $this->assertEqual($search_result[0]['node']->nid, $node->nid, 'The search has resulted the correct node.');
+      $sts = $this->assertTrue(!empty($search_result[0]['node']->nid)
+        && $search_result[0]['node']->nid == $node->nid,
+        'The search has resulted the correct node.');
     }
   }
 }
diff --git a/core/modules/search/search.api.php b/core/modules/search/search.api.php
index 3dcea0d..0dd0a84 100644
--- a/core/modules/search/search.api.php
+++ b/core/modules/search/search.api.php
@@ -362,7 +362,7 @@ function hook_update_index() {
     variable_set('node_cron_last', $node->changed);
 
     // Render the node.
-    node_build_content($node, 'search_index');
+    $build = node_view($node, 'search_index');
     $node->rendered = drupal_render($node->content);
 
     $text = '<h1>' . check_plain($node->label()) . '</h1>' . $node->rendered;
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/TermRenderController.php b/core/modules/taxonomy/lib/Drupal/taxonomy/TermRenderController.php
new file mode 100644
index 0000000..9d8fd30
--- /dev/null
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/TermRenderController.php
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\taxonomy\TermRenderController.
+ */
+
+namespace Drupal\taxonomy;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityRenderController;
+
+/**
+ * Render controller for taxonomy terms.
+ */
+class TermRenderController extends EntityRenderController {
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityRenderController::buildContent().
+   */
+  public function buildContent(array $entities = array(), $view_mode = 'full', $langcode = NULL) {
+    parent::buildContent($entities, $view_mode, $langcode);
+
+    foreach ($entities as $entity) {
+      // Try to add in the core taxonomy pieces like description.
+      $bundle = $entity->bundle();
+      $entity_view_mode = $entity->content['#view_mode'];
+      $settings = field_view_mode_settings($this->entityType, $bundle);
+	    $fields = field_extra_fields_get_display($this->entityType, $bundle, $entity_view_mode);
+      if (!empty($entity->description) && isset($fields['description']) && $fields['description']['visible']) {
+        $entity->content['description'] = array(
+          '#markup' => check_markup($entity->description, $entity->format, '', TRUE),
+          '#weight' => $fields['description']['weight'],
+          '#prefix' => '<div class="taxonomy-term-description">',
+          '#suffix' => '</div>',
+        );
+      }
+    }
+  }
+
+  protected function getBuildDefaults(EntityInterface $entity, $view_mode, $langcode) {
+    $return = parent::getBuildDefaults($entity, $view_mode, $langcode);
+
+    // TODO: rename "term" to "taxonomy_term" in theme_taxonomy_term().
+    $return['#term'] = $return["#{$this->entityType}"];
+    unset($return["#{$this->entityType}"]);
+
+    return $return;
+  }
+
+  protected function alterBuild(array &$build, EntityInterface $entity, $view_mode, $langcode = NULL) {
+    parent::alterBuild($build, $entity, $view_mode, $langcode);
+    $build['#attached']['css'][] = drupal_get_path('module', 'taxonomy') . '/taxonomy.css';
+  }
+}
diff --git a/core/modules/taxonomy/taxonomy.module b/core/modules/taxonomy/taxonomy.module
index c28cc7b..6b60a85 100644
--- a/core/modules/taxonomy/taxonomy.module
+++ b/core/modules/taxonomy/taxonomy.module
@@ -130,6 +130,7 @@ function taxonomy_entity_info() {
         'bundle' => 'machine_name',
       ),
       'bundles' => array(),
+      'render controller class' => 'Drupal\taxonomy\TermRenderController',
       'view modes' => array(
         // @todo View mode for display as a field (when attached to nodes etc).
         'full' => array(
@@ -163,6 +164,12 @@ function taxonomy_entity_info() {
       'label' => 'name',
     ),
     'fieldable' => FALSE,
+    'view modes' => array(
+      'full' => array(
+        'label' => t('Taxonomy vocabulary default'),
+        'custom settings' => FALSE,
+      ),
+    ),
   );
 
   return $return;
@@ -574,138 +581,35 @@ function taxonomy_term_delete_multiple(array $tids) {
  *
  * @param Drupal\taxonomy\Term $term
  *   A taxonomy term object.
- * @return array
- *   A $page element suitable for use by drupal_page_render().
- */
-function taxonomy_term_show(Term $term) {
-  return taxonomy_term_view_multiple(array($term->tid => $term), 'full');
-}
-
-/**
- * Constructs a drupal_render() style array from an array of loaded terms.
- *
- * @param array $terms
- *   An array of taxonomy terms as returned by taxonomy_term_load_multiple().
  * @param string $view_mode
  *   View mode, e.g. 'full', 'teaser'...
- * @param int $weight
- *   An integer representing the weight of the first node in the list.
  * @param string $langcode
  *   (optional) A language code to use for rendering. Defaults to the global
  *   content language of the current request.
  *
  * @return array
- *   An array in the format expected by drupal_render().
- */
-function taxonomy_term_view_multiple(array $terms, $view_mode = 'teaser', $weight = 0, $langcode = NULL) {
-  field_attach_prepare_view('taxonomy_term', $terms, $view_mode, $langcode);
-  entity_prepare_view('taxonomy_term', $terms, $langcode);
-  $build = array();
-  foreach ($terms as $term) {
-    $build['taxonomy_terms'][$term->tid] = taxonomy_term_view($term, $view_mode, $langcode);
-    $build['taxonomy_terms'][$term->tid]['#weight'] = $weight;
-    $weight++;
-  }
-  $build['taxonomy_terms']['#sorted'] = TRUE;
-  return $build;
-}
-
-/**
- * Builds a structured array representing the term's content.
- *
- * The content built for the taxonomy term (field values, file attachments or
- * other term components) will vary depending on the $view_mode parameter.
- *
- * Drupal core defines the following view modes for terms, with the following
- * default use cases:
- *   - full (default): term is displayed on its own page (taxonomy/term/123)
- * Contributed modules might define additional view modes, or use existing
- * view modes in additional contexts.
- *
- * @param Drupal\taxonomy\Term $term
- *   A taxonomy term object.
- * @param string $view_mode
- *   View mode, e.g. 'full', 'teaser'...
- * @param string $langcode
- *   (optional) A language code to use for rendering. Defaults to the global
- *   content language of the current request.
+ *   A $page element suitable for use by drupal_page_render().
  */
-function taxonomy_term_build_content(Term $term, $view_mode = 'full', $langcode = NULL) {
-  if (!isset($langcode)) {
-    $langcode = language(LANGUAGE_TYPE_CONTENT)->langcode;
-  }
-
-  // Remove previously built content, if exists.
-  $term->content = array();
-
-  // Allow modules to change the view mode.
-  $context = array('langcode' => $langcode);
-  drupal_alter('entity_view_mode', $view_mode, $term, $context);
-
-  // Try to add in the core taxonomy pieces like description and nodes.
-  $settings = field_view_mode_settings($term->entityType(), $term->bundle());
-  $fields = field_extra_fields_get_display($term->entityType(), $term->bundle(), $view_mode);
-  if (!empty($term->description) && isset($fields['description']) && $fields['description']['visible']) {
-    $term->content['description'] = array(
-      '#markup' => check_markup($term->description, $term->format, '', TRUE),
-      '#weight' => $fields['description']['weight'],
-      '#prefix' => '<div class="taxonomy-term-description">',
-      '#suffix' => '</div>',
-    );
-  }
-
-  // Build fields content.
-  // In case of a multiple view, taxonomy_term_view_multiple() already ran the
-  // 'prepare_view' step. An internal flag prevents the operation from running
-  // twice.
-  field_attach_prepare_view('taxonomy_term', array($term->tid => $term), $view_mode, $langcode);
-  entity_prepare_view('taxonomy_term', array($term->tid => $term), $langcode);
-  $term->content += field_attach_view('taxonomy_term', $term, $view_mode, $langcode);
-
-  // Allow modules to make their own additions to the taxonomy term.
-  module_invoke_all('taxonomy_term_view', $term, $view_mode, $langcode);
-  module_invoke_all('entity_view', $term, $view_mode, $langcode);
+function taxonomy_term_view(Term $term, $view_mode = 'full', $langcode = NULL) {
+  return entity_view($term, $view_mode, $langcode);
 }
 
-/**
- * Generate an array for rendering the given term.
+ /**
+ * Constructs a drupal_render() style array from an array of loaded terms.
  *
- * @param Drupal\taxonomy\Term $term
- *   A taxonomy term entity.
+ * @param array $terms
+ *   An array of taxonomy terms as returned by taxonomy_term_load_multiple().
  * @param string $view_mode
  *   View mode, e.g. 'full', 'teaser'...
  * @param string $langcode
  *   (optional) A language code to use for rendering. Defaults to the global
  *   content language of the current request.
  *
- * @return
- *   An array as expected by drupal_render().
+ * @return array
+ *   An array in the format expected by drupal_render().
  */
-function taxonomy_term_view(Term $term, $view_mode = 'full', $langcode = NULL) {
-  if (!isset($langcode)) {
-    $langcode = language(LANGUAGE_TYPE_CONTENT)->langcode;
-  }
-
-  // Populate $term->content with a render() array.
-  taxonomy_term_build_content($term, $view_mode, $langcode);
-  $build = $term->content;
-
-  // We don't need duplicate rendering info in $term->content.
-  unset($term->content);
-
-  $build += array(
-    '#theme' => 'taxonomy_term',
-    '#term' => $term,
-    '#view_mode' => $view_mode,
-    '#language' => $langcode,
-  );
-
-  $build['#attached']['css'][] = drupal_get_path('module', 'taxonomy') . '/taxonomy.css';
-
-  // Allow modules to modify the structured term.
-  drupal_alter(array('taxonomy_term_view', 'entity_view'), $build, $term);
-
-  return $build;
+function taxonomy_term_view_multiple(array $terms, $view_mode = 'full', $langcode = NULL) {
+  return entity_view_multiple($terms, $view_mode, $langcode);
 }
 
 /**
diff --git a/core/modules/taxonomy/taxonomy.pages.inc b/core/modules/taxonomy/taxonomy.pages.inc
index 7c77716..f1ec8ea 100644
--- a/core/modules/taxonomy/taxonomy.pages.inc
+++ b/core/modules/taxonomy/taxonomy.pages.inc
@@ -45,10 +45,10 @@ function taxonomy_term_page(Term $term) {
   // Set the non-aliased path as a default shortlink.
   drupal_add_html_head_link(array('rel' => 'shortlink', 'href' => url($uri['path'], array_merge($uri['options'], array('alias' => TRUE)))), TRUE);
 
-  $build = taxonomy_term_show($term);
+  $build['taxonomy_terms'] = taxonomy_term_view_multiple(array($term->id() => $term));
   if ($nids = taxonomy_select_nodes($term->tid, TRUE, variable_get('default_nodes_main', 10))) {
     $nodes = node_load_multiple($nids);
-    $build += node_view_multiple($nodes);
+    $build['nodes'] = node_view_multiple($nodes);
     $build['pager'] = array(
       '#theme' => 'pager',
       '#weight' => 5,
diff --git a/core/modules/user/lib/Drupal/user/UserRenderController.php b/core/modules/user/lib/Drupal/user/UserRenderController.php
new file mode 100644
index 0000000..1a696c1
--- /dev/null
+++ b/core/modules/user/lib/Drupal/user/UserRenderController.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\user\UserRenderController.
+ */
+
+namespace Drupal\user;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityRenderController;
+
+/**
+ * Render controller for users.
+ */
+class UserRenderController extends EntityRenderController {
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityRenderController::getBuildDefaults().
+   */
+  protected function getBuildDefaults(EntityInterface $entity, $view_mode, $langcode) {
+    $return = parent::getBuildDefaults($entity, $view_mode, $langcode);
+
+    // @todo rename "theme_user_profile" to "theme_user", 'account' to 'user'.
+    $return['#theme'] = 'user_profile';
+    $return['#account'] = $return['#user'];
+
+    return $return;
+  }
+}
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index e42002b..fae45ad 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -168,6 +168,7 @@ function user_entity_info() {
           ),
         ),
       ),
+      'render controller class' => 'Drupal\user\UserRenderController',
       'view modes' => array(
         'full' => array(
           'label' => t('User account'),
@@ -2062,61 +2063,25 @@ function user_view_page($account) {
  *   An array as expected by drupal_render().
  */
 function user_view($account, $view_mode = 'full', $langcode = NULL) {
-  if (!isset($langcode)) {
-    $langcode = language(LANGUAGE_TYPE_CONTENT)->langcode;
-  }
-
-  // Retrieve all profile fields and attach to $account->content.
-  user_build_content($account, $view_mode, $langcode);
-
-  $build = $account->content;
-  // We don't need duplicate rendering info in account->content.
-  unset($account->content);
-
-  $build += array(
-    '#theme' => 'user_profile',
-    '#account' => $account,
-    '#view_mode' => $view_mode,
-    '#language' => $langcode,
-  );
-
-  // Allow modules to modify the structured user.
-  drupal_alter(array('user_view', 'entity_view'), $build, $account);
-
-  return $build;
+  return entity_view($account, $view_mode, $langcode);
 }
 
 /**
- * Builds a structured array representing the profile content.
+ * Constructs a drupal_render() style array from an array of loaded users.
  *
- * @param $account
- *   A user object.
+ * @param $accounts
+ *   An array of user accounts as returned by user_load_multiple().
  * @param $view_mode
- *   View mode, e.g. 'full'.
+ *   (optional) View mode, e.g., 'full', 'teaser'... Defaults to 'teaser.'
  * @param $langcode
  *   (optional) A language code to use for rendering. Defaults to the global
  *   content language of the current request.
+ *
+ * @return
+ *   An array in the format expected by drupal_render().
  */
-function user_build_content($account, $view_mode = 'full', $langcode = NULL) {
-  if (!isset($langcode)) {
-    $langcode = language(LANGUAGE_TYPE_CONTENT)->langcode;
-  }
-
-  // Remove previously built content, if exists.
-  $account->content = array();
-
-  // Allow modules to change the view mode.
-  $context = array('langcode' => $langcode);
-  drupal_alter('entity_view_mode', $view_mode, $account, $context);
-
-  // Build fields content.
-  field_attach_prepare_view('user', array($account->uid => $account), $view_mode, $langcode);
-  entity_prepare_view('user', array($account->uid => $account), $langcode);
-  $account->content += field_attach_view('user', $account, $view_mode, $langcode);
-
-  // Populate $account->content with a render() array.
-  module_invoke_all('user_view', $account, $view_mode, $langcode);
-  module_invoke_all('entity_view', $account, $view_mode, $langcode);
+function user_view_multiple($accounts, $view_mode = 'full', $langcode = NULL) {
+  return entity_view($accounts, $view_mode, $langcode);
 }
 
 /**
