Integrated Windows Authentication

View inUWPWPFWinUIiOSView on GitHub

Connect to an IWA secured Portal and search for maps.

Image of integrated windows authentication

Use case

Your organization might use Integrated Windows Authentication (IWA) to secure ArcGIS Enterprise. This can be useful because the same credentials used to log into your work computer and network can be used to authenticate with ArcGIS. IWA is built into Microsoft Internet Information Server (IIS) and works well for intranet applications but isn't always practical for internet apps.

How to use the sample

  1. Enter the URL to your IWA-secured portal.
  2. Tap the button to search for web maps stored on the portal.
  3. You will be prompted for a user name, password, and domain (some platforms will use the current Windows login).
  4. If you authenticate successfully, portal item results will display in the list.
  5. Select a web map item to display it in the map view.

How it works

  1. The AuthenticationManager object is configured with a challenge handler that will prompt for a Windows login (username, password, and domain) if a secure resource is encountered.
  2. When a search for portal items is performed against an IWA-secured portal, the challenge handler creates an ArcGISNetworkCredential object from the information entered by the user.
  3. If the user authenticates, the search returns a list of web maps (ArcGISPortalItem) and the user can select one to display as a Map.
  4. On some platforms, the current Windows account is used by default and a login prompt will not be shown if it can authenticate successfully.

Relevant API

  • ArcGISNetworkCredential
  • ArcGISPortal
  • AuthenticationManager

About the data

This sample searches for web map portal items on a secure portal. To successfully run the sample, you need:

  • Access to a portal secured with Integrated Windows Authentication that contains one or more web map items.
  • A login that grants you access to the portal.

Additional information

IWA, which is built into Microsoft Internet Information Server (IIS), works well for intranet applications but isn't always practical for internet apps.

More information about IWA and its use with ArcGIS can be found at the following links:

Tags

authentication, Portal, security, Windows

Sample Code

IntegratedWindowsAuth.cs
Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
// Copyright 2018 Esri.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
// You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
// language governing permissions and limitations under the License.

using ArcGISRuntime;
using CoreGraphics;
using Esri.ArcGISRuntime.Mapping;
using Esri.ArcGISRuntime.Portal;
using Esri.ArcGISRuntime.Security;
using Esri.ArcGISRuntime.UI.Controls;
using Foundation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UIKit;

namespace ArcGISRuntimeXamarin.Samples.IntegratedWindowsAuth
{
    [ArcGISRuntime.Samples.Shared.Attributes.Sample(
        name: "Integrated Windows Authentication",
        category: "Security",
        description: "Connect to an IWA secured Portal and search for maps.",
        instructions: "1. Enter the URL to your IWA-secured portal.",
        tags: new[] { "Portal", "Windows", "authentication", "security" })]
    [Register("IntegratedWindowsAuth")]
    public class IntegratedWindowsAuth : UIViewController
    {
        // A TaskCompletionSource to store the result of a login task.
        private TaskCompletionSource<Credential> _loginTaskCompletionSrc;

        // The map view to display a map in the app.
        private MapView _myMapView;

        // A table view to show search results (web map portal items).
        private UITableView _webMapTableView = new UITableView();

        // UI controls needed for user input.
        private readonly UIToolbar _toolbar = new UIToolbar();
        private UITextField _securePortalUrlEntry;
        private UIButton _searchSecurePortalButton;
        private UIButton _searchPublicPortalButton;
        private UILabel _messagesLabel;

        // An overlay containing login controls to display over the map view.
        private LoginOverlay _loginUI;

        // The ArcGIS Online URL for searching public web maps.
        private const string PublicPortalUrl = "https://www.arcgis.com";

        public IntegratedWindowsAuth()
        {
            Title = "Integrated Windows Authentication";
        }

        public override void ViewDidLoad()
        {
            base.ViewDidLoad();

            // Call a function to create the user interface.
            CreateLayout();

            // Call a function to initialize the app.
            Initialize();
        }

        public override void ViewDidLayoutSubviews()
        {
            try
            {
                // Margins and control heights for calculating positions in the UI.
                nfloat topMargin = NavigationController.NavigationBar.Frame.Height + UIApplication.SharedApplication.StatusBarFrame.Height;
                nfloat margin = 5;
                nfloat controlHeight = 30;

                // Position the controls.
                _myMapView.Frame = new CGRect(0, 0, View.Bounds.Width, View.Bounds.Height);
                _toolbar.Frame = new CGRect(0, topMargin, View.Bounds.Width, controlHeight * 6 + margin * 5);
                _securePortalUrlEntry.Frame = new CGRect(margin, topMargin + margin, View.Bounds.Width - 2 * margin, controlHeight);
                _searchPublicPortalButton.Frame = new CGRect(margin, topMargin + controlHeight + margin, View.Bounds.Width / 2 - 2 * margin, controlHeight);
                _searchSecurePortalButton.Frame = new CGRect(View.Bounds.Width / 2 + margin, topMargin + controlHeight + margin, View.Bounds.Width / 2 - margin, controlHeight);
                _webMapTableView.Frame = new CGRect(margin, topMargin + 2 * controlHeight + 2 * margin, View.Bounds.Width - 2 * margin, controlHeight * 3);
                _messagesLabel.Frame = new CGRect(margin, topMargin + 5 * controlHeight + 4 * margin, View.Bounds.Width - 2 * margin, controlHeight);
                _myMapView.ViewInsets = new UIEdgeInsets(_toolbar.Frame.Bottom, 0, 0, 0);

                base.ViewDidLayoutSubviews();
            }
            catch (NullReferenceException)
            {
                // Needed to prevent crash when NavigationController is null. This happens sometimes when switching between samples.
            }
        }

        private void CreateLayout()
        {
            // Setup the visual frame for the MapView.
            var mapViewRect = new CGRect(0, 90, View.Bounds.Width, View.Bounds.Height - 90);

            // Create the map view (map will be added in Initialize).
            _myMapView = new MapView
            {
                Frame = mapViewRect
            };

            // Text entry for the secure portal URL.
            _securePortalUrlEntry = new UITextField
            {
                Placeholder = "Enter IWA-secured portal URL",
                BorderStyle = UITextBorderStyle.RoundedRect,
                BackgroundColor = ApplicationTheme.BackgroundColor,
                AutocapitalizationType = UITextAutocapitalizationType.None,
                SpellCheckingType = UITextSpellCheckingType.No,
                AutocorrectionType = UITextAutocorrectionType.No
            };

            // Allow the search bar to dismiss the keyboard.
            _securePortalUrlEntry.ShouldReturn += sender =>
            {
                sender.ResignFirstResponder();
                return true;
            };

            // Button for searching web maps on the secured portal.
            _searchSecurePortalButton = new UIButton();
            _searchSecurePortalButton.SetTitle("Search secure", UIControlState.Normal);
            _searchSecurePortalButton.SetTitleColor(View.TintColor, UIControlState.Normal);
            _searchSecurePortalButton.TouchUpInside += SearchSecurePortalButton_Click;

            // Button for searching the public portal.
            _searchPublicPortalButton = new UIButton();
            _searchPublicPortalButton.SetTitle("Search ArcGIS online", UIControlState.Normal);
            _searchPublicPortalButton.SetTitleColor(View.TintColor, UIControlState.Normal);
            _searchPublicPortalButton.TouchUpInside += SearchPublicPortalButton_Click;

            // Table view to show web map item results.
            _webMapTableView = new UITableView
            {
                RowHeight = 20
            };

            // A label to display errors and other messages.
            _messagesLabel = new UILabel
            {
                Text = "Search portals for web maps",
                TextAlignment = UITextAlignment.Center
            };
            _messagesLabel.Font = _messagesLabel.Font.WithSize(10.0f);

            // Add the map view and toolbar controls to the page.
            View.AddSubviews(_myMapView, _toolbar, _securePortalUrlEntry, _webMapTableView, _searchSecurePortalButton, _searchPublicPortalButton, _messagesLabel);
            View.BackgroundColor = ApplicationTheme.BackgroundColor;
        }

        private void Initialize()
        {
            // Define a challenge handler method for the AuthenticationManager.
            // This method handles getting credentials when a secured resource is encountered.
            AuthenticationManager.Current.ChallengeHandler = new ChallengeHandler(CreateCredentialAsync);

            // Show a default map (light gray canvas).
            Map defaultMap = new Map(BasemapStyle.ArcGISLightGray)
            {
                InitialViewpoint = new Viewpoint(0.0, 0.0, 200000000)
            };
            _myMapView.Map = defaultMap;
        }

        // AuthenticationManager.ChallengeHandler function that prompts the user for login information to create a credential.
        private async Task<Credential> CreateCredentialAsync(CredentialRequestInfo info)
        {
            // Ignore token or certificate authentication challenges (would require more code/UI).
            if (info.AuthenticationType != AuthenticationType.NetworkCredential)
            {
                Console.WriteLine("Skipping authentication for " + info.ServiceUri.Host);
                return null;
            }

            // Return if authentication is already in progress.
            if (_loginTaskCompletionSrc != null && !_loginTaskCompletionSrc.Task.IsCanceled)
            {
                return null;
            }

            // Create a new TaskCompletionSource for the login operation.
            // (passing the CredentialRequestInfo object to the constructor will make it available from its AsyncState property)
            _loginTaskCompletionSrc = new TaskCompletionSource<Credential>(info);

            // Show the login controls on the UI thread, OnLoginInfoEntered event will return the values entered (username, password, and domain)
            InvokeOnMainThread(ShowLoginUI);

            // Return the login task, the result will be ready when completed (user provides login info and clicks the "Login" button).
            return await _loginTaskCompletionSrc.Task;
        }

        private async void SearchSecurePortalButton_Click(object sender, EventArgs e)
        {
            // Clear any previous results.
            _webMapTableView.Source = null;
            _webMapTableView.ReloadData();

            try
            {
                // Get the value entered for the secure portal URL.
                string securedPortalUrl = _securePortalUrlEntry.Text.Trim();

                // Make sure a portal URL has been entered in the text box.
                if (string.IsNullOrEmpty(securedPortalUrl))
                {
                    _messagesLabel.Text = "Please enter the URL of the secured portal.";
                    return;
                }

                // Create an instance of the IWA-secured portal, the user may be challenged for access.
                var iwaSecuredPortal = await ArcGISPortal.CreateAsync(new Uri(securedPortalUrl), true);

                // Call a function to search the portal.
                SearchPortal(iwaSecuredPortal);

                // Report the username for this connection.
                if (iwaSecuredPortal.User != null)
                {
                    _messagesLabel.Text = "Connected as: " + iwaSecuredPortal.User.UserName;
                }
                else
                {
                    // This shouldn't happen (if the portal is truly secured)!
                    _messagesLabel.Text = "Connected anonymously";
                }
            }
            catch (TaskCanceledException)
            {
                // Report canceled login.
                _messagesLabel.Text = "Login was canceled";
            }
            catch (Exception ex)
            {
                // Report errors (connecting to the secured portal, for example).
                _messagesLabel.Text = ex.Message;
            }
            finally
            {
                // Set the task completion source to null so user can attempt another login (if it failed).
                _loginTaskCompletionSrc = null;
            }
        }

        private async void SearchPublicPortalButton_Click(object sender, EventArgs e)
        {
            // Clear any previous results.
            _webMapTableView.Source = null;
            _webMapTableView.ReloadData();

            try
            {
                // Create an instance of the public portal.
                var publicPortal = await ArcGISPortal.CreateAsync(new Uri(PublicPortalUrl));

                // Call a function to search the portal.
                SearchPortal(publicPortal);
            }
            catch (Exception ex)
            {
                // Report errors, if any.
                _messagesLabel.Text = ex.Message;
            }
        }

        private async void SearchPortal(ArcGISPortal currentPortal)
        {
            // Show status message.
            _messagesLabel.Text = "Searching for web map items on the portal at " + currentPortal.Uri.AbsoluteUri;
            var messageBuilder = new StringBuilder();

            try
            {
                // Report connection info.
                messageBuilder.AppendLine("Connected to the portal on " + currentPortal.Uri.Host);

                // Report the user name used for this connection.
                if (currentPortal.User != null)
                {
                    messageBuilder.AppendLine("Connected as: " + currentPortal.User.UserName);
                }
                else
                {
                    // Note: This shouldn't happen for a secure portal!
                    messageBuilder.AppendLine("Anonymous");
                }

                // Search the portal for web maps.
                var items = await currentPortal.FindItemsAsync(new PortalQueryParameters("type:(\"web map\" NOT \"web mapping application\")"));

                // Build a list of items from the results that stores the map name as a key for the item.
                var resultItems = from r in items.Results select new KeyValuePair<string, PortalItem>(r.Title, r);

                // Add the items to a dictionary.
                List<PortalItem> webMapItems = new List<PortalItem>();
                foreach (var itm in resultItems)
                {
                    webMapItems.Add(itm.Value);
                }

                // Show the portal item titles in the list view.
                PortalItemListSource webMapTableSource = new PortalItemListSource(webMapItems);
                webMapTableSource.OnWebMapSelected += WebMapTableSource_OnWebMapSelected;
                _webMapTableView.Source = webMapTableSource;
                _webMapTableView.ReloadData();
            }
            catch (Exception ex)
            {
                // Report errors searching the portal.
                messageBuilder.AppendLine(ex.Message);
            }
            finally
            {
                // Show messages.
                _messagesLabel.Text = messageBuilder.ToString();
            }
        }

        private void WebMapTableSource_OnWebMapSelected(object sender, WebMapSelectedEventArgs e)
        {
            try
            {
                // Get the web map (portal item) that was selected.
                var webMap = e.SelectedWebMapItem;
                if (webMap != null)
                {
                    // Create a new map from the portal item and display it in the map view.
                    var map = new Map(webMap);
                    _myMapView.Map = map;
                }

                _messagesLabel.Text = "Loaded web map from item " + webMap.ItemId;
            }
            catch (Exception ex)
            {
                // Report error.
                _messagesLabel.Text = "Exception: " + ex.Message;
            }
        }

        private void ShowLoginUI()
        {
            // Create a view to show login controls over the map view.
            var ovBounds = _myMapView.Bounds;
            _loginUI = new LoginOverlay(ovBounds, 0.65f, UIColor.DarkGray);

            // Handle the login event to get the login entered by the user.
            _loginUI.OnLoginInfoEntered += LoginEntered;

            // Handle the cancel event when the user closes the dialog without entering a login.
            _loginUI.OnCanceled += LoginCanceled;

            // Add the login UI view (will display semi-transparent over the map view).
            View.Add(_loginUI);
        }

        // Handle the OnLoginEntered event from the login UI, LoginEventArgs contains the username, password, and domain that were entered
        private void LoginEntered(object sender, LoginEventArgs e)
        {
            // Make sure the task completion source has all the information needed.
            if (_loginTaskCompletionSrc == null ||
                _loginTaskCompletionSrc.Task == null ||
                _loginTaskCompletionSrc.Task.AsyncState == null)
            {
                return;
            }

            try
            {
                // Get the associated CredentialRequestInfo (will need the URI of the service being accessed).
                CredentialRequestInfo requestInfo = _loginTaskCompletionSrc.Task.AsyncState as CredentialRequestInfo;

                // Create a new network credential using the values entered by the user.
                var netCred = new System.Net.NetworkCredential(e.Username, e.Password, e.Domain);

                // Create a new ArcGIS network credential to hold the network credential and service URI.
                var arcgisCred = new ArcGISNetworkCredential(requestInfo.ServiceUri, netCred);

                // Set the task completion source result with the ArcGIS network credential.
                // AuthenticationManager is waiting for this result and will add it to its Credentials collection.
                _loginTaskCompletionSrc.TrySetResult(arcgisCred);
            }
            catch (Exception ex)
            {
                // Unable to create credential, set the exception on the task completion source.
                _loginTaskCompletionSrc.TrySetException(ex);
            }
            finally
            {
                // Get rid of the login controls.
                _loginUI.Hide();
                _loginUI = null;
            }
        }

        private void LoginCanceled(object sender, EventArgs e)
        {
            // Remove the login UI.
            _loginUI.Hide();
            _loginUI = null;

            // Cancel the task completion source task.
            _loginTaskCompletionSrc.TrySetCanceled();
        }
    }

    // View containing login controls (username, password, and domain).
    public class LoginOverlay : UIView
    {
        // Event to provide login information when the user dismisses the view.
        public event EventHandler<LoginEventArgs> OnLoginInfoEntered;

        // Event to report that the login was canceled.
        public event EventHandler OnCanceled;

        // Store the username, password, and domain controls so the values can be read.
        private UITextField _usernameTextField;
        private UITextField _passwordTextField;
        private UITextField _domainTextField;

        public LoginOverlay(CGRect frame, nfloat transparency, UIColor color) : base(frame)
        {
            // Create a semi-transparent overlay with the specified background color.
            BackgroundColor = color;
            Alpha = transparency;

            // Set size and spacing for controls.
            nfloat controlHeight = 25;
            nfloat rowSpace = 11;
            nfloat buttonSpace = 15;
            nfloat textViewWidth = Frame.Width - 60;
            nfloat buttonWidth = 60;

            // Get the total height and width of the control set (four rows of controls, three sets of space).
            nfloat totalHeight = (4 * controlHeight) + (3 * rowSpace);
            nfloat totalWidth = textViewWidth;

            // Find the center x and y of the view.
            nfloat centerX = Frame.Width / 2;
            nfloat centerY = Frame.Height / 2;

            // Find the start x and y for the control layout.
            nfloat controlX = centerX - (totalWidth / 2);
            nfloat controlY = centerY - (totalHeight / 2);

            // Username text input.
            _usernameTextField = new UITextField(new CGRect(controlX, controlY, textViewWidth, controlHeight))
            {
                Placeholder = "Username",
                AutocapitalizationType = UITextAutocapitalizationType.None,
                BackgroundColor = ApplicationTheme.BackgroundColor,
                TextColor = ApplicationTheme.ForegroundColor
            };

            // Adjust the Y position for the next control.
            controlY = controlY + controlHeight + rowSpace;

            // Password text input.
            _passwordTextField = new UITextField(new CGRect(controlX, controlY, textViewWidth, controlHeight))
            {
                SecureTextEntry = true,
                Placeholder = "Password",
                AutocapitalizationType = UITextAutocapitalizationType.None,
                BackgroundColor = ApplicationTheme.BackgroundColor,
                TextColor = ApplicationTheme.ForegroundColor
            };

            // Adjust the Y position for the next control.
            controlY = controlY + controlHeight + rowSpace;

            // Domain text input.
            _domainTextField = new UITextField(new CGRect(controlX, controlY, textViewWidth, controlHeight))
            {
                Placeholder = "Domain",
                AutocapitalizationType = UITextAutocapitalizationType.None,
                BackgroundColor = ApplicationTheme.BackgroundColor,
                TextColor = ApplicationTheme.ForegroundColor
            };

            // Adjust the Y position for the next control.
            controlY = controlY + controlHeight + rowSpace;

            // Button to submit the login information.
            UIButton loginButton = new UIButton(new CGRect(controlX, controlY, buttonWidth, controlHeight));
            loginButton.SetTitle("Login", UIControlState.Normal);
            loginButton.SetTitleColor(ApplicationTheme.BackgroundColor, UIControlState.Normal);
            loginButton.TouchUpInside += LoginButtonClick;

            // Adjust the X position for the next control.
            controlX = controlX + buttonWidth + buttonSpace;

            // Button to cancel the login.
            UIButton cancelButton = new UIButton(new CGRect(controlX, controlY, buttonWidth, controlHeight));
            cancelButton.SetTitle("Cancel", UIControlState.Normal);
            cancelButton.SetTitleColor(ApplicationTheme.BackgroundColor, UIControlState.Normal);
            cancelButton.TouchUpInside += (s, e) => { OnCanceled.Invoke(this, null); };

            // Add the controls.
            AddSubviews(_usernameTextField, _passwordTextField, _domainTextField, loginButton, cancelButton);
        }

        // Animate increasing transparency to completely hide the view, then remove it.
        public void Hide()
        {
            // Action to make the view transparent.
            Action makeTransparentAction = () => Alpha = 0;

            // Action to remove the view.
            Action removeViewAction = RemoveFromSuperview;

            // Time to complete the animation (seconds).
            const double secondsToComplete = 0.75;

            // Animate transparency to zero, then remove the view.
            Animate(secondsToComplete, makeTransparentAction, removeViewAction);
        }

        private void LoginButtonClick(object sender, EventArgs e)
        {
            // Get the values entered in the text fields.
            string username = _usernameTextField.Text.Trim();
            string password = _passwordTextField.Text.Trim();
            string domain = _domainTextField.Text.Trim();

            // Make sure the user entered all values.
            if (string.IsNullOrEmpty(username) ||
                string.IsNullOrEmpty(password) ||
                string.IsNullOrEmpty(domain))
            {
                new UIAlertView("Login", "Please enter a username, password, and domain", (IUIAlertViewDelegate)null, "OK", null).Show();
                return;
            }

            // Fire the OnLoginInfoEntered event and provide the login values.
            if (OnLoginInfoEntered != null)
            {
                // Create a new LoginEventArgs to contain the user's values.
                var loginEventArgs = new LoginEventArgs(username, password, domain);

                // Raise the event.
                OnLoginInfoEntered(sender, loginEventArgs);
            }
        }
    }

    // Custom EventArgs implementation to hold login information (username, password, and domain).
    public class LoginEventArgs : EventArgs
    {
        // Username property.
        public string Username { get; set; }

        // Password property.
        public string Password { get; set; }

        // Domain property.
        public string Domain { get; set; }

        // Store login values passed into the constructor.
        public LoginEventArgs(string username, string password, string domain)
        {
            Username = username;
            Password = password;
            Domain = domain;
        }
    }

    // Table view data source that manages a list of web map portal items.
    public class PortalItemListSource : UITableViewSource
    {
        // Event to provide the selected portal item when the user taps a row.
        public event EventHandler<WebMapSelectedEventArgs> OnWebMapSelected;

        // List of portal items to display.
        private readonly List<PortalItem> _webMapItemsList;

        // Used when re-using cells to ensure that a cell of the right type is used.
        private const string CellId = "TableCell";

        public PortalItemListSource(List<PortalItem> items)
        {
            // Set the items.
            _webMapItemsList = items;
        }

        // This method gets a table view cell for the specified index.
        public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
        {
            // Try to get a re-usable cell (this is for performance). If there are no cells, create a new one.
            UITableViewCell cell = tableView.DequeueReusableCell(CellId);
            if (cell == null)
            {
                cell = new UITableViewCell(UITableViewCellStyle.Default, CellId);
            }

            // Specify the text for the cell.
            cell.TextLabel.Text = _webMapItemsList[indexPath.Row].Title;

            // Ensure that the label fits.
            cell.TextLabel.AdjustsFontSizeToFitWidth = true;

            // Return the cell.
            return cell;
        }

        // This method allows the UITableView to know how many rows to render.
        public override nint RowsInSection(UITableView tableview, nint section)
        {
            return _webMapItemsList.Count;
        }

        // Called when a row is tapped.
        public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
        {
            // Get the portal item for the selected row.
            PortalItem webMapItem = _webMapItemsList[indexPath.Row];

            // Raise the selection event with an argument containing the selected portal item.
            var webMapSelectedArgs = new WebMapSelectedEventArgs(webMapItem);
            OnWebMapSelected(this, webMapSelectedArgs);
        }
    }

    // Custom EventArgs implementation to hold the selected web map portal item.
    public class WebMapSelectedEventArgs : EventArgs
    {
        // The selected web map portal item.
        public PortalItem SelectedWebMapItem { get; set; }

        // Take the portal item in the constructor.
        public WebMapSelectedEventArgs(PortalItem webMapItem)
        {
            SelectedWebMapItem = webMapItem;
        }
    }
}

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.