diff --git a/core/modules/config/config.admin.inc b/core/modules/config/config.admin.inc
deleted file mode 100644
index 2d30e49..0000000
--- a/core/modules/config/config.admin.inc
+++ /dev/null
@@ -1,151 +0,0 @@
-<?php
-
-/**
- * @file
- * Admin page callbacks for the config module.
- */
-
-use Drupal\Core\Ajax\AjaxResponse;
-use Drupal\Core\Ajax\OpenModalDialogCommand;
-use Drupal\Core\Config\ConfigException;
-use Drupal\Core\Config\ConfigImporter;
-use Drupal\Core\Config\StorageComparer;
-use Drupal\Core\Config\StorageInterface;
-
-/**
- * Helper function to construct the storage changes in a configuration synchronization form.
- *
- * @param array $form
- *   The form structure to add to. Passed by reference.
- * @param array $form_state
- *   The current state of the form. Passed by reference.
- * @param \Drupal\Core\Config\StorageInterface $source_storage
- *   The source storage to retrieve differences from.
- *
- * @return array
- *   The form with the configuration storage changes.
- */
-function config_admin_sync_form(array &$form, array &$form_state, StorageInterface $source_storage) {
-  $source_list = $source_storage->listAll();
-  $config_comparer = new StorageComparer(Drupal::service('config.storage.staging'), Drupal::service('config.storage'));
-  if (empty($source_list) || !$config_comparer->createChangelist()->hasChanges()) {
-    $form['no_changes'] = array(
-      '#markup' => t('There are no configuration changes.'),
-    );
-    $form['actions']['#access'] = FALSE;
-    return $form;
-  }
-  else {
-    // Store the comparer for use in the submit.
-    $form_state['storage_comparer'] = $config_comparer;
-  }
-
-  // Add the AJAX library to the form for dialog support.
-  $form['#attached']['library'][] = array('system', 'drupal.ajax');
-
-  foreach ($config_comparer->getChangelist() as $config_change_type => $config_files) {
-    if (empty($config_files)) {
-      continue;
-    }
-
-    // @todo A table caption would be more appropriate, but does not have the
-    //   visual importance of a heading.
-    $form[$config_change_type]['heading'] = array(
-      '#type' => 'html_tag',
-      '#tag' => 'h3',
-    );
-    switch ($config_change_type) {
-      case 'create':
-        $form[$config_change_type]['heading']['#value'] = format_plural(count($config_files), '@count new', '@count new');
-        break;
-
-      case 'update':
-        $form[$config_change_type]['heading']['#value'] = format_plural(count($config_files), '@count changed', '@count changed');
-        break;
-
-      case 'delete':
-        $form[$config_change_type]['heading']['#value'] = format_plural(count($config_files), '@count removed', '@count removed');
-        break;
-    }
-    $form[$config_change_type]['list'] = array(
-      '#theme' => 'table',
-      '#header' => array('Name', 'Operations'),
-    );
-
-    foreach ($config_files as $config_file) {
-      $links['view_diff'] = array(
-        'title' => t('View differences'),
-        'href' => 'admin/config/development/sync/diff/' . $config_file,
-        'attributes' => array(
-          'class' => array('use-ajax'),
-          'data-accepts' => 'application/vnd.drupal-modal',
-          'data-dialog-options' => json_encode(array(
-            'width' => 700
-          )),
-        ),
-      );
-      $form[$config_change_type]['list']['#rows'][] = array(
-        'name' => $config_file,
-        'operations' => array(
-          'data' => array(
-            '#type' => 'operations',
-            '#links' => $links,
-          ),
-        ),
-      );
-    }
-  }
-}
-
-/**
- * Form constructor for configuration import form.
- *
- * @see config_admin_import_form_submit()
- * @see config_import()
- */
-function config_admin_import_form($form, &$form_state) {
-  // Retrieve a list of differences between last known state and active store.
-  $source_storage = Drupal::service('config.storage.staging');
-  $target_storage = Drupal::service('config.storage');
-
-  $form['actions'] = array('#type' => 'actions');
-  $form['actions']['submit'] = array(
-    '#type' => 'submit',
-    '#value' => t('Import all'),
-  );
-
-  config_admin_sync_form($form, $form_state, $source_storage, $target_storage);
-
-  return $form;
-}
-
-/**
- * Form submission handler for config_admin_import_form().
- */
-function config_admin_import_form_submit($form, &$form_state) {
-  $config_importer = new ConfigImporter(
-    $form_state['storage_comparer'],
-    Drupal::service('event_dispatcher'),
-    Drupal::service('config.factory'),
-    Drupal::entityManager(),
-    Drupal::lock()
-  );
-  if ($config_importer->alreadyImporting()) {
-    drupal_set_message(t('Another request may be synchronizing configuration already.'));
-  }
-  else{
-    try {
-      $config_importer->import();
-      drupal_flush_all_caches();
-      drupal_set_message(t('The configuration was imported successfully.'));
-    }
-    catch (ConfigException $e) {
-      // Return a negative result for UI purposes. We do not differentiate between
-      // an actual synchronization error and a failed lock, because concurrent
-      // synchronizations are an edge-case happening only when multiple developers
-      // or site builders attempt to do it without coordinating.
-      watchdog_exception('config_import', $e);
-      drupal_set_message(t('The import failed due to an error. Any errors have been logged.'), 'error');
-    }
-  }
-}
diff --git a/core/modules/config/config.module b/core/modules/config/config.module
index 3e24189..e972b23 100644
--- a/core/modules/config/config.module
+++ b/core/modules/config/config.module
@@ -64,10 +64,7 @@ function config_menu() {
   $items['admin/config/development/sync'] = array(
     'title' => 'Synchronize configuration',
     'description' => 'Synchronize configuration changes.',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('config_admin_import_form'),
-    'access arguments' => array('synchronize configuration'),
-    'file' => 'config.admin.inc',
+    'route_name' => 'config_sync',
   );
   $items['admin/config/development/export'] = array(
     'title' => 'Configuration export',
@@ -81,7 +78,7 @@ function config_menu() {
   );
   $items['admin/config/development/sync/diff/%'] = array(
     'title' => 'Configuration file diff',
-    'description' => 'Diff between active and staged configuraiton.',
+    'description' => 'Diff between active and staged configuration.',
     'route_name' => 'config_diff',
   );
   $items['admin/config/development/sync/import'] = array(
diff --git a/core/modules/config/config.routing.yml b/core/modules/config/config.routing.yml
index 53c860c..d2749bf 100644
--- a/core/modules/config/config.routing.yml
+++ b/core/modules/config/config.routing.yml
@@ -22,3 +22,9 @@ config_import:
     _form: '\Drupal\config\Form\ConfigImportForm'
   requirements:
     _permission: 'import configuration'
+config_sync:
+  pattern: 'admin/config/development/sync'
+  defaults:
+    _form: '\Drupal\config\Form\ConfigSync'
+  requirements:
+    _permission: 'synchronize configuration'
diff --git a/core/modules/config/lib/Drupal/config/Form/ConfigSync.php b/core/modules/config/lib/Drupal/config/Form/ConfigSync.php
new file mode 100644
index 0000000..a6735ff
--- /dev/null
+++ b/core/modules/config/lib/Drupal/config/Form/ConfigSync.php
@@ -0,0 +1,218 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\config\Form\ConfigSync.
+ */
+
+namespace Drupal\config\Form;
+
+use Drupal\Core\ControllerInterface;
+use Drupal\Core\Database\Connection;
+use Drupal\Core\Form\FormInterface;
+use Drupal\Core\Config\StorageInterface;
+use Drupal\Core\Ajax\AjaxResponse;
+use Drupal\Core\Ajax\OpenModalDialogCommand;
+use Drupal\Core\Lock\LockBackendInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Construct the storage changes in a configuration synchronization form.
+ */
+class ConfigSync implements ControllerInterface, FormInterface {
+
+  /**
+   * The database connection object.
+   *
+   * @var \Drupal\Core\Database\Connection
+   */
+  protected $database;
+
+  /**
+   * The database lock object.
+   *
+   * @var \Drupal\Core\Lock\LockBackendInterface
+   */
+  protected $lock;
+
+  /**
+   * The source configuration object.
+   *
+   * @var \Drupal\Core\Config\StorageInterface
+   */
+  protected $sourceStorage;
+
+  /**
+   * The target configuration object.
+   *
+   * @var \Drupal\Core\Config\StorageInterface
+   */
+  protected $targetStorage;
+
+  /**
+   * Constructs a database object.
+   *
+   * @param \Drupal\Core\Database\Connection; $database
+   *   The database object.
+   * @param \Drupal\Core\Config\StorageInterface $sourceStorage
+   *   The source storage object.
+   * @param \Drupal\Core\Config\StorageInterface $targetStorage
+   *   The target storage manager.
+   * @param \Drupal\Core\Lock\LockBackendInterface $lock
+   *   The lock object.
+   */
+  public function __construct(Connection $database, StorageInterface $sourceStorage, StorageInterface $targetStorage, LockBackendInterface $lock) {
+    $this->database = $database;
+    $this->sourceStorage = $sourceStorage;
+    $this->targetStorage = $targetStorage;
+    $this->lock = $lock;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('database'),
+      $container->get('config.storage.staging'),
+      $container->get('config.storage'),
+      $container->get('lock')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormID() {
+    return 'config_admin_import_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, array &$form_state) {
+    $form['actions'] = array('#type' => 'actions');
+    $form['actions']['submit'] = array(
+      '#type' => 'submit',
+      '#value' => t('Import all'),
+    );
+
+    $this->config_admin_sync_form($form, $form_state);
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, array &$form_state) {
+    if (!$this->lock->lockMayBeAvailable(CONFIG_IMPORT_LOCK)) {
+      drupal_set_message(t('Another request may be synchronizing configuration already.'));
+    }
+    else if (config_import()) {
+      // Once a sync completes, we empty the staging directory. This prevents
+      // changes from being accidentally overwritten by stray files getting
+      // imported later.
+      foreach ($this->sourceStorage->listAll() as $name) {
+        $this->sourceStorage->delete($name);
+      }
+
+      drupal_flush_all_caches();
+
+      drupal_set_message(t('The configuration was imported successfully.'));
+    }
+    else {
+      drupal_set_message(t('The import failed due to an error. Any errors have been logged.'), 'error');
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, array &$form_state) {
+  }
+
+  /**
+   * Helper function to construct the storage changes in a configuration synchronization form.
+   *
+   * @param array $form
+   *   The form structure to add to. Passed by reference.
+   * @param array $form_state
+   *   The current state of the form. Passed by reference.
+   */
+  private function config_admin_sync_form(array &$form, array &$form_state) {
+    $source_list = $this->sourceStorage->listAll();
+    if (empty($source_list)) {
+      $form['no_changes'] = array(
+        '#markup' => t('There is no configuration to import.'),
+      );
+      $form['actions']['#access'] = FALSE;
+      return $form;
+    }
+
+    $config_changes = config_sync_get_changes($this->sourceStorage, $this->targetStorage);
+    if (empty($config_changes)) {
+      $form['no_changes'] = array(
+        '#markup' => t('There are no configuration changes.'),
+      );
+      return $form;
+    }
+
+    // Add the AJAX library to the form for dialog support.
+    $form['#attached']['library'][] = array('system', 'drupal.ajax');
+
+    foreach ($config_changes as $config_change_type => $config_files) {
+      if (empty($config_files)) {
+        continue;
+      }
+
+      // @todo A table caption would be more appropriate, but does not have the
+      // visual importance of a heading.
+      $form[$config_change_type]['heading'] = array(
+        '#theme' => 'html_tag__h3',
+        '#tag' => 'h3',
+      );
+      switch ($config_change_type) {
+        case 'create':
+          $form[$config_change_type]['heading']['#value'] = format_plural(count($config_files), '@count new', '@count new');
+          break;
+
+        case 'change':
+          $form[$config_change_type]['heading']['#value'] = format_plural(count($config_files), '@count changed', '@count changed');
+          break;
+
+        case 'delete':
+          $form[$config_change_type]['heading']['#value'] = format_plural(count($config_files), '@count removed', '@count removed');
+          break;
+      }
+      $form[$config_change_type]['list'] = array(
+        '#theme' => 'table',
+        '#header' => array('Name', 'Operations'),
+      );
+
+      foreach ($config_files as $config_file) {
+        $links['view_diff'] = array(
+          'title' => t('View differences'),
+          'href' => 'admin/config/development/sync/diff/' . $config_file,
+          'attributes' => array(
+            'class' => array('use-ajax'),
+            'data-accepts' => 'application/vnd.drupal-modal',
+            'data-dialog-options' => json_encode(array(
+              'width' => 700
+            )),
+          ),
+        );
+        $form[$config_change_type]['list']['#rows'][] = array(
+          'name' => $config_file,
+          'operations' => array(
+            'data' => array(
+              '#type' => 'operations',
+              '#links' => $links,
+            ),
+          ),
+        );
+      }
+    }
+  }
+
+}
