diff --git a/core/includes/entity.api.php b/core/includes/entity.api.php
index 102fa70..869cb04 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 b651e32..9d9d994 100644
--- a/core/includes/entity.inc
+++ b/core/includes/entity.inc
@@ -47,6 +47,7 @@ function entity_get_info($entity_type = NULL) {
           'fieldable' => FALSE,
           'entity class' => 'Drupal\Core\Entity\Entity',
           'controller class' => 'Drupal\Core\Entity\DatabaseStorageController',
+          'render controller class' => 'Drupal\Core\Entity\EntityRenderController',
           'form controller class' => array(
             'default' => 'Drupal\Core\Entity\EntityFormController',
           ),
@@ -406,6 +407,23 @@ function entity_form_controller($entity_type, $operation = 'default') {
 }
 
 /**
+ * Factory method to returns an entity render controller.
+ *
+ * @see hook_entity_info()
+ *
+ * @param string $entity_type
+ *   The type of the entity.
+ *
+ * @return Drupal\Core\Entity\EntityRenderControllerInterface
+ *   An entity render controller instance.
+ */
+function entity_render_controller($entity_type) {
+  $info = entity_get_info($entity_type);
+  $class = $info['render controller class'];
+  return new $class($entity_type);
+}
+
+/**
  * Returns the form id for the given entity and operation.
  *
  * @param EntityInterface $entity
diff --git a/core/lib/Drupal/Core/Entity/EntityFieldQuery.php b/core/lib/Drupal/Core/Entity/EntityFieldQuery.php
index 0c054ce..7aa5c01 100644
--- a/core/lib/Drupal/Core/Entity/EntityFieldQuery.php
+++ b/core/lib/Drupal/Core/Entity/EntityFieldQuery.php
@@ -436,7 +436,7 @@ class EntityFieldQuery {
    * @return Drupal\Core\Entity\EntityFieldQuery
    *   The called object.
    *
-   * @see Drupal\entity\EntityFieldQuery::propertyLanguageCondition()
+   * @see Drupal\Core\Entity\EntityFieldQuery::propertyLanguageCondition()
    */
   public function propertyCondition($column, $value, $operator = NULL, $langcode_group = 0) {
     $this->properties[$langcode_group][$column] = $column;
@@ -487,11 +487,11 @@ class EntityFieldQuery {
    *   condition with a related set of property conditions. By default all
    *   conditions belong to the same group.
    *
-   * @return Drupal\entity\EntityFieldQuery
+   * @return Drupal\Core\Entity\EntityFieldQuery
    *   The called object.
    *
-   * @see Drupal\entity\EntityFieldQuery::addFieldCondition()
-   * @see Drupal\entity\EntityFieldQuery::deleted()
+   * @see Drupal\Core\Entity\EntityFieldQuery::addFieldCondition()
+   * @see Drupal\Core\Entity\EntityFieldQuery::deleted()
    */
   public function propertyLanguageCondition($langcode = NULL, $operator = NULL, $langcode_group = 0) {
     // We have a separate method here to ensure there is a distinction at API
@@ -603,7 +603,7 @@ class EntityFieldQuery {
    * @return Drupal\Core\Entity\EntityFieldQuery
    *   The called object.
    *
-   * @see Drupal\entity\EntityFieldQuery::propertyLanguageCondition()
+   * @see Drupal\Core\Entity\EntityFieldQuery::propertyLanguageCondition()
    */
   public function propertyOrderBy($column, $direction = 'ASC', $langcode_group = 0) {
     $this->properties[$langcode_group][$column] = $column;
diff --git a/core/lib/Drupal/Core/Entity/EntityRenderController.php b/core/lib/Drupal/Core/Entity/EntityRenderController.php
new file mode 100644
index 0000000..c9ac399
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/EntityRenderController.php
@@ -0,0 +1,142 @@
+<?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;
+  }
+
+  /**
+   * @see \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);
+
+    $return = 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;
+      $return[$key] = $entity->content;
+    }
+    return $return;
+  }
+
+  /**
+   * Build fields content.
+   *
+   * In case of a multiple view, "{$entity}_view_multiple"() already ran the
+   * 'prepare_view' step. An internal flag prevents the operation from running
+   * twice.
+   *
+   * @param EntityInterface $entity
+   * @param string $view_mode
+   * @param string $langcode
+   */
+  protected function prepareView(EntityInterface $entity, $view_mode, $langcode) {
+    $entry = array($entity->id() => $entity);
+    field_attach_prepare_view($this->entityType, $entry, $view_mode, $langcode);
+    entity_prepare_view($this->entityType, $entry, $langcode);
+    $entity->content += field_attach_view($this->entityType, $entity, $view_mode, $langcode);
+  }
+
+  /**
+   * Provide entity-specific defaults to the build process.
+   *
+   * @param EntityInterface $entity
+   * @param string $view_mode
+   * @param string $langcode
+   *
+   * @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.
+   *
+   * This method is typically not invoked by the child reimplementations, but
+   * simply replaced.
+   *
+   * @param array $build
+   * @param EntityInterface $entity
+   * @param string $view_mode
+   * @param string $langcode
+   *
+   * @return array
+   *   The build array.
+   */
+  protected function prepareBuild(array $build, EntityInterface $entity, $view_mode, $langcode = NULL) {
+    return $build;
+  }
+
+  /**
+   * @see \Drupal\Core\Entity\EntityRenderControllerInterface::viewSingle()
+   */
+  public function viewSingle(EntityInterface $entity, $view_mode = 'full', $langcode = NULL) {
+    $buildList = $this->viewMultiple(array($entity), $view_mode, $langcode);
+    return $buildList[0];
+  }
+
+  /**
+   * @see \Drupal\Core\Entity\EntityRenderControllerInterface::viewMultiple()
+   */
+  public function viewMultiple(array $entities = array(), $view_mode = 'full', $weight = 0, $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);
+    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);
+      $build[$key] += $this->prepareBuild($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..3799bc8
--- /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 EntityInterface $entity
+   *   The entities, implementing EntityInterface, whose content is being built.
+   * @param string $view_mode
+   *   The view mode to use when building that entity. All core entities include
+   *   at least a default "full" view mode.
+   * @param string $langcode
+   *   The language for which to built the content of the entity.
+   *
+   * @return array
+   *   The content array.
+   */
+  public function buildContent(array &$entities = array(), $view_mode = 'full', $langcode = NULL);
+
+  /**
+   * Main Entity view method.
+   *
+   * @param EntityInterface $entity
+   *   The entity to view.
+   * @param string $view_mode
+   *   The view mode to use when building that entity. All core entities include
+   *   at least a default "full" view mode.
+   * @param string $langcode
+   *   The language for which to view the entity.
+   *
+   * @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 viewSingle(EntityInterface $entity, $view_mode = 'full', $langcode = NULL);
+
+  /**
+   * Multiple Entity view method.
+   *
+   * @param array $entities
+   *   An array of entities implementing EntityInterface to view.
+   * @param string $view_mode
+   *   The view mode to use when building that entity. All core entities include
+   *   at least a default "full" view mode.
+   * @param string $langcode
+   *   The language for which to view the entity.
+   *
+   * @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..e58dac9 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_render_controller('comment')->viewMultiple($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..b270940
--- /dev/null
+++ b/core/modules/comment/lib/Drupal/comment/CommentRenderController.php
@@ -0,0 +1,103 @@
+<?php
+namespace Drupal\comment;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityRenderController;
+
+class CommentRenderController extends EntityRenderController {
+
+  /**
+   * In addition to modifying the content key on entities, this implementation
+   * will also set the node key which all comments carry.
+   *
+   * @see \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);
+
+    // 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 $key => $entity) {
+      if (!isset($entity->node)) {
+        $entity->node = $node;
+      }
+      $this->prepareView($entity, $entity->content['#view_mode'], $langcode);
+
+      $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')),
+        );
+      }
+      $return[$key] = $entity->content;
+    }
+
+    return $return;
+  }
+
+  /**
+   * @todo Accessing $node on an EntityInterface is not clean. Maybe we want
+   *   to define some extended interface exposing node.
+   */
+  protected function getBuildDefaults(EntityInterface $entity, $view_mode, $langcode) {
+    $return = parent::getBuildDefaults($entity, $view_mode, $langcode);
+    $node = $entity->node;
+    $return = array_merge($return, array(
+      '#theme' => 'comment__node_' . $node->bundle(),
+      '#node' => $node,
+    ));
+    return $return;
+  }
+
+  protected function prepareBuild(array $build, EntityInterface $comment, $view_mode, $langcode = NULL) {
+    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);
+      }
+    }
+
+    return $build;
+  }
+}
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..73405b0
--- /dev/null
+++ b/core/modules/node/lib/Drupal/node/NodeRenderController.php
@@ -0,0 +1,72 @@
+<?php
+namespace Drupal\node;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityRenderController;
+
+class NodeRenderController extends EntityRenderController {
+
+  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);
+      }
+
+      $this->prepareView($entity, $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')),
+      );
+      $return[$key] = $entity->content;
+    }
+
+    return $return;
+  }
+
+  protected function prepareBuild(array $build, EntityInterface $entity, $view_mode, $langcode = NULL) {
+    // 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));
+    }
+
+    return $build;
+  }
+}
diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeBuildContentTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeBuildContentTest.php
index 2b0dbeb..0254f4a 100644
--- a/core/modules/node/lib/Drupal/node/Tests/NodeBuildContentTest.php
+++ b/core/modules/node/lib/Drupal/node/Tests/NodeBuildContentTest.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\node\Tests;
 
+use Drupal\Core\Entity\EntityRenderController;
+
 /**
  * Test to ensure that a node's content is always rebuilt.
  */
@@ -27,8 +29,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 = EntityRenderController::create('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..e757e08 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'),
@@ -433,6 +434,9 @@ function node_type_get_types() {
  */
 function node_type_get_base($type) {
   $types = _node_types_build()->types;
+  if (is_object($type)) {
+    ddebug_backtrace();
+  }
   return isset($types[$type]) && isset($types[$type]->base) ? $types[$type]->base : FALSE;
 }
 
@@ -1151,53 +1155,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.
  *
@@ -1307,7 +1264,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);
@@ -2529,16 +2486,7 @@ function node_feed($nids = FALSE, $channel = array()) {
  *   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;
+  return entity_render_controller('node')->viewMultiple($nodes, $view_mode, $weight, $langcode);
 }
 
 /**
@@ -2562,7 +2510,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/system/tests/modules/taxonomy_test/taxonomy_test.module b/core/modules/system/tests/modules/taxonomy_test/taxonomy_test.module
index 63f6406..0bf50a7 100644
--- a/core/modules/system/tests/modules/taxonomy_test/taxonomy_test.module
+++ b/core/modules/system/tests/modules/taxonomy_test/taxonomy_test.module
@@ -87,6 +87,34 @@ function taxonomy_test_entity_view($entity, $view_mode, $langcode) {
 }
 
 /**
+ * Implements hook_taxonomy_term_view().
+ */
+function taxonomy_test_taxonomy_term_view($term, $view_mode, $langcode) {
+  if ($view_mode == 'full') {
+    $term->content['taxonomy_test_term_view_check'] = array(
+      '#prefix' => '<div>',
+      '#markup' => t('The antonym is %antonym', array('%antonym' => $term->antonym)),
+      '#suffix' => '</div>',
+      '#weight' => 10,
+    );
+  }
+}
+
+/**
+ * Implements hook_entity_view().
+ */
+function taxonomy_test_entity_view($entity, $view_mode, $langcode) {
+  if ($entity->entityType() == 'taxonomy_term' && $view_mode == 'full') {
+    $entity->content['taxonomy_test_entity_view_check'] = array(
+      '#prefix' => '<div>',
+      '#markup' => t('The antonym is %antonym', array('%antonym' => $entity->antonym)),
+      '#suffix' => '</div>',
+      '#weight' => 20,
+    );
+  }
+}
+
+/**
  * Implements hook_form_FORM_ID_alter().
  */
 function taxonomy_test_form_taxonomy_term_form_alter(&$form, $form_state, $form_id) {
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..9903529
--- /dev/null
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/TermRenderController.php
@@ -0,0 +1,46 @@
+<?php
+namespace Drupal\taxonomy;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityRenderController;
+
+class TermRenderController extends EntityRenderController {
+
+  public function buildContent(array &$entities = array(), $view_mode = 'full', $langcode = NULL) {
+    parent::buildContent($entities, $view_mode, $langcode);
+
+    foreach ($entities as $key => $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>',
+        );
+      }
+
+      parent::prepareView($entity, $entity_view_mode, $langcode);
+    }
+    return $entity->content;
+  }
+
+  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 prepareBuild(array $build, EntityInterface $entity, $view_mode, $langcode = NULL) {
+    $build['#attached']['css'][] = drupal_get_path('module', 'taxonomy') . '/taxonomy.css';
+    return $build;
+  }
+}
diff --git a/core/modules/taxonomy/taxonomy.module b/core/modules/taxonomy/taxonomy.module
index c28cc7b..5b5cd22 100644
--- a/core/modules/taxonomy/taxonomy.module
+++ b/core/modules/taxonomy/taxonomy.module
@@ -5,6 +5,7 @@
  * Enables the organization of content into categories.
  */
 
+use Drupal\Core\Entity\EntityRenderController;
 use Drupal\node\Node;
 use Drupal\taxonomy\Term;
 use Drupal\taxonomy\Vocabulary;
@@ -130,6 +131,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 +165,12 @@ function taxonomy_entity_info() {
       'label' => 'name',
     ),
     'fieldable' => FALSE,
+    'view modes' => array(
+      'full' => array(
+        'label' => t('Taxonomy vocabulary default'),
+        'custom settings' => FALSE,
+      ),
+    ),
   );
 
   return $return;
@@ -581,7 +589,7 @@ 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
@@ -598,16 +606,7 @@ function taxonomy_term_show(Term $term) {
  *   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;
+  return entity_render_controller('taxonomy_term')->viewMultiple($terms, $view_mode, $weight, $langcode);
 }
 
 /**
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..ef3e328
--- /dev/null
+++ b/core/modules/user/lib/Drupal/user/UserRenderController.php
@@ -0,0 +1,32 @@
+<?php
+namespace Drupal\user;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityRenderController;
+
+class UserRenderController extends EntityRenderController {
+
+  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) {
+      $this->prepareView($entity, $entity->content['#view_mode'], $langcode);
+      $return[$key] = $entity->content;
+    }
+    return $return;
+  }
+
+  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 7fb6a37..93ea02f 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'),
@@ -2051,61 +2052,27 @@ 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 user_view_multiple(array($account->id() => $account), $view_mode, 0, $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 $weight
+ *   (optional) Integer representing the weight of the first account in the list.
  * @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', $weight = 0, $langcode = NULL) {
+  return entity_render_controller('user')->viewMultiple($accounts, $view_mode, $weight, $langcode);
 }
 
 /**
