8 - Caching for custom blocks doesn't work on I'm working on a project based on Drupal 8.3.x . It comes with cache already enabled which is great if we know how to customize it. I've been going through the different tutorials and I was able to implement the following cache for my custom block:

$build = [
      '#cache' =&gt; [
        'tags'=&gt;['node'], // Invalidate cache whenever a new node is created/modified/deleted
        'contexts'=&gt;['url'] // Invalidate cache per url
      ]
    ];


The problem I'm having is my website depends on cities and display different content for different cities.
I have the following scenarios:


When I visit www.example.com/city1 the block should display nodes from city 1
When I visit www.example.com/city2 the block should display nodes from city 2
When I visit www.example.com the block should display the nodes from the latest visited city


With the snippet above I'm able to make the point 1 and 2 work. however when visiting front only the content of the first visited city appears, which make sense because the Invalidation of the cache depends on the url and  is only 1 url. 

I tried this but didn't work:

global $selected_city;
'#cache' =&gt; [
    'keys'=&gt;['home_slider_'.$selected_city],
    'tags'=&gt;['node'],
    'contexts'=&gt;['url']
  ]


Where $selected_city is coming from the cookie or the request depending on separate logic. 

Is there a way to Invalidate the cache per url + global variable ?

EDIT: The answer below fixes the issue for authenticated users but doesn't fix it for anonymous users. Any help with that would be appreciated.
The issue I had is the front page always returns the first visited city. Which is normal because the cache was invalidated by url OR node and the homepage has 1 URL. So unless I create/update/delete a node the cache would not be invalidated.

The solution was to create my own cache context, i.e city and merge it with the url context. This would invalidate the cache per url per city and create a combination of the different possibilities.

Here is the code :

city/city.services.yml

cache_context.cw_city:
    class: Drupal\city\CacheContext\CityCacheContext
    arguments: []
    tags:
      - { name: cache.context }


city/src/CacheContext/CityCacheContext.php

&lt;?php   
namespace Drupal\city\CacheContext;

use Drupal\Core\Cache\Context\CacheContextInterface;
use Drupal\Core\Cache\CacheableMetadata;

class CityCacheContext implements CacheContextInterface {

  /**
   * {@inheritdoc}
   */
  public static function getLabel() {

    return t('some label');
  }
  /**
   * {@inheritdoc}
   */
  public function getContext() {
    // The logic goes here
  }
  /**
   * {@inheritdoc}
   */
  public function getCacheableMetadata() {
    return new CacheableMetadata();
  }
}


And on the block I merged the new context with the parent contexts like this :

/**
   * {@inheritdoc}
   */
  public function getCacheContexts() {
    return Cache::mergeContexts(
      parent::getCacheContexts(),
      ['city']
    );
  }


For more information please check this post