|
POST
|
Hi, I just want to check that Xcode 14 and iOS 16 are supported by 100.15. We are building a final Xamarin Forms version of our apps based on 100.15. I want to be sure of where we should stay for Xcode and iOS. Thanks - Joe @MichaelBranscomb
... View more
09-20-2022
10:04 AM
|
0
|
5
|
1249
|
|
POST
|
Trying to be clear what you are trying to do. You are having the behavior trigger the command on every location changed if I am reading correctly. I have taken a different approach and I inject the interface that exposes my LocationDataSource into the code behind and wire up there. You are already injecting your IGeoLocationService so not much different. I do have some differing ideas on how I did things. One thing is I use a control I made up to share my MapView among different Views in the application, but I think things will work the same either way. You don't show IGeoLocationService but have an idea what you are doing. I've got ILocationProvider which would seem to do the same as your IGeoLocationService public interface ILocationProvider
{
event EventHandler<ConnectionStatusChangedEventArgs> ConnectionStatusChanged;
LocationDataSource LocationDataSource { get; }
bool IsConnected { get; }
bool ShowLocation { get; set; }
string Receiver { get; }
} This implementation would then expose any type of LocationDataSource I want. I have recently switched to using the NmeaLocationDataSource but all this worked with a custom LocationDataSource prior to NmeaLocationDataSource being added to the API. Below shows different implementations that I have and can swap out (SxBlueLocationProvider was the previous custom one used before NMEA was added to API) //containerRegistry.RegisterSingleton<ILocationProvider, SxBlueLocationProvider>();
containerRegistry.RegisterSingleton<ILocationProvider, SystemLocationProvider>();
//containerRegistry.RegisterSingleton<ILocationProvider, NmeaLocationProvider>(); This part would be different because I am doing as a control, and so instead of injecting anything they are setup as BindableProperties public IEventAggregator EventAggregator
{
get => (IEventAggregator) GetValue(EventAggregatorProperty);
set => SetValue(EventAggregatorProperty, value);
}
public ILocationProvider LocationProvider
{
get => (ILocationProvider) GetValue(LocationProviderProperty);
set => SetValue(LocationProviderProperty, value);
}
public static readonly BindableProperty EventAggregatorProperty = ...
public static readonly BindableProperty LocationProviderProperty = ... You should be able to just inject into code behind like you do. I think I have the relevant pieces included if you are setup with the MapView in a view private readonly ILocationProvider _provider;
private readonly IEventAggregator _eventAggregator;
public MainMapUC(ILocationProvider provider, IEventAggregator eventAggregator)
{
_provide = provide;
_eventAggregator = eventAggregator;
InitializeComponent();
mapView.PropertyChanged += (s, e) =>
{
//LocationDisplay is not set when MapView created
if ( e.PropertyName == nameof(mapView.LocationDisplay) )
{
OnPropertyChanged(nameof(LocationProvider));
}
};
}
protected override void OnPropertyChanged(string propertyName = null)
{
base.OnPropertyChanged(propertyName);
switch (propertyName)
{
case nameof(LocationProvider):
SetupLocationDataSource();
break;
}
}
private async void SetupLocationDataSource()
{
try
{
MainThread.BeginInvokeOnMainThread(() =>
mapView.LocationDisplay.DataSource = _provider.LocationDataSource);
if ( mapView.LocationDisplay.DataSource?.Status == LocationDataSourceStatus.Stopped )
{
await mapView.LocationDisplay.DataSource?.StartAsync()!;
}
}
catch (Exception e)
{
Log?.Error(e, e.Message);
}
} In the code behind we are now tying the LocationDataSource to our map view. At this point we should be using the custom LocationDataSource Now you can setup listening for LocationChanged in any ViewModel you want. public EditControlViewModel(IEventAggregator eventAggregator, ILocationProvider locationProvider, IFeatureAttributes featureAttributes) : base(eventAggregator)
{
_locationProvider = locationProvider;
_featureAttributes = featureAttributes;
try
{
if ( _locationProvider.LocationDataSource != null )
{
if ( _locationProvider.LocationDataSource.Status == LocationDataSourceStatus.Started )
{
_locationProvider.LocationDataSource.LocationChanged += OnLocationChanged;
}
else
{
void OnLocationDataSourceStatusChanged(object s, LocationDataSourceStatus status)
{
if ( status == LocationDataSourceStatus.Started )
{
_locationProvider.LocationDataSource.LocationChanged += OnLocationChanged;
//Remove Status Changed listener
_locationProvider.LocationDataSource.StatusChanged -= OnLocationDataSourceStatusChanged;
}
}
//If LocationDataSource has not yet started listen for Status Changed
_locationProvider.LocationDataSource.StatusChanged += OnLocationDataSourceStatusChanged;
}
}
}
catch (Exception e)
{
Log?.Error(e, e.Message);
}
}
private void OnLocationChanged(object sender, Esri.ArcGISRuntime.Location.Location location)
{
_currentLocation = location;
} I did a little extra stuff in there to make sure the LocationDataSource is up and running before tying the event to it. Also my personal feelin is to try and avoid sending off Prism events from LocationChanged handler because that event is fired so frequently.
... View more
09-16-2022
09:14 AM
|
1
|
0
|
1637
|
|
POST
|
It sounds like what you want to do is not really the pre-planned workflow. The offline map workflow is designed to have an exact WebMap in an offline mode There are other ways to take data offline. For what you describe, seems you are looking for a way to take data offline in a more generic way and create the maps from that offline data Generate geodatabase replica from feature service. I think this would be fine with UN stuff, but it has been a while since I played around with doing UN tracing offline, so not 100% sure That said, if you really wanted to get creative you could load layers from one offline web maps and in theory add the to another, but that seems like a complex approach
... View more
09-09-2022
10:51 AM
|
0
|
1
|
1133
|
|
IDEA
|
The best practice is to use AuthenticationManager.Current.ChallengeHandler to generate credentials and update AuthenticationManager credential store. The previous approach of calling ArcGISPortal.CreateAsync and passing a credential has been marked as obsolete. This allows one to create a ArcGISPortal object which is not initialized. I am unclear what value this object would serve and why the ArcGISPortal.CreateAsync does not fire the ChallengeHandler . Currently in order to get a initialized ArcGISPortal there would be a couple approaches //Launches OAuth dialog, but does not fire ChallengeHandler
var credential = await AuthenticationManager.Current.GenerateCredentialAsync(new Uri(ServerUrl));
AuthenticationManager.Current.AddCredential(credential);
//ArcGIS Portal object that is hydrated
_portal = await ArcGISPortal.CreateAsync(new Uri(ServerUrl)); The other approach would be //Create ArcGISPortal without a User object
_portal = await ArcGISPortal.CreateAsync(new Uri(ServerUrl));
// this will trigger the ChallengeHandler which launches OAuth dialog
_= await PortalItem.CreateAsync(_portal, "eb8be7257df742baaf320158b16f676b");
//run CreateAsync another time to get the hydrated ArcGISPortal object
_portal = await ArcGISPortal.CreateAsync(new Uri(ServerUrl)); The first approach seems better and it is not hard to hide the details in an your own OpenPortal method. It seems to me to defeat the point of the AuthenticationManager. and ChallengeHandler pattern. To encapsulate the GenerateCredential code in the single ChallengeHandler delegate, the latter approach would be required, and one calls CreateAsync twice. Or one could use the former approach, but then are not even using the ChallengeHandler. Seems that _portal = await ArcGISPortal.CreateAsync(new Uri(ServerUrl)); triggering the ChallengeHandler would be better. As the ArcGISPortal object is somewhat useless until authentication has occurred
... View more
08-12-2022
09:06 AM
|
1
|
2
|
1804
|
|
POST
|
In the end is pretty straight forward. The simplest way is to simply get a token directly from a call to the /authorize method. This was done by navigating to the authorize Url in the OnInitialized method of the Index.razor page protected override void OnInitialized()
{
try
{
string authorizeUrl = "https://www.arcgis.com/sharing/rest/oauth2/authorize";
string clientId = Configuration["Authorization:clientId"]!.ToString();
string redirectUrl = Configuration["Authorization:redirectUrl"]!.ToString();
string responseType = "token";
UriBuilder builder = new UriBuilder(authorizeUrl)
{
Query = $"client_id={clientId}&redirect_uri={redirectUrl}&response_type={responseType}"
};
string oAuthUrl = builder.ToString();
NavigationManager.NavigateTo(oAuthUrl);
}
catch (Exception e)
{
Console.WriteLine(e);
}
} Then in the page defined in the redirectUrl you grab the token from the return returned url protected override void OnInitialized()
{
try
{
_token = Navigation.Uri.Substring(Navigation.Uri.IndexOf("=", StringComparison.Ordinal) + 1);
}
catch (Exception e)
{
Console.WriteLine(e);
}
} An approach which I think is a bit more secure would involve requesting code from /authorize method and then getting the token in the redirect page using the /token method. In this case in the Index.razor we have protected override void OnInitialized()
{
try
{
string authorizeUrl = "https://www.arcgis.com/sharing/rest/oauth2/authorize";
string clientId = Configuration["Authorization:clientId"]!.ToString();
string redirectUrl = Configuration["Authorization:redirectUrl"]!.ToString();
string responseType = "code";
string codeChallenge = CreateChallangeCode();
string codeChallengeMethod = "plain";
UriBuilder builder = new UriBuilder(authorizeUrl)
{
Query = $"client_id={clientId}&redirect_uri={redirectUrl}&response_type={responseType}&code_challenge={codeChallenge}&code_challenge_method={codeChallengeMethod}"
};
string oAuthUrl = builder.ToString();
NavigationManager.NavigateTo(oAuthUrl);
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
private string CreateChallangeCode()
{
using SHA256 sha256 = SHA256.Create();
byte[] data = sha256.ComputeHash(Encoding.UTF8.GetBytes("blazor"));
var builder = new StringBuilder();
// Loop through each byte of the hashed data
// and format each one as a hexadecimal string.
foreach (var b in data)
{
builder.Append(b.ToString("x2"));
}
return builder.ToString();
} and in the redirect page get the token from the returned code protected override async void OnInitialized()
{
try
{
string code = Navigation.Uri.Substring(Navigation.Uri.IndexOf("=", StringComparison.Ordinal) + 1);
await RequestToken(code);
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
private async Task RequestToken(string code)
{
string tokenUrl = "https://www.arcgis.com/sharing/rest/oauth2/token";
string clientId = Configuration["Authorization:clientId"]!;
string redirectUrl = Configuration["Authorization:redirectUrl"]!;
string codeChallenge = CreateCodeChallenge();
var dictionary = new Dictionary<string, string>
{
{ "client_id", clientId },
{ "grant_type", "authorization_code" },
{ "code", code! },
{ "code_verifier", codeChallenge },
{ "redirect_uri", redirectUrl }
};
FormUrlEncodedContent content = new FormUrlEncodedContent(dictionary);
using HttpClient client = new HttpClient();
var response = await client.PostAsync(tokenUrl, content);
var json = await response.Content.ReadAsStringAsync();
JsonNode node = JsonNode.Parse(json)!;
_token = node!["access_token"]!.ToString();
}
... View more
08-11-2022
12:20 PM
|
1
|
0
|
1858
|
|
POST
|
After a bit of deliberation I was finally able to get the OAuth credential generation working in a net6 wpf application. See Challenge Handler not called in WPF I took a bit of a different approach than discussed there, shown below // Calls await ArcGISPortal.CreateAsync(new Uri(ServerUrl));
_portal = await _authenticationHelper.OpenPortalAsync();
//Done to fire the ChallengeHandler before loading anything
_= await PortalItem.CreateAsync(_portal, "itemid");
Map = new Map(new Basemap(BasemapStyle.ArcGISStreetsRelief))
{
InitialViewpoint = ...
};
_projectsFeatureLayer = new FeatureLayer(new Uri(ProjectsShapeUrl));
_photosFeatureLayer = new FeatureLayer(new Uri(PhotoUrl));
Map.OperationalLayers.Add(_photosFeatureLayer);
Map.OperationalLayers.Add(_projectsFeatureLayer);
await Map.LoadAsync(); This will fire the OAuth dialog and the user logs in then everything loads and no APIKey is needed. The issue is that even though I am logged in and can load layers without issue the _portal object does not seem to hydrate completely. There is no PortalUser on the object, and if I try to get a license it fails saying the user cannot According to help At this point the user is authenticated because the maps have loaded, load status is Loaded. Also if I try to get a license it fails Which is not true, the user does have access to a Standard Runtime license. There is nothing I could find, beyond loading a map that would be required for initialization, as described in help. Am I missing something? Thanks @MatveiStefarov
... View more
08-11-2022
08:59 AM
|
0
|
0
|
499
|
|
POST
|
That did work. Removed the setting of the APIKey at the initialization stage and then when trying to add the feature layer it works. Right now have just ran a quick test but this should move me forward Thanks - Joe
... View more
08-04-2022
02:39 PM
|
0
|
0
|
2243
|
|
POST
|
I'll give a try when I get the chance. I will say, imo, that defeats the point of being able to use an API key for public layers. An application that has a workflow of the user only needing a basemap without private operational layers is certainly realistic, and it would be nice if in these cases they didn't have to log on. So the log-on would only pop up when they need to add an operational layer. I'll let you know after I test Thanks - Joe
... View more
08-04-2022
07:20 AM
|
0
|
1
|
2251
|
|
POST
|
My issue was that I was putting S256 not plain when I was generating the authorization code. I guess I misunderstand what one needs to do for using the S256 setting. My initial challenge was created with an SH256 encryption (as yours was), but I guess am supposed to encrypt something being sent back when defining as S256 Thanks - Joe
... View more
08-02-2022
12:24 PM
|
1
|
0
|
10963
|
|
POST
|
The desktop apps do have a API key set, because this was just going to be online. The Xamarin is setup for offline. Also, I am trying to do this using OAth not just user/password. However, I have tried the approach you mention. I will get the OAuth to popup, it seems I have a credential in there when looking at AuthenticationManager in debugger, but I get a token error
... View more
08-02-2022
10:08 AM
|
0
|
3
|
2261
|
|
POST
|
I'll check again, I had not noticed that. The 12345 was just trying to simplify it to get working. My original attempts used an SHA256 string created by my app. I'll look at your example and see if I see anything different from what I did
... View more
08-02-2022
10:04 AM
|
1
|
1
|
10968
|
|
POST
|
This rest call has nothing to do with taking something offline. If you want offline needs to be via a Web Map. That is simply a rest endpoint for the service.
... View more
08-02-2022
10:00 AM
|
0
|
1
|
1137
|
|
POST
|
You cannot just take a base map offline. You would need to do is create a WebMap and then take the webmap offline. The is considerable information on the dev site of the different ways to do this Offline maps, scenes, and data However, this is very limited as to the size of the area you can take offline ands still have good resolution. (generally significant)
... View more
08-01-2022
09:47 AM
|
0
|
3
|
1149
|
|
POST
|
Hi, I am trying to get a token using OAuth2 from a web app. I am able to do it using the /authorize endpoint if using response_type=token. However, this returns the token in plan text in the redirect url which I find a bit low security. I guess it is in the user's own browser, so perhaps not that big a deal. I thought I would try to use the approach of getting an authorization code and then use the /token endpoint to get the token. However, I am unable to retrieve this successfully. Initially I send the /authorize request string authorizeUrl = "https://www.arcgis.com/sharing/rest/oauth2/authorize";
string clientId = "my-client-id";
string redirectUrl = "https://localhost:7109/counter";
string responseType = "code";
string codeChallenge = "12345";
string codeChallengeMethod = "plain";
Console.WriteLine($"Index Challenge: {codeChallenge}");
$"{authorizeUrl}?client_id={clientId}&redirect_uri={redirectUrl}&response_type={responseType}&code_challenge={codeChallenge}&code_challenge_method={codeChallengeMethod}";
UriBuilder builder = new UriBuilder(authorizeUrl)
{
Query = $"client_id={clientId}&redirect_uri={redirectUrl}&response_type={responseType}&code_challenge={codeChallenge}&code_challenge_method={codeChallengeMethod}"
};
string oAuthUrl = builder.ToString();
NavigationManager.NavigateTo(oAuthUrl); This works perfect and takes me to the login page and when I login I am then redirected correctly to redirect page and the code is attached. So then a post request is made to get the /token endpoint //this gets the returned code, I have validated that it matches when is in the redirect Url
string code = Navigation.Uri.Substring(Navigation.Uri.IndexOf("=", StringComparison.Ordinal) + 1);
string tokenUrl = "https://www.arcgis.com/sharing/rest/oauth2/token";
string clientId = "my-client-id";
string redirectUrl = "https://localhost:7109/counter";
//string codeChallenge = CreateSHA256Challenge();
string codeChallenge = "12345";
var dictionary = new Dictionary<string, string>
{
{"client_id", clientId},
{"grant_type", "authorization_code"},
{"code", code!},
{"code_verifier", codeChallenge},
{"redirect_uri", redirectUrl}
};
FormUrlEncodedContent content = new FormUrlEncodedContent(dictionary);
using HttpClient client = new HttpClient();
var response = await client.PostAsync(tokenUrl, content);
var json = await response.Content.ReadAsStringAsync(); However, this fails and returns {
"error": {
"code": 400,
"error": "invalid_request",
"error_description": "Invalid PKCE code_challenge_verifier",
"message": "Invalid PKCE code_challenge_verifier",
"details": []
}
} The example I am just using a very simple challenge in plain text to make this as easy as possible to validate. Is there something I am missing in the /token request? One thing I find odd is you do not need to specify if is plain or S256 in the /token request. The /token post call is within milliseconds of the initial /authorize request so nothing could have expired. Does anyone has thoughts on what I am missing? Thanks - Joe
... View more
07-29-2022
02:46 PM
|
0
|
4
|
11324
|
|
POST
|
Hi, I am wondering if anyone has tried (successfully) to setup OAuth in a Blazor Web Assembly. I think I have things configured based on the documentation but continue to get an error saying it is not setup correct. Without any specifics. Thanks - joe
... View more
07-28-2022
10:28 AM
|
0
|
3
|
1915
|
| Title | Kudos | Posted |
|---|---|---|
| 1 | 10-23-2025 12:16 PM | |
| 1 | 10-19-2022 01:08 PM | |
| 1 | 09-03-2025 09:25 AM | |
| 1 | 04-16-2025 12:37 PM | |
| 1 | 03-18-2025 12:17 PM |
| Online Status |
Offline
|
| Date Last Visited |
12-04-2025
04:12 PM
|