How to deserialize results of Autodesk.Forge.FoldersAPI.GetFolderContentsAsync(...) - autodesk-forge

I am trying to list the file contents of a given BIM360 folder using the forge .NET client to access the Autodesk's Data Management API.
GetFolderContents returns a JsonApiCollection but I am confused as to how to deserialize the response data using the SDK's model objects.
C# sample code from the documentation:
// Configure OAuth2 access token for authorization: oauth2_access_code
Configuration.Default.AccessToken = "YOUR_ACCESS_TOKEN";
var apiInstance = new FoldersApi();
var projectId = projectId_example; // string | the `project id`
var folderId = folderId_example; // string | the `folder id`
var filterType = new List<string>(); // List<string> | filter by the `type` of the `ref` target (optional)
var filterId = new List<string>(); // List<string> | filter by the `id` of the `ref` target (optional)
var filterExtensionType = new List<string>(); // List<string> | filter by the extension type (optional)
var pageNumber = 56; // int? | specify the page number (optional)
var pageLimit = 56; // int? | specify the maximal number of elements per page (optional)
try
{
JsonApiCollection result = apiInstance.GetFolderContents(projectId, folderId, filterType, filterId, filterExtensionType, pageNumber, pageLimit);
Debug.WriteLine(result);
}
catch (Exception e)
{
Debug.Print("Exception when calling FoldersApi.GetFolderContents: " + e.Message );
}

Please refer to our Hubs Browser tutorial that handles this.
Specificaly in the Data Browsing part.
You can also check the repo.

Related

Schema Extension Value is always NULL when updating through Microsoft Graph SDK

Step 1:
Created GraphServiceClient using Microsoft.Graph 4.9.0 and Microsoft.Graph.Core 2.0.5 SDK
var scopes = new[] { "https://graph.microsoft.com/.default" };
ClientSecretCredential clientSecretCredential = new ClientSecretCredential(tenantId, clientId, clientSecret, new ClientSecretCredentialOptions()
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
});`
GraphServiceClient graphServiceClient = new GraphServiceClient(clientSecretCredential, scopes);
Step 2:
And created a custom schema extension like below.
SchemaExtension schemaExtension = new SchemaExtension()
{
Id = "data1",
Description = "creating test schema extn",
TargetTypes = new List<string>()
{
"User"
},
Properties = new List<ExtensionSchemaProperty>()
{
new ExtensionSchemaProperty()
{
Name ="prop1",
Type ="String"
}
}
};
Step 3:
Updated the Schema extension status to "Available"
var updatedExtn = await graphServiceClient
.SchemaExtensions[schemaExtension.Id].Request()
.UpdateAsync(new SchemaExtension()
{
Status = "Available"
});
Step 4:
Create Class for extension data
public class data1
{
// You must serialize your property names to camelCase if your SchemaExtension describes as such.
[JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "prop1", Required = Newtonsoft.Json.Required.Default)]
public string Prop1 { get; set; }
}
Step 5:
Find the User and add the created schema extension to the user
IDictionary<string, object> extensionInstance = new Dictionary<string, object>();
// The below line is not working. but doesn't throw error
extensionInstance.Add(schemaExtension.Id, new data1 { prop1 = "testing" });
var usrCollection = await graphServiceClient.Users
.Request()
.Filter($"userPrincipalNames eq '{adelev_Mail}'")
.GetAsync();
var usr = usrCollection.FirstOrDefault();
if(usr != null)
{
usr.AdditionalData.Add(extensionInstance);
var updatedUser = await graphServiceClient.Users[usr.Id]
.Request()
.UpdateAsync(usr);
}
Step 6:
When you try to retrieve the extension the value is NULL.
User updatedUser = await graphServiceClient.Users[usr.Id].Request()
.Select($"id, {schemaExtension.Id}")
.GetAsync();
But it works with API using Graph Explorer.
PATCH https://graph.microsoft.com/v1.0/users/{userId}
{
"extXXXXXXXX_data1":
{
"prop1" : "testing"
}
}
Please let me know if I'm missing anything here. Any help here is much appreciated.
You should accessing the data on AdditionalData property. Try looking at user.AdditionalData in your result. Here is a screenshot with my example.
Getting User with Schema extension from Graph explorer.
While using the SDK, i access my custom data in user.AdditionalData
Check this thread - Graph SDK and SchemaExtensions for details.

Cannot Create a Group, Invalid Scope

I am trying to create a group with the following dot.net code:
var groupDef = new Group()
{
DisplayName = name,
MailNickname = name + " " + GetTimestamp(),
Description = "Group/Team created for testing purposes",
Visibility = "Private",
GroupTypes = new string[] { "Unified" }, // same for all teams
MailEnabled = true, // same for all teams
SecurityEnabled = false, // same for all teams
AdditionalData = new Dictionary<string, object>()
{
["owners#odata.bind"] = owners.Select(o => $"{graphV1Endpoint}/users/{o.Id}").ToArray(),
["members#odata.bind"] = members.Select(o => $"{graphV1Endpoint}/users/{o.Id}").ToArray(),
}
};
// Create the modern group for the team
Group group = await graph.Groups.Request().AddAsync(groupDef);
I am getting a "Method not allowed." error thrown on the last line shown (Group group = await ...).
The scope parameter for the auth provider contains "Group.Read.All Group.ReadWrite.All".
If I add Group.Create to the scope I get an error stating the scope is invalid. Reducing the scope to just "Group.Create" also gives an error.
It certainly appears that I cannot create a group without Group.Create in the scope, but that throws an error at sign in.
Microsoft.Graph is version 3.19.0
Microsoft.Graph.Core is version 1.22.0
I ended up serializing the object and making the Http call with my own code. Basically, something like this:
string json = JsonConvert.SerializeObject(groupDef, jsonSettings);
Group group = HttpPost<Group>("/groups", json);
No permissions were changed.

How do I consume a JSon object that has no headers?

My C# MVC5 Razor page returns a Newtonsoft json link object to my controller (the "1" before \nEdit |" indicates that the checkbox is checked:
"{\"0\": [\"6146\",\"Kimball\",\"Jimmy\",\"General Funny Guy\",\"277\",\"Unite\",\"Jun 2019\",\"\",\"\",\"1\",\"\nEdit |\nDetails\n\n \n\"],\"1\": [\"6147\",\"Hawk\",\"Jack\",\"\",\"547\",\"Painters\",\"Jun 2019\",\"\",\"\",\"on\",\"\nEdit |\nDetails\n\n \n\"]}"
How do I parse this?
I am using a WebGrid to view and I want to allow the users to update only the lines they want (by checking the checkbox for that row), but it doesn't include an id for the 's in the dom. I figured out how to pull the values, but not the fieldname: "Last Name" , value: "Smith"... I only have the value and can't seem to parse it... one of my many failed attempts:
public ActoinResult AttMods(string gridData)
{
dynamic parsedArray = JsonConvert.DeserializeObject(gridData);
foreach (var item in parsedArray)
{
string[] itemvalue = item.Split(delimiterChars);
{
var id = itemvalue[0];
}
}
I finally sorted this one out..If there is a more dynamic answer, please share... I'll give it a few days before I accept my own (admittedly clugy) answer.
if(ModelState.IsValid)
{
try
{
Dictionary <string,string[]> log = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, string[]>>(gridData);
foreach (KeyValuePair<string,string[]> keyValue in log)
{
if (keyValue.Value[9] == "1")//Update this row based on the checkbox being checked
{
var AttendeeID = keyValue.Value[0];
int intAttendeeID = 0;
if (int.TryParse(AttendeeID, out intAttendeeID))//Make sure the AttendeeID is valid
{
var LName = keyValue.Value[1];
var FName = keyValue.Value[2];
var Title = keyValue.Value[3];
var kOrgID = keyValue.Value[4];
var Org = keyValue.Value[5];
var City = keyValue.Value[7];
var State = keyValue.Value[8];
var LegalApproval = keyValue.Value[9];
tblAttendee att = db.tblAttendees.Find(Convert.ToInt32(AttendeeID));
att.FName = FName;
att.LName = LName;
att.Title = Title;
att.kOrgID = Convert.ToInt32(kOrgID);
att.Organization = Org;
att.City = City;
att.State = State;
att.LegalApprovedAtt = Convert.ToBoolean(LegalApproval);
}
}
}
db.SaveChanges();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
You can avoid assigning the var's and just populate the att object with the KeyValue.Value[n] value, but you get the idea.

Downloading dwg to Forge

I'm in the process of learning the Forge platform. I'm currently using an example (Jigsawify) written by Kean Walmsley because it most accurately describes my goals. I'm running into an issue of getting my file to download from an Azure Storage Account to Forge. The error I receive is "The value for one of the HTTP headers is not in the correct format." My question is how does someone go about troubleshooting HTTP protocol when writing, in this case, a workitem in code? I can put in a breakpoint to view the workitem, but I'm not versed enough to understand where the flaw is in the HTTP header, or even where to find it. Is there a specific property of the workitem I should be looking at? If I could find the HTTP statement, I could test it, but I don't where I should find it.
Or am I just completely off base?
Anyway here's the code. It's a modified version of what Kean wrote:
static void SubmitWorkItem(Activity activity)
{
Console.WriteLine("Submitting workitem...");
CloudStorageAccount storageAccount =
CloudStorageAccount.Parse(Microsoft.Azure.CloudConfigurationManager.GetSetting("StorageConnectionString"));
StorageCredentials crd = storageAccount.Credentials;
CloudFileClient fileClient = storageAccount.CreateCloudFileClient();
CloudFileShare ShareRef = fileClient.GetShareReference("000scrub");
CloudFileDirectory rootDir = ShareRef.GetRootDirectoryReference();
CloudFile Fileshare = rootDir.GetFileReference("3359fort.dwg");
// Create a workitem
var wi = new WorkItem()
{
Id = "", // Must be set to empty
Arguments = new Arguments(),
ActivityId = activity.Id
};
if (Fileshare.Exists())
{
wi.Arguments.InputArguments.Add(new Argument()
{
Name = "HostDwg", // Must match the input parameter in activity
Resource = Fileshare.Uri.ToString(),
StorageProvider = StorageProvider.Generic // Generic HTTP download (vs A360)
});
}
wi.Arguments.OutputArguments.Add(new Argument()
{
Name = "Results", // Must match the output parameter in activity
StorageProvider = StorageProvider.Generic, // Generic HTTP upload (vs A360)
HttpVerb = HttpVerbType.POST, // Use HTTP POST when delivering result
Resource = null, // Use storage provided by AutoCAD.IO
ResourceKind = ResourceKind.ZipPackage // Upload as zip to output dir
});
container.AddToWorkItems(wi);
container.SaveChanges();
// Polling loop
do
{
Console.WriteLine("Sleeping for 2 sec...");
System.Threading.Thread.Sleep(2000);
container.LoadProperty(wi, "Status"); // HTTP request is made here
Console.WriteLine("WorkItem status: {0}", wi.Status);
}
while (
wi.Status == ExecutionStatus.Pending ||
wi.Status == ExecutionStatus.InProgress
);
// Re-query the service so that we can look at the details provided
// by the service
container.MergeOption =
Microsoft.OData.Client.MergeOption.OverwriteChanges;
wi = container.WorkItems.ByKey(wi.Id).GetValue();
// Resource property of the output argument "Results" will have
// the output url
var url =
wi.Arguments.OutputArguments.First(
a => a.Name == "Results"
).Resource;
if (url != null)
DownloadToDocs(url, "SGA.zip");
// Download the status report
url = wi.StatusDetails.Report;
if (url != null)
DownloadToDocs(url, "SGA-Report.txt");
}
Any help is appreciated,
Chuck
Azure requires that you specify the x-ms-blob-type header when you upload to a presigned URL. See https://github.com/Autodesk-Forge/design.automation-.net-input.output.sample/blob/master/Program.cs#L167
So, I was able to figure out how to download my file from Azure to Forge using Albert's suggestion of moving to a blob service. Here's the code:
static void SubmitWorkItem(Activity activity)
{
Console.WriteLine("Submitting workitem...");
CloudStorageAccount storageAccount =
CloudStorageAccount.Parse(Microsoft.Azure.CloudConfigurationManager.GetSetting("StorageConnectionString"));
CloudBlobClient BlobClient = storageAccount.CreateCloudBlobClient();
CloudBlobContainer cloudBlobContainer = BlobClient.GetContainerReference("000scrub");
CloudBlockBlob blockBlob = cloudBlobContainer.GetBlockBlobReference("3359fort.dwg");
// Create a workitem
var wi = new WorkItem()
{
Id = "", // Must be set to empty
Arguments = new Arguments(),
ActivityId = activity.Id
};
if (blockBlob.Exists())
{
wi.Arguments.InputArguments.Add(new Argument()
{
Name = "HostDwg", // Must match the input parameter in activity
Resource = blockBlob.Uri.ToString(),
StorageProvider = StorageProvider.Generic, // Generic HTTP download (vs A360)
Headers = new System.Collections.ObjectModel.ObservableCollection<Header>()
{
new Header() { Name = "x-ms-blob-type", Value = "BlockBlob" } // This is required for Azure.
}
});
}
wi.Arguments.OutputArguments.Add(new Argument()
{
Name = "Results", // Must match the output parameter in activity
StorageProvider = StorageProvider.Generic, // Generic HTTP upload (vs A360)
HttpVerb = HttpVerbType.POST, // Use HTTP POST when delivering result
Resource = null, // Use storage provided by AutoCAD.IO
ResourceKind = ResourceKind.ZipPackage, // Upload as zip to output dir
});
container.AddToWorkItems(wi);
container.SaveChanges();
// Polling loop
do
{
Console.WriteLine("Sleeping for 2 sec...");
System.Threading.Thread.Sleep(2000);
container.LoadProperty(wi, "Status"); // HTTP request is made here
Console.WriteLine("WorkItem status: {0}", wi.Status);
}
while (
wi.Status == ExecutionStatus.Pending ||
wi.Status == ExecutionStatus.InProgress
);
// Re-query the service so that we can look at the details provided
// by the service
container.MergeOption =
Microsoft.OData.Client.MergeOption.OverwriteChanges;
wi = container.WorkItems.ByKey(wi.Id).GetValue();
// Resource property of the output argument "Results" will have
// the output url
var url =
wi.Arguments.OutputArguments.First(
a => a.Name == "Results"
).Resource;
if (url != null)
DownloadToDocs(url, "SGA.zip");
// Download the status report
url = wi.StatusDetails.Report;
if (url != null)
DownloadToDocs(url, "SGA-Report.txt");
}
What isn't complete is the result section. The ZIP has nothing in it, but hey, baby steps right?
Thanks Albert.
-Chuck

Azure Stream Analytics Error : Could not deserialize the input event(s) from IOT hub

I have created the Stream Analytics job to read data from IOT hub as input and and to store data to SQL DB.
Here are the some important input details configured for Steam Analytics job are
Event Serialization format: JSON
Encoding :utf-8
The message is sent to IOT Hub from Dotnet simulated code.
When I am running my job I am getting the following error:
Could not deserialize the input event as Json. Some possible reasons:
1) Malformed events
2) Input source configured with incorrect serialization format
And here is the my dotnet code.
private static async void ReceiveC2dAsync()
{
Console.WriteLine("\nReceiving cloud to device messages from service");
while (true)
{
Message receivedMessage = await deviceClient.ReceiveAsync();
if (receivedMessage == null) continue;
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("Received message: {0}", Encoding.ASCII.GetString(receivedMessage.GetBytes()));
Console.ResetColor();
await deviceClient.CompleteAsync(receivedMessage);
}
}
private static async void SendDeviceToCloudMessagesAsync()
{
double minTemperature = 20;
double minHumidity = 60;
int messageId = 1;
Random rand = new Random();
while (true)
{
double currentTemperature = minTemperature + rand.NextDouble() * 15;
double currentHumidity = minHumidity + rand.NextDouble() * 20;
var telemetryDataPoint = new
{
messageId = messageId++,
deviceId = "myFirstDevice",
temperature = currentTemperature,
humidity = currentHumidity
};
var messageString = JsonConvert.SerializeObject(telemetryDataPoint);
string levelValue;
string temperatureAlert = "false";
if (rand.NextDouble() > 0.7)
{
if (rand.NextDouble() > 0.5)
{
messageString = "This is a critical message";
levelValue = "critical";
}
else
{
messageString = "This is a storage message";
levelValue = "storage";
}
}
else
{
levelValue = "normal";
}
if(currentTemperature > 30)
{
temperatureAlert = "true";
}
var message = new Message(Encoding.UTF8.GetBytes(messageString));
message.Properties.Add("level", levelValue);
message.Properties.Add("temperatureAlert", temperatureAlert);
await deviceClient.SendEventAsync(message);
Console.WriteLine("{0} > Sending message: {1}", DateTime.Now, messageString);
await Task.Delay(1000);
}
}
it looks like your simulated device generated non-json formatted messages such as "This is a critical message" and "This is a storage message".
Basically, you have two choices to fix this issue:
1. comment this part in the simulated code or
2. add the filter in the Azure IoT Hub Routes for these messages