Cannot parse id for appbundles using Design Automation SDK - autodesk-designautomation

Here I am again trying to use the Design Automation SDK and I get this error when I try to retrieve bundle aliases, versions or other information that require the id.
I am testing that using one of the existing appbundles available...
public static async Task<dynamic> GetAppBundleVersionsAsync(ForgeService service, Token token, string id)
{
try
{
if (token.ExpiresAt < DateTime.Now)
token = Get2LeggedToken();
AppBundlesApi appBundlesApi = new AppBundlesApi(service);
Dictionary<string, string> headers = new Dictionary<string, string>();
headers.Add("Authorization", "Bearer " + token.AccessToken);
headers.Add("content-type", "application/json");
var aliases = await appBundlesApi.GetAppBundleVersionsAsync(id, null, null, headers);
return aliases;
}
catch (Exception ex)
{
Console.WriteLine(string.Format("Error : {0}", ex.Message));
return null;
}
}
Almost thinking to go to my previous RestSharp implementation :)

There are 2 kinds of IDs:
Fully qualified (string in format owner.name+alias)
Unqualified (just name)
You are trying to list versions of your own AppBundle, so you need to use Unqualified. It seems your ID is fully qualified form.
For more info look at API documentation description of endpoint id parameter you are using https://forge.autodesk.com/en/docs/design-automation/v3/reference/http/design-automation-appbundles-id-versions-GET/#uri-parameters

Related

Asp.net FromBody fails when payload is null

I have a controller that expects to get a json payload ie
public async Task<IActionResult> InitUser([FromBody] Tenant tenant)
This is fine when a valid json payload is sent, but if no payload is sent I get the error
No input formatter was found to support the content type 'null' for use with the [FromBody] attribute
And HTTP status code 415 is returned to the client.
Is it possible to catch this case and set the json payload to some default value so that the input formatter wont throw this error?
You can remove the [FromBody] attribute and get the body directly from the HTTP request. Make sure you have the [HttpPost] Attribute decoration.
In the example below you can see how to do that in a very simple way. You can also create your own CustomAttribute and middleware if you want to make it a system wide and elegant solution.
You will also need to parse the body. For that you can use JsonConverter if you like.
[HttpPost]
public async Task<IActionResult> Index()
{
Tenant tenant;
string result;
using (var reader = new StreamReader(Request.Body))
{
var body = await reader.ReadToEndAsync();
result = body;
}
//Define the naming strategy here if you need
DefaultContractResolver contractResolver = new DefaultContractResolver
{
//NamingStrategy = new SnakeCaseNamingStrategy()
NamingStrategy = new CamelCaseNamingStrategy()
};
//Optional configuration to add in DeserializeObject constructor as second param.
var jsonSerializerSettings = new JsonSerializerSettings
{
ContractResolver = contractResolver,
};
tenant = JsonConvert.DeserializeObject<Tenant>(result);
Console.WriteLine(tenant);
return View();
}

Post Request via JxBrowser does not hand over data

I try a POST Request with the new JxBrowser Version. Unfortunately the data in the body is not handed over.
I guess I am just not using JxBrowser 7 properly.
GET Request does work.
// Post Request
protected void postRequestFromScout(JxBrowserEvent event) {
String url = event.getUrl();
Map<String, String> postData = event.getPostData();
getBrowser().navigation().loadUrl(LoadRequest.newBuilder()
.setUrl(url)
.setPostData(toPostDataString(postData))
.build());
}
// data in POST Request Body as String
protected String toPostDataString(Map<String, String> postData) {
StringBuilder sb = new StringBuilder();
for (Entry<String, String> entry : postData.entrySet()) {
sb
.append(entry.getKey())
.append("=")
.append(IOUtility.urlEncode(entry.getValue()))
.append("&");
}
sb.deleteCharAt(sb.length() - 1);
return sb.toString();
}
I obviously need to hand over the data in this way:
LoadUrlParams.newBuilder(url)
.postData(toPostDataString(postData))
.build();
As we are using a Compiler based on Java 7 in our Project, this is not a solution for me right now and I will check for another one if possible, but it surely works when used with Java 8.

C# OData GetKeyFromUri - Resource not found for the segment 'odata'

I've updated from Microsoft.AspNet.OData version 6.0.0 to OData version 7.0.1. The upgrade has broken my ability to get the Id from a path when linking one object to another. Here is my Web API call to add a role to a specific user using the OData standard:
POST: http://localhost:61506/odata/users('bob')/roles/$ref
Request body: {"#odata.id":"http://localhost:61506/odata/roles(1)"}
The Web API method verifies the user and then makes a call to Helpers.GetKeyFromUri to get the role Id value from the request body.
[HttpPost, HttpPut]
public IHttpActionResult CreateRef([FromODataUri] string key, string navigationProperty, [FromBody] Uri link)
{
// Ensure the User exists
User user = new User().GetById(key);
if (user == null)
{
return NotFound();
}
// Determine which navigation property to use
switch (navigationProperty)
{
case "roles":
// Get the Role id
int roleId;
try
{
roleId = Helpers.GetKeyFromUri<int>(Request, link);
}
catch (Exception ex)
{
return BadRequest();
}
// Ensure the Role exists
Role role = new Role().GetById(roleId);
if (role == null)
{
return NotFound();
}
// Add the User/Role relationship
user.Roles.Add(role);
user.Update();
break;
default:
return StatusCode(HttpStatusCode.NotImplemented);
}
return StatusCode(HttpStatusCode.NoContent);
}
That function looks like this (Originally from here but with updated references: https://github.com/OData/ODataSamples/blob/master/RESTier/Trippin/Trippin/Helpers.cs)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Web.Http.Routing;
using Microsoft.AspNet.OData.Extensions;
using Microsoft.AspNet.OData.Routing;
using Microsoft.OData.UriParser;
namespace Project1.Extensions
{
public class Helpers
{
public static TKey GetKeyFromUri<TKey>(HttpRequestMessage request, Uri uri)
{
if (uri == null)
{
throw new ArgumentNullException("uri");
}
var urlHelper = request.GetUrlHelper() ?? new UrlHelper(request);
var pathHandler = (IODataPathHandler)request.GetRequestContainer().GetService(typeof(IODataPathHandler));
string serviceRoot = urlHelper.CreateODataLink(
request.ODataProperties().RouteName,
pathHandler, new List<ODataPathSegment>());
var odataPath = pathHandler.Parse(serviceRoot, uri.LocalPath, request.GetRequestContainer());
var keySegment = odataPath.Segments.OfType<KeySegment>().FirstOrDefault();
if (keySegment == null)
{
throw new InvalidOperationException("The link does not contain a key.");
}
var value = keySegment.Keys.FirstOrDefault().Value;
return (TKey)value;
}
}
}
This line of code is now throwing the following error: Resource not found for the segment 'odata'
var odataPath = pathHandler.Parse(serviceRoot, uri.LocalPath, request.GetRequestContainer());
This worked fine when using OData 6.0.0 but fails in 7.0.1. It seems to have some sort of issue parsing my odata segment or not being able to find it at all. Here is my routing setup if it helps:
public static void Register(HttpConfiguration config)
{
// Setup the OData routes and endpoints
config.MapODataServiceRoute(
routeName: "ODataRoute",
routePrefix: "odata",
model: GetEdmModel());
// Enable OData URL querying globally
config.Count().Filter().Select().OrderBy().Expand().MaxTop(null);
}
I know I'm a bit late here, but I ran into this same problem with upgrading to Microsoft.AspNet.OData 7.x. After a bit of debugging and tinkering, I found that this code works for me - without having to remove the routePrefix:
public static TKey GetKeyFromUri<TKey>(HttpRequestMessage request, Uri uri)
{
if (uri == null)
{
throw new ArgumentNullException(nameof(uri));
}
var urlHelper = request.GetUrlHelper() ?? new UrlHelper(request);
string serviceRoot = urlHelper.CreateODataLink(
request.ODataProperties().RouteName,
request.GetPathHandler(),
new List<ODataPathSegment>());
var odataPath = request.GetPathHandler().Parse(
serviceRoot,
uri.AbsoluteUri,
request.GetRequestContainer());
var keySegment = odataPath.Segments.OfType<KeySegment>().FirstOrDefault();
if (keySegment == null)
{
throw new InvalidOperationException("The link does not contain a key.");
}
return (TKey)keySegment.Keys.FirstOrDefault().Value;
}
It turns out that IODataPathHandler.Parse(...) can take an absolute URI and resolve it against the serviceRoot.
The other key difference is that KeySegment.Keys already has a mapping of Key-Values, where the value is already parsed - it just needs to be casted.
For reference, I am using Microsoft.AspNet.OData 7.4.0
Hope this helps!
I caused myself the same problem by changing routePrefix from null to odata just like you've done. Setting routePrefix to null will allow your code to work perfectly fine as long as you don't need a route prefix (such as /odata/).

Graph REST AddMember to Group - Bad Request

I am attempting to add members to a group. I am able to list all groups in my org, get user by email, get all users and I can even remove a Member from a group but I cannot add one - The error returned is 400 Bad Request.
Here is the function which is the same function signature as those that work: (I do have the accesstoken, valid group id and a valid member id)
I have confirmed the body data looks correct at least as far as I can see from the example in the docs.
Not sure what else I can add to make things clearer, ask and I'll update
public async Task<string> AddGroupMember(string accessToken, string groupId, string memberId)
{
var status = string.Empty;
string endpoint = $"https://graph.microsoft.com/v1.0/groups/{groupId}/members/$ref";
string queryParameter = "";
// pass body data
var keyOdataId = "#odata.id";
var valueODataId = $"https://graph.microsoft.com/v1.0/directoryObjects/{memberId}";
var values = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>(keyOdataId, valueODataId)
};
var body = new FormUrlEncodedContent(values);
try
{
using(var client = new HttpClient())
{
using(var request = new HttpRequestMessage(HttpMethod.Post, endpoint + queryParameter))
{
request.Content = body;
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
using(var response = await client.SendAsync(request))
{
if (response.StatusCode == HttpStatusCode.NoContent)
status = "Member added to Group";
else
status = $"Unable to add Member to Group: {response.StatusCode}";
}
}
}
}
catch (Exception ex)
{
status = $"Error adding Member to Group: {ex.Message}";
}
return status;
}
Thanks for any help that anyone can offer - this is the last call I have to make then home free
Found the issue for any who care to know for the future:
var body = new FormUrl... my code was incorrect, what's needed is a simple json string changed to this UPDATED:
var jsonData = $#"{{ ""{keyOdataId}"": ""{valueODataId}"" }}";
var body = new StringContent(jsonData, Encoding.UTF8, "application/json");
I would normally put the values in a class but this is for proof of concept and the json key needs to look exactly like this #odata.id
Clarifying what is happening here:
The request body for this call should be JSON encoded (application/json). The FormUrlEncodedContent method returns your dictionary as Form encoded (application/x-www-form-urlencoded).
You can write the JSON by hand (like you have so far) but a better solution would be to leverage Json.NET. This will let you encode the dictionary in much the same way you were with FormUrlEncodedContent:
var values = new Dictionary<string, string>
{
{ keyOdataId, valueODataId}
};
var body = JsonConvert.SerializeObject(values);
If you're going to be doing a lot of work with Microsoft Graph, I would highly recommend switching to the Microsoft Graph .NET SDK.
You're method here would be far simpler using the SDK:
public async Task<string> AddGroupMember(string groupId, string memberId)
{
GraphServiceClient graphClient = AuthenticationHelper.GetAuthenticatedClient();
User userToAdd = new User { Id = memberId };
await graphClient.Groups[groupId].Members.References.Request().AddAsync(userToAdd);
}

Handling concurrency exceptions with external API calls

I have the following POST edit action method, which mainly perform two Update actions:-
Edit the object on the external system suing API calls.
Edit the object on our system database.
[HttpPost]
public ActionResult Create(RackJoin rj, FormCollection formValues)
{string controllername = RouteData.Values["controller"].ToString();
if (ModelState.IsValid)
{ var message = "";
var status = "";
long assetid = new long();
XmlDocument doc = new XmlDocument();
using (var client = new WebClient())
{
var query = HttpUtility.ParseQueryString(string.Empty);
foreach (string key in formValues)
{
query[key] = this.Request.Form[key];
}
query["username"] = System.Web.Configuration.WebConfigurationManager.AppSettings["ApiUserName"];
query["password"] = System.Web.Configuration.WebConfigurationManager.AppSettings["ApiPassword"];
string apiurl = System.Web.Configuration.WebConfigurationManager.AppSettings["ApiURL"];
var url = new UriBuilder(apiurl);
url.Query = query.ToString();
try
{
string xml = client.DownloadString(url.ToString());
doc.LoadXml(xml);
status = doc.SelectSingleNode("/operation/operationstatus").InnerText;
message = doc.SelectSingleNode("/operation/message").InnerText;
}
catch (WebException ex)
{
ModelState.AddModelError(string.Empty, "Error occurred:" + ex.InnerException);
}
}
if (status.ToUpper() == "SUCCESS")
{
repository.InsertOrUpdateRack(rj.Rack, User.Identity.Name, rj.Resource.RESOURCEID);
repository.Save();
return RedirectToAction("Index");
}
else
{
ModelState.AddModelError(string.Empty, message.ToString());
}
}
}
catch (DbUpdateConcurrencyException ex)
{
As shown in the above code I will not do a repository.save() to update the object on our system, unless the API return a “success”.
But currently I am facing the following problem:-
If the API return a “success” but a concurrency exception occurred, then the API will update the object on the external system, but the object will not be updated on our system?
So is there a way to handle this situation?
There's no easy way to solve this situation. One way to handle it would be to ask the designers of the external API expose a method allowing to commit the transaction done in a previous call. Basically your first call will make modifications to the external system but with some boolean flag indicating that those changes are still pending. Then you update your system and in case of success you would call the external API to flag the data from pending to valid.
If you have no control over the external API and it makes the changes to the data from the first call irreversible, then I am afraid that you do not have much choices left. You might remember the state of the object you are modifying on the external system before calling the API and in case of an exception on your system, revert back to the previous state by calling the API with the previous values.

Resources