Skip To Content

Get driving directions

In this topic

ArcGIS Runtime API for .NET provides a RouteTask abstract class, which is a base class for the OnlineRouteTask and LocalRouteTask classes. These classes allow you to perform routing functionality, including detailed directions between locations, leveraging either online or local data sources.

This tutorial introduces you to using the classes available for working with routes in your app. Your completed app will allow users to locate "from" and "to" address locations, determine the best route between them, and display the route on the map along with turn-by-turn directions.


This tutorial requires a supported version of Microsoft Visual Studio and ArcGIS Runtime SDK for .NET. Refer to the appropriate topics in the guide for information on installing the SDK and system requirements.

Familiarity with Visual Studio and a basic understanding of XAML and C# or Visual Basic .NET (VB .NET) is recommended.


You must have an ArcGIS Online organizational account in order to connect to the world route service used to complete this tutorial. Visit the Esri website for information about pricing and trial subscriptions.

Create a WPF app

You'll use Visual Studio to create a WPF app.

  1. Open a supported version of Microsoft Visual Studio.
  2. Choose File > New > Project (or click New Project on the Start page) to create a project.
  3. Click Windows Desktop > WPF Application in the New Project dialog box (you can create your project in either C# or VB .NET).

    Visual Studio 2015 organizes project types slightly differently. You'll find WPF Application projects under Windows > Classic Desktop


    ArcGIS Runtime SDK for .NET provides a project template for creating your mapping app, called ArcGIS Runtime 10.2.7 for .NET App. Creating your project from the template will add the appropriate references and a page with a map view containing a single base layer. In this tutorial, you'll build your app from a blank template.

  4. Choose a folder location for your new project and name it DrivingDirections.

    Visual Studio New Project dialog box

  5. Click OK to create the project.

    Your project opens in Visual Studio and contains a single WPF window called MainWindow.xaml.

  6. Next, you'll add a reference to the ArcGIS Runtime SDK for .NET API assembly.
  7. Right-click the References node under the DrivingDirections project listing in the Visual Studio Solution Explorer window, and click Add Reference in the context menu.
  8. Check the listing for the Esri.ArcGISRuntime assembly under Assemblies > Extensions.

    Visual Studio Reference Manager dialog box

  9. Click OK to add the reference to ArcGIS Runtime for .NET.

    Visual Studio project references

  10. The Esri.ArcGISRuntime library contains the Map control and all core API components and classes you'll need.

Create a map with a streets basemap layer

Now that you've created a simple project and referenced ArcGIS Runtime SDK, you're ready to add a map to your app. You'll create a map to display the ArcGIS Online world streets basemap layer to provide context for your route results and driving directions. After implementing the routing functionality, your results will be displayed as graphics on top of this layer.

    To use the ESRI.ArcGISRuntime library in the XAML designer, you must first make a required reference to its XML namespace.
  1. Open the MainWindow.xaml file in the Visual Studio designer.
  2. Go to the XAML view of the designer, and add the following XML namespace reference to the Window XAML element. Visual Studio offers IntelliSense to help complete the URL portion of the statement.
  3. Next, you'll add a Map control to the window and define a layer to display.
  4. Add the following XAML inside the <Grid> element to define a new MapView control on the page. The map will contain a Map displaying the World Streets Map basemap layer from ArcGIS Online and an empty graphics layer for showing route results.
    <esri:MapView x:Name="MyMapView" >
      <esri:Map x:Name="MyMap">
        <esri:ArcGISTiledMapServiceLayer ID="Streets" 
        <esri:GraphicsLayer ID="RouteResults"/>            

    In the previous XAML, a value is provided for each layer's ID property. This simplifies referring to the layer programmatically. For the MapView and Map controls, the x:Name property serves the same purpose.


    In addition to the World imagery map, ArcGIS Online provides basemaps of streets, topographic data, historical maps, and many others. Visit Living Atlas of the World to browse some of the available basemaps. If you find a basemap you'd like to use in your app, you can copy the service URI from its description page.

Create a UI for routing between two addresses

In this section, you'll create a simple UI that will allow the user to input a "from" and a "to" address and view step-by-step driving directions in a scrolling panel adjacent to the map control. You'll use a Grid element to layout your app's UI according to the following graphic:

Visual Studio designer view of the UI

  1. Add the following XAML immediately inside the Grid element that contains your map. This will divide the Grid into four sections defined by two rows and two columns.

       <RowDefinition Height="Auto"/>
       <RowDefinition Height="1*"/>
       <ColumnDefinition Width="1*"/>
       <ColumnDefinition Width="300"/>

  2. To define an element's location within your grid, you must set the Grid.Row and Grid.Column attached properties. These properties default to 0, which means an element will appear in the first column of the first row (upper-left cell) unless otherwise specified.
  3. Move your MapView control to the correct position in the grid by setting its Grid.Row and Grid.Column properties as shown in the following code example:

    <esri:MapView x:Name="MyMapView" Grid.Row="1" Grid.Column="0">


    Since the default value is 0 for Grid.Row and Grid.Column, these properties do not need to be explicitly set for controls appearing in those positions. You can set them to make your XAML more readable for those not aware of the default values.

  4. Inside the same Grid, below the XAML that defines your MapView, add the following XAML to define the address input portion of the UI. This will create a StackPanel element with TextBox controls for addresses and a Button element to initiate execution of the route.

    <StackPanel Orientation="Horizontal" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2">
       <TextBox x:Name="FromTextBox" Text="380 New York Street, Redlands CA" Width="300" Margin="5"/>
       <TextBox x:Name="ToTextBox" Text="277 N Ave Caballeros, Palm Springs CA" Width="300" Margin="5"/>
       <Button x:Name="GetDirectionsButton" Content="Get Directions" Margin="5"/>

    The Grid.RowSpan and Grid.ColumnSpan properties allow an element to occupy multiple rows or columns. The StackPanel occupies the entire top row of the grid (two columns).

  5. Add the following XAML to define the driving directions portion of the UI. This takes up the right side of the app's UI, immediately to the right of the Map. It contains TextBlock controls to display the total route time and distance, as well as a ScrollViewer and TextBlock for showing the step-by-step driving directions.

    <StackPanel Orientation="Vertical" Grid.Row="1" Grid.Column="1" Margin="10">
       <TextBlock x:Name="RouteDistance"/>
       <TextBlock x:Name="RouteTime"/>
       <ScrollViewer HorizontalScrollBarVisibility="Auto" Height="250" Margin="10">
         <TextBlock x:Name="DirectionsTextBlock"/>

Add code to geocode the input addresses

In this section, you'll begin writing code for the Get Directions button. You'll start by geocoding the addresses provided by the user for the route's from and to locations.

  1. Add a click handler to the XAML definition of the GetDirectionsButton element. When prompted by IntelliSense, create a New Event Handler called GetDirectionsButton_Click as shown in the following code example:

    <Button x:Name="GetDirectionsButton" Content="Get Directions" Margin="5" 

  2. Right-click the XAML for the GetDirectionsButton_Click event handler, and choose Go To Definition from the context menu.

    Visual Studio switches to code view and displays the GetDirectionsButton_Click function.

  3. Add the async keyword to the definition of your GetDirectionsButton_Click event handler as shown in the following code example. Because you're calling asynchronous methods using the await keyword, the containing function must be defined with async.

    private async void GetDirectionsButton_Click(object sender, RoutedEventArgs e)

  4. Before fleshing out the code for GetDirectionsButton_Click, you'll add a function that asynchronously geocodes an address that's passed in. You'll then return to the GetDirectionsButton_Click handler and call your function to get the from and to locations for the route.
  5. Add the following using statements at the top of your MainWindow.xaml.cs module for namespaces in the ArcGIS Runtime assembly that you'll need.

    using Esri.ArcGISRuntime.Geometry;
    using Esri.ArcGISRuntime.Layers;
    using Esri.ArcGISRuntime.Symbology;
    using Esri.ArcGISRuntime.Tasks.Geocoding;
    using Esri.ArcGISRuntime.Tasks.NetworkAnalyst;

  6. Add the following function to asynchronously geocode an address:

    private async System.Threading.Tasks.Task<Graphic> FindAddress(string address)
          var uri = new Uri(""); 
          var locator = new OnlineLocatorTask(uri, string.Empty);
          var findParams = new OnlineLocatorFindParameters(address);
          findParams.OutSpatialReference = new SpatialReference(4326);
          Graphic matchGraphic = null;
          var results = await locator.FindAsync(findParams, new System.Threading.CancellationToken());
          if (results.Count > 0)
               var firstMatch = results[0].Feature;
               var matchLocation = firstMatch.Geometry as MapPoint;
               matchGraphic = new Graphic();
               matchGraphic.Geometry = matchLocation;
               matchGraphic.Attributes.Add("Name", address);
          return matchGraphic;

    The previous function is defined as async, which means it must return void or a value of Task or Task<T>. The FindAddress function uses an OnlineLocatorTask to asynchronously locate the first match for the address provided. If a match is found, its Geometry is used to create a Graphic. A Name attribute is added and populated with the value of the address string. This is important for your app, because the driving directions returned from the route task will refer to the stops using a Name attribute (if it exists). The function returns the Graphic (which will be null if a match was not found).

    To learn more about geocoding, see the Add geocoding to your app tutorial.

  7. Return to the GetDirectionsButton_Click event handler. Add two calls to the FindAddress function to get the start and end locations using the address strings provided by the user as shown in the following code example:

    var from = await this.FindAddress(this.FromTextBox.Text);
    var to = await this.FindAddress(this.ToTextBox.Text);

  8. Below the code that gets the from and to locations, create a try catch block similar to the following. This code checks for the required route points and the graphics layer for displaying results. If a required object is null, the appropriate exception message is shown in the driving directions panel.

         if (from == null)
              throw new Exception("Unable to find a match for '" + this.FromTextBox.Text + "'.");
         if (to == null)
              throw new Exception("Unable to find a match for '" + this.ToTextBox.Text + "'.");
         // get the RouteResults graphics layer; add the from/to graphics
         var routeGraphics = MyMap.Layers["RouteResults"] as GraphicsLayer;
         if (routeGraphics == null)
              throw new Exception("A graphics layer named 'RouteResults' was not found in the map.");
         // code here to show address locations on the map
    catch (Exception exp)
         this.DirectionsTextBlock.Text = exp.Message;

  9. Add the following code immediately below the comment that says // code here to show address locations on the map. This adds the from address location to the results graphics layer as a green circle and the to address location as a red circle.

    var fromMapPoint = GeometryEngine.Project(from.Geometry, new SpatialReference(102100));
    var toMapPoint = GeometryEngine.Project(to.Geometry, new SpatialReference(102100));
    var fromSym = new SimpleMarkerSymbol { Style = SimpleMarkerStyle.Circle, Size = 16, Color = Colors.Green };
    var toSym = new SimpleMarkerSymbol { Style = SimpleMarkerStyle.Circle, Size = 16, Color = Colors.Red };
    var fromMapGraphic = new Graphic { Geometry = fromMapPoint, Symbol = fromSym };
    var toMapGraphic = new Graphic { Geometry = toMapPoint, Symbol = toSym };

    The previous code projects the address match points from a geographic spatial reference (required for the OnlineRouteTask) to Web Mercator (to match the map) before creating and adding the graphics.

  10. Test the functionality you've added to this point. Run the app and click the Get Directions button with the default address values in the text boxes. You'll see graphics added to the southern California region of the map. Zoom in and you'll see both graphics as shown in the following screen capture:

    From and to geocode results shown on the map

Enable access to a secured service

To instantiate an OnlineRouteTask, you need to provide a URL to a REST resource that represents a Route layer in a Network Analyst service. If the service is secured, you will also need to provide the credentials that can be used to access the service. In the following steps, you'll add code that allows your app to connect to a secured ArcGIS Online route service. More information about accessing secured services is available in the Security topic.


You must have an ArcGIS Online organizational account in order to connect to the world route service used in this tutorial. Visit the Esri website for information about pricing and trial subscriptions.

  1. Add the following using statements to the top of your code module to bring in some security-related classes.

    using Esri.ArcGISRuntime.Security;
    using Esri.ArcGISRuntime.Http;

  2. Create two new string variables to hold your ArcGIS Online organizational username and password, as shown in the following code (replace the values with your login info). Add these lines immediately inside your class definition.

    private const string ArcGISOnineUser = "MyUsername"; // update value with your username
    private const string ArcGISOninePassword = "my-P@SSw0rd"; // update value with your password

  3. Add the following function to serve as a ChallengeHandler. When a request for a secured service is encountered, this function will handle getting credentials for a user that can connect to the service.

    private async Task<Credential> CreateHardCodedCredentialAsync(CredentialRequestInfo requestInfo)
        Credential cred = null;
            var options = new GenerateTokenOptions();
            options.TokenAuthenticationType = TokenAuthenticationType.ArcGISToken;
            cred = await IdentityManager.Current.GenerateCredentialAsync(requestInfo.ServiceUri, ArcGISOnineUser, ArcGISOninePassword, options);
        catch (ArcGISWebException webEx)
            // couldn't generate a token, probably a bad login  
            MessageBox.Show("Unable to log in to ArcGIS Online: " + webEx.Message);
        return cred;
    This function will attempt to obtain credentials for any secured services with the username and password provided in your code. If the login fails, an ArcGISWebException is thrown. For more information about using ArcGIS Token authentication (including how to prompt users for their credentials), see the Use ArcGIS token authentication topic.

  4. Next, set the app to use the challenge handler function above by assigning the IdentityManager.ChallengeHandler property as shown in the following example. This code should go in your class constructor, immediately following the call to InitializeComponent.

    IdentityManager.Current.ChallengeHandler = new ChallengeHandler(CreateHardCodedCredentialAsync);

    You are now able to connect to the secured ArcGIS Online world routing service to solve routes and get driving directions.

Find the best route and display it on the map

Next, you'll use an OnlineRouteTask to solve a route using the geocoded locations. If the route is solved successfully, you'll display the result as a graphic on the map.

  1. Continue the code in your GetDirectionsButton_Click function by adding the following code to create an OnlineRouteTask. Add this code immediately after the code that adds the geocoded point graphics to the map.

    var uri = new Uri(
    var routeTask = new OnlineRouteTask(uri);

  2. The OnlineRouteTask requires an ArcGIS Network Analyst service, whose URI is passed into the constructor. The Route_World service is a secured ArcGIS Online service available to users with an organizational account.
  3. Add the following code to set route parameters using the RouteParameters class:

    var routeParams = await routeTask.GetDefaultParametersAsync();
    routeParams.OutSpatialReference = MyMapView.SpatialReference;
    routeParams.DirectionsLengthUnit = LinearUnits.Miles;
    routeParams.ReturnDirections = true;

    The RouteParameters object is used to specify properties such as the output spatial reference for route result features, the length unit to use in driving directions, whether certain restrictions should be placed on routing (for example, no u-turns), barriers to consider, and so on. It also holds a Stops collection, which you'll set using your geocoded points.

  4. Add the following code to create the required stops for the RouteParameters. Make sure you use the graphics in geographic units (from and to variables), rather than those you projected to Web Mercator for display on the map.

    var stopGraphics = new List<Graphic>();

  5. Solve the route by calling SolveAsync. If no results are returned, you'll throw an exception to display in the app. Otherwise, get a reference to the first (only) route in the results.

    var routeResult = await routeTask.SolveAsync(routeParams);
    if (routeResult == null || routeResult.Routes == null || routeResult.Routes.Count == 0)
         throw new Exception("Unable to solve the route.");
    var firstRoute = routeResult.Routes[0];

  6. Get the result RouteFeature, create a new Graphic using the route geometry and a new SimpleLineSymbol.

    var routeFeature = firstRoute.RouteFeature;
    var routeSym = new SimpleLineSymbol
          Color = Colors.Navy,
          Style = SimpleLineStyle.Dash,
          Width = 2
    var routeGraphic = new Graphic(routeFeature.Geometry, routeSym);

  7. Add the route graphic to the results layer, and set the map view to the extent of the route.

    await MyMapView.SetViewAsync(routeGraphic.Geometry.Extent);

  8. Test the functionality you've coded to this point. Run the app, and click the Get Directions button with the default address values. The map zooms to the extent of the resulting route as shown in the following screen capture:

    Map showing the route between the input address locations

Display driving directions for the route

The Route class exposes a collection of RouteDirection objects that can be used to get driving directions along all segments in the route. In this section, you'll iterate over this collection and build the driving direction text to display in the app.

  1. Continue your code in GetDirectionsButton_Click immediately below the line that zooms the map to the extent of the route. Begin by defining the following variables to store driving directions as well as information about the route:

    var directionsBuilder = new System.Text.StringBuilder();
    var totalMiles = 0.0;
    var totalMinutes = 0.0;
    var step = 1;

  2. Create the following loop to iterate over each RouteDirection in the RouteDirections collection:

    foreach (var d in firstRoute.RouteDirections)

  3. Inside the foreach loop, add the following code to append the current directions text to the directionsBuilder variable (StringBuilder):

    directionsBuilder.AppendFormat("{0} \t {1}", step.ToString(), d.Text + "\n");

    This code will append a step number, followed by a TAB, followed by the directions text, and finally a new line. The step variable is then incremented.

  4. Add the following code inside the loop to keep a running total of the segment length (miles) and time (minutes):

    totalMiles += d.GetLength(LinearUnits.Miles);
    totalMinutes += d.Time.TotalMinutes;

  5. Add the following code outside of the loop to update the app UI with the route info and driving directions:

    this.RouteDistance.Text = totalMiles.ToString("0.00") + " miles";
    this.RouteTime.Text = totalMinutes.ToString("0.00") + " minutes";
    this.DirectionsTextBlock.Text = directionsBuilder.ToString();

  6. Test the app again. Click the Get Directions button with the default address values. An image similar to the following displays:

    Map with route and driving directions for input addresses

In this tutorial, you used the OnlineRouteTask to perform routing operations using an ArcGIS for Server Network Analyst service. To perform the same operations using a network dataset on the local file system, use the LocalRouteTask class. To learn more about using geocode functionality with ArcGIS Runtime SDK for .NET, see the Add geocoding to your app tutorial.

Related topics