Unable To Get Popup Information From Portal Via Any Method of JavaScript Code

573
1
Jump to solution
03-27-2023 07:29 AM
osswmi
by
New Contributor II

I'm encountering some very strange behavior that I have never encountered developing applications that use ArcGIS JSAPI. For reference, my setup is using the following versions of ArcGIS products:

  • ArcGIS Pro 2.9.6
  • ArcGIS Enterprise 10.9.1 with Federated Portal
  • ArcGIS JavaScript SDK 4.25

I am trying to implement the default popup display fields that can be assigned in ArcGIS Pro's "Configure Pop-ups" window pane. The fields display appropriately when viewed from Portal's map viewer. Initially, I was only querying the map service endpoint "/MapServer" but soon discovered that this information does not come from the map service itself but from Portal's service information of that map service.

Therefore, I attempted to query the Portal system for this popup information but ran into the two scenarios which both ended in a dead end. The code I am using to load the map layers and to register the token is as follows:

 

// the token is obtained server side for the app so that the user does not have to sign in.
// this code works flawlessly if querying map services endpoints directly, but does not appear to work at all for portal endpoints
IdentityManager.registerToken({
	expires: VueModel.ArcGISToken.expires,
	token: VueModel.ArcGISToken.token,
	ssl: true,
	server: "https://portal.mygis.com/portal/sharing/rest/"
});

// does not work at all and prompts for authentication before map can load. no token is present on the network call
let baseLayer =	new MapImageLayer({	portalItem: { id: "some_unique_portal_id" }	});


// does work and loads the map. token is present on the network call, but does not return the "Pop-Up" information needed for my application
// let baseLayer = new window.GIS.MapImageLayer({ url: "https://server.mygis.com/server/rest/services/SurroundingCounties/MapServer" })

Map.layers.add(baseLayer);

 

The first problem I ran into was that even though I have a valid token (which can be confirmed by opening the target Portal content manually in the URL bar with the same token), I'm still getting prompted to sign in in my custom JavaScript application. I checked the developer console and saw that when attempting to add a layer, the call to the "sharing/rest/content/item/" path on the Portal server did not append the token as it would if you were adding a layer to the map via the "MapServer/FeatureServer" endpoint.

The second problem I ran into occurred when I went to change the code to load the map layers from the map service endpoint, then chain an AJAX call to the Portal server's "/data" endpoint for the same map layer(s) to get the popup information that was needed. This is where things really get weird. The call to the Portal endpoint would always return an "Invalid Token" response. Below is the code that was used to make that call:

 

$.ajax({
    type: "GET",
    url: "https://portal.mygis/portal/sharing/rest/content/items/some_unique_portal_id/data",
    async: true,
	data: {
		token: VueModel.ArcGISToken.token,
		f: pjson
	},
    contentType: "application/json; charset=utf-8",
    dataType: "jsonp",
    success: function (portalResponse) {
        console.log(portalResponse);
    },
    error: function (portalError) {
        console.log(portalError);
    }
});

 

Even if I make a call using the following code, I would still get the same "Invalid Token" response:

 

window.open("https://portal.mygis.com/portal/sharing/rest/content/items/some_unique_map_id/data?format=json&token=some_randomly_but_perfectly_valid_token")

 

But, here is the kicker in all this...

If I take the URL part of the "window.open" code above and manually into a new window and paste the URL, the content loads with the information I'm trying to get to. It seems that Portal is rejecting any automated attempt by JavaScript to get information about a map server or feature server with any assigned token.

For the time being, we are using the "Field" tab in ArcGIS Pro to limit what fields are shown in the popup on the map but this is going to present problems down the road as we get deeper in application development.

Any ideas on what is happening here?

0 Kudos
1 Solution

Accepted Solutions
osswmi
by
New Contributor II

So I was able to come up with a workable solution to the problem above. Rather than letting the clients machine make the needed JavaScript calls to the Portal server to get the popup information, they will now call back to the web server hosting the custom JavaScript application via an independent Asp.NET Web Method call. That server will in turn call out to Portal server to get the popup information and relay it back to the client.

I'm still not sure why the JSAPI is incapable of providing a token to Portal using the "registerToken" method provided by the Identity Manager module and/or why Portal rejects any JavaScript requests for item info even with a valid token, but for the time being, we now have an acceptable workaround.

Below is the the C# code that I came up with so that anyone else can take it and transpose it to fit their particular setup.

 

// param serviceUrl: the url to the /MapServer or /FeatureServer endpoint
// param portalUrl: the url to the portal items content path "sharing/content/items/item_id"
// param token: the token assigned to the end user
[WebMethod]
public static WebMethodResult GetArcGISItemInfo(string serviceUrl, string portalUrl, string token)
{
	WebMethodResult webMethodResult = new WebMethodResult();

	try
	{
		JavaScriptSerializer javaScriptSerializer = new JavaScriptSerializer();

		HttpWebRequest serviceRequest = (HttpWebRequest)WebRequest.Create(serviceUrl);

		string servicePostData = "f=pjson";
		servicePostData += "&token=" + token;
		
		byte[] serviceData = Encoding.ASCII.GetBytes(servicePostData);

		serviceRequest.Method = "POST";
		serviceRequest.ContentType = "application/x-www-form-urlencoded";
		serviceRequest.ContentLength = serviceData.Length;

		using (Stream stream = serviceRequest.GetRequestStream())
		{
			stream.Write(serviceData, 0, serviceData.Length);
		}

		HttpWebResponse serviceResponse = (HttpWebResponse)serviceRequest.GetResponse();

		StreamReader serviceStreamReader = new StreamReader(serviceResponse.GetResponseStream() ?? throw new InvalidOperationException());
		
		string serviceResponseString = serviceStreamReader.ReadToEnd();
		string portalResponseString = null;

		if (!string.IsNullOrWhiteSpace(portalUrl))
		{
			HttpWebRequest request = (HttpWebRequest)WebRequest.Create(portalUrl + "/data");

			string portalPostData = "f=pjson";
			portalPostData += "&token=" + token;

			byte[] data = Encoding.ASCII.GetBytes(portalPostData);

			request.Method = "POST";
			request.ContentType = "application/x-www-form-urlencoded";
			request.ContentLength = data.Length;

			using (Stream stream = request.GetRequestStream())
			{
				stream.Write(data, 0, data.Length);
			}

			HttpWebResponse portalResponse = (HttpWebResponse)request.GetResponse();

			StreamReader portalStreamReader = new StreamReader(portalResponse.GetResponseStream() ?? throw new InvalidOperationException());

			portalResponseString = portalStreamReader.ReadToEnd();
		}

		webMethodResult.MethodResult = new
		{
			ServiceInfo = javaScriptSerializer.Deserialize<object>(serviceResponseString),
			PortalInfo = string.IsNullOrWhiteSpace(portalResponseString) ? "\\{\\}" : javaScriptSerializer.Deserialize<object>(portalResponseString)
		};
	}
	catch (Exception e)
	{
		Log.Error(e.Message, e);
	}

	return webMethodResult;
}

 

 

View solution in original post

0 Kudos
1 Reply
osswmi
by
New Contributor II

So I was able to come up with a workable solution to the problem above. Rather than letting the clients machine make the needed JavaScript calls to the Portal server to get the popup information, they will now call back to the web server hosting the custom JavaScript application via an independent Asp.NET Web Method call. That server will in turn call out to Portal server to get the popup information and relay it back to the client.

I'm still not sure why the JSAPI is incapable of providing a token to Portal using the "registerToken" method provided by the Identity Manager module and/or why Portal rejects any JavaScript requests for item info even with a valid token, but for the time being, we now have an acceptable workaround.

Below is the the C# code that I came up with so that anyone else can take it and transpose it to fit their particular setup.

 

// param serviceUrl: the url to the /MapServer or /FeatureServer endpoint
// param portalUrl: the url to the portal items content path "sharing/content/items/item_id"
// param token: the token assigned to the end user
[WebMethod]
public static WebMethodResult GetArcGISItemInfo(string serviceUrl, string portalUrl, string token)
{
	WebMethodResult webMethodResult = new WebMethodResult();

	try
	{
		JavaScriptSerializer javaScriptSerializer = new JavaScriptSerializer();

		HttpWebRequest serviceRequest = (HttpWebRequest)WebRequest.Create(serviceUrl);

		string servicePostData = "f=pjson";
		servicePostData += "&token=" + token;
		
		byte[] serviceData = Encoding.ASCII.GetBytes(servicePostData);

		serviceRequest.Method = "POST";
		serviceRequest.ContentType = "application/x-www-form-urlencoded";
		serviceRequest.ContentLength = serviceData.Length;

		using (Stream stream = serviceRequest.GetRequestStream())
		{
			stream.Write(serviceData, 0, serviceData.Length);
		}

		HttpWebResponse serviceResponse = (HttpWebResponse)serviceRequest.GetResponse();

		StreamReader serviceStreamReader = new StreamReader(serviceResponse.GetResponseStream() ?? throw new InvalidOperationException());
		
		string serviceResponseString = serviceStreamReader.ReadToEnd();
		string portalResponseString = null;

		if (!string.IsNullOrWhiteSpace(portalUrl))
		{
			HttpWebRequest request = (HttpWebRequest)WebRequest.Create(portalUrl + "/data");

			string portalPostData = "f=pjson";
			portalPostData += "&token=" + token;

			byte[] data = Encoding.ASCII.GetBytes(portalPostData);

			request.Method = "POST";
			request.ContentType = "application/x-www-form-urlencoded";
			request.ContentLength = data.Length;

			using (Stream stream = request.GetRequestStream())
			{
				stream.Write(data, 0, data.Length);
			}

			HttpWebResponse portalResponse = (HttpWebResponse)request.GetResponse();

			StreamReader portalStreamReader = new StreamReader(portalResponse.GetResponseStream() ?? throw new InvalidOperationException());

			portalResponseString = portalStreamReader.ReadToEnd();
		}

		webMethodResult.MethodResult = new
		{
			ServiceInfo = javaScriptSerializer.Deserialize<object>(serviceResponseString),
			PortalInfo = string.IsNullOrWhiteSpace(portalResponseString) ? "\\{\\}" : javaScriptSerializer.Deserialize<object>(portalResponseString)
		};
	}
	catch (Exception e)
	{
		Log.Error(e.Message, e);
	}

	return webMethodResult;
}

 

 

0 Kudos