How to get WorkItem Id when receiving the result? - autodesk-forge

Let's say I post a workitem to Revit engine using the following code
var response = await _flurlClient.Request("https://developer.api.autodesk.com/da/us-east/v3/workitems")
.PostJsonAsync(new
{
activityId = "ActivityID",
arguments = new
{
rvtFile = new
{
url = storageUrl,
Headers = new
{
Authorization = $"Bearer {accessToken}"
}
},
result = new
{
verb = "post",
url = $"{baseUrl}/api/result"
}
}
})
.ReceiveJson();
The response will contain the Id for this workitem. once the work item completes successfully, Forge calls my API endpoint with the output file. My endpoint is implemented as follows:
[HttpPost("Result")]
public async Task<IActionResult> PostResults()
{
await using (var fs = new FileStream("D://Test//l2.xlsx", FileMode.Create))
{
await Request.Body.CopyToAsync(fs);
}
return Ok();
}
The file is correctly saved but I can't get the associated workitem Id (not as a query parameter nor a header). This causes an issue, let's say I submitted two work items (A and B) when I receive a file how I can tell if it is related to work item A or B.

See answer.
Design automation allows you to specify a variable workitem.id in your output url. When your workitem completes we shall call this url with the variable expanded to the id of that workitem. This dynamic variable path allows you to determine the workitem id associated with that callback.
At your end you will need to implement Attribute Routing.
[HttpPost("Result/{workitemId}")]
public async Task<IActionResult> PostResults(string workitemId)
{
}
You may post the workitem with:
result = new
{
verb = "post",
url = $"{baseUrl}/api/result/$(workitem.id)"
}

Related

How to access JSON?

I am wrote API method, after calling that method , I got my response like
[
{
"spark_version": "7.6.x-scala2.12"
}
]
Now I want to have variable in my API method which store value 7.6.x-scala2.12.
API Controller method
[HttpGet]
public IActionResult GetTest(int ActivityId)
{
string StoredJson = "exec sp_GetJobJSONTest " +
"#ActivityId = " + ActivityId ;
var result = _context.Test.FromSqlRaw(StoredJson);
return Ok(result);
}
So how this variable should call on this response to get string stored in spark_version?
Thank you
As you have the JavaScript tag, here's how you'd do it in JS:
If you are able to call your API method, then you can just assign the response to a variable. For example, if you are calling it using the fetch API, it would look something like:
let apiResponse;
fetch('myApiUrl.com').then((response) => {
if (response.status === 200) {
apiResponse = response.body;
console.log('Response:', apiResponse[0]['spark_version']);
}
});
(I defined the variable outside the then to make it globally accessible)

Send a parameter with Http.GetJsonAsync

I have the following code in a Blazor app that calls an API and retrieved a list of Items based on the user id parameter.
The Http.GetJsonAsync does not seem to allow me to enter a parameter. How do I specify the userid variable as the parameter?
Calling the API
protected override async Task OnInitializedAsync()
{
userid = await localStorage.GetItemAsync<string>("userid");
myTrades = await Http.GetJsonAsync<ItemForTrade[]>("api/Trading/GetTradesForUser");
}
The API code
[Authorize]
[HttpGet("[action]")]
public async Task<List<ItemForTrade>> GetTradesForUser(string userid)
{
return await tradingService.GetTradesForUser(userid);
}
HttpGet passes parameters through the URL,so it's as easy as adding ?userid=value to the URL like this:
myTrades = await Http.GetJsonAsync<ItemForTrade[]>("api/Trading/GetTradesForUser?userid=" + userid);

On dropdown select viewbag data get changed but the view displays old data

My drop down where I am getting my groupId
<select id="GroupDropdown" name="GroupDropdown" onchange="Filteration();" class="form-control"></select>
MY Javascript ajax
function Filteration()
{
var groupid = document.getElementById("GroupDropdown").value;
var subgroupid = document.getElementById("SubGroupDropdown").value;
var locationid = document.getElementById("LocationID").value;
var propertyname = document.getElementById("txtPropertyName").value;
var propertydescription = document.getElementById("Description").value;
$.ajax({
type: 'GET',
contentType: "application/json;charset=utf-8",
url: "/Home/Index",
data: { 'GroupId': groupid, 'SubGroupId': subgroupid, 'LocationId': locationid, 'PropertyName': propertyname, 'Description': propertydescription},
dataType: "json",
context: this,
success: function (data) {
console.log(data);
},
error: function (request) {
}
});
}
Hi in my controller I had written a code which every time I get new data according to GroupId and other data pass.
public ActionResult Index(string GroupId, string SubGroupId, string LocationId, string PropertyName, string Description)
{
using (SqlConnection con = new SqlConnection(constr))
{
using (SqlCommand cmd = new SqlCommand("InsUpsDelTbl_Property"))
{
try
{
DataSet GetData = new DataSet();
cmd.Connection = con;
con.Open();
cmd.Parameters.AddWithValue("#Operation", "GetPropertyDataFilteration");
cmd.Parameters.AddWithValue("#GroupID", GroupId);
cmd.Parameters.AddWithValue("#SubGroupID", SubGroupId);
cmd.Parameters.AddWithValue("#LocationID", LocationId);
cmd.Parameters.AddWithValue("#Title", PropertyName);
cmd.Parameters.AddWithValue("#Description", Description);
cmd.CommandType = CommandType.StoredProcedure;
SqlDataAdapter ad = new SqlDataAdapter(cmd);
ad.Fill(GetData);
ViewBag.tblProperty = GetData.Tables[1];
con.Close();
}
catch (Exception ex)
{ throw ex; }
}
}
return View();
}
Here in above code on basis of groupid, subgroupid the data of ViewBag.tblProperty get changed but the data displaying on view is not changing. The view is displaying same data retrieved initially from viewbag.tblProperty when page get loads for the 1st time.
My view page code
#foreach (System.Data.DataRow dr in ViewBag.tblProperty.Rows)
{
<p><b>Group:</b> #dr["GroupName"].ToString()</p>
<p><b>SubGroup:</b> #dr["SubGroupName"].ToString()</p>
<p><b>Location:</b> #dr["LocationName"].ToString()</p>
<p><b>Age:</b> #dr["Age"].ToString() Years</p>
}
initially on page load event the group id, subgroup id is used to pass as empty so at that time 4 data used to get retrieved from database and used to get that data bind in viewbag.tblProperty but after changing of group id, subgroup id data only 2 data gets retrieved from database and viewbag.tblProperty again get bind with that data but on view page same old record with 4 data is used to display.
How can I change the view on basis of data retrieved from database
When you change your dropdown, you are calling the Filteration javascript method which does an ajax call. In your action method, you are setting the viewbag and returning the view. But your original razor view code was already executed and the output of which you are currently seeing in your browser).
You have 2 options.
1. Do not use ajax call. Reload the GET action
So instead of making the ajax call, you make a normal HTTP request to the GET action method with the selected values as querystring params. With your current action method code, it sets the viewbag data and return the view. When razor executes the code it will render the new data in the viewBag.
function Filteration()
{
var groupid = $("#GroupDropdown").val();
var subgroupid = $("#SubGroupDropdown").val();
var locationid = $("#LocationID").val();
var propertyname = $("#txtPropertyName").val();
var propertydescription = $("#Description").val();
var baseUrl="/Home/Index?groupId="+groupId+"&subgroupid="+subgroupid+
"&locationId="+locationid+"&propertyname="+propertyname+
"&propertydescription"=propertydescription ;
window.location.href=baseUrl;
}
2. Use ajax, but have the action method return partial view result.
Have your action method return a partial view result for ajax call. So create a partial view and return that when the request is an ajax call and update the DOM of current page when you receive the successful response from the ajax call.
So create a partial view called ResultList and it will have this code
#foreach (System.Data.DataRow dr in ViewBag.tblProperty.Rows)
{
<p><b>Group:</b> #dr["GroupName"].ToString()</p>
<p><b>SubGroup:</b> #dr["SubGroupName"].ToString()</p>
<p><b>Location:</b> #dr["LocationName"].ToString()</p>
<p><b>Age:</b> #dr["Age"].ToString() Years</p>
}
and your action method return this partial view
if(Request.IsAjaxRequest())
{
return PartialView("ResultList");
}
return View();
Now go to the main view and replace the loop code with a call to the partial and wrap it inside a container div.
<div id="results">
#Html.Partial("ResultList")
</div>
Now on your ajax call's success/done event, update the content of this container div with the results coming from the ajax call.
success: function (data) {
$("#results").html(data);
},

Submitting File and Json data to webapi from HttpClient

I want to send file and json data from HttpClient to web api server.
I cant seem to access the json in the server via the payload, only as a json var.
public class RegulationFilesController : BaseApiController
{
public void PostFile(RegulationFileDto dto)
{
//the dto is null here
}
}
here is the client:
using (var client = new HttpClient())
{
using (var content = new MultipartFormDataContent())
{
client.BaseAddress = new Uri(ConfigurationManager.AppSettings["ApiHost"]);
content.Add(new StreamContent(File.OpenRead(#"C:\\Chair.png")), "Chair", "Chair.png");
var parameters = new RegulationFileDto
{
ExternalAccountId = "1234",
};
JavaScriptSerializer serializer = new JavaScriptSerializer();
content.Add(new StringContent(serializer.Serialize(parameters), Encoding.UTF8, "application/json"));
var resTask = client.PostAsync("api/RegulationFiles", content); //?ApiKey=24Option_key
resTask.Wait();
resTask.ContinueWith(async responseTask =>
{
var res = await responseTask.Result.Content.ReadAsStringAsync();
}
);
}
}
this example will work:HttpClient Multipart Form Post in C#
but only via the form-data and not payload.
Can you please suggest how to access the file and the submitted json And the file at the same request?
Thanks
I have tried many different ways to submit both file data and metadata and this is the best approach I have found:
Don't use MultipartFormDataContent, use only StreamContent for the file data. This way you can stream the file upload so you don't take up too much RAM on the server. MultipartFormDataContent requires you to load the entire request into memory and then save the files to a local storage somewhere. By streaming, you also have the benefit of copying the stream into other locations such as an Azure storage container.
This solves the issue of the binary data, and now for the metadata. For this, use a custom header and serialize your JSON into that. Your controller can read the custom header and deserialize it as your metadata dto. There is a size limit to headers, see here (8-16KB), which is a large amount of data. If you need more space, you could do two separate requests, one to POST the minimum need, and then a PATCH to update any properties that needed more than a header could fit.
Sample code:
public class RegulationFilesController : BaseApiController
{
public async Task<IHttpActionResult> Post()
{
var isMultipart = this.Request.Content.IsMimeMultipartContent();
if (isMultipart)
{
return this.BadRequest("Only binary uploads are accepted.");
}
var headerDto = this.GetJsonDataHeader<RegulationFileDto>();
if(headerDto == null)
{
return this.BadRequest("Missing X-JsonData header.");
}
using (var stream = await this.Request.Content.ReadAsStreamAsync())
{
if (stream == null || stream.Length == 0)
{
return this.BadRequest("Invalid binary data.");
}
//save stream to disk or copy to another stream
var model = new RegulationFile(headerDto);
//save your model to the database
var dto = new RegulationFileDto(model);
var uri = new Uri("NEW URI HERE");
return this.Created(uri, dto);
}
}
private T GetJsonDataHeader<T>()
{
IEnumerable<string> headerCollection;
if (!this.Request.Headers.TryGetValues("X-JsonData", out headerCollection))
{
return default(T);
}
var headerItems = headerCollection.ToList();
if (headerItems.Count() != 1)
{
return default(T);
}
var meta = headerItems.FirstOrDefault();
return !string.IsNullOrWhiteSpace(meta) ? JsonConvert.DeserializeObject<T>(meta) : default(T);
}
}

Not able to read nested properties from doPost(e)

I am trying to create a webservice using the Contentservice in Apps Script and doPost(e) function to interact with Google Apps AdminDirectory service
Here is the overview of my code. I saved this as my server and published it as a websapp
function doPost(e) {
var data = e.parameter;
var resourceType = data.resourceType;
var method = data.method;
var resource = data.resource;
var resourceParams = resource.parameters;
//other code to work with AdminDIrectory
// return ContentService.createTextOutput(myoutputdata).setMimeType(ContentService.MimeType.JSON);
}
In my client code which I wrote using Apps Script to test the webservice
function test() {
var jsonData = {
authKey : 'HwZMVe3ZCGuPOhTSmdcfOqsl12345678',
resourceType : 'user',
method : 'get',
resource : {
parameters : {
userKey : 'waqar.ahmad#mydomain.com'
}
}
}
var url = 'https://script.google.com/macros/s/xyzabc12345678_wE3CQV06APje6497dyI7Hh-mQMUFM0pYDrk/exec';
var params = {
method : 'POST',
payload : jsonData
}
var resp = UrlFetchApp.fetch(url, params).getContentText();
Logger.log(resp);
}
Now when I try to read e.parameter.resource.parameters on server side, it gives error and shows that e.parameter.resource is string type.
How I can read nested objects on server side? It seems, it is recognizing only first level parameters.
Using
function doPost(e) {
return ContentService.createTextOutput(JSON.stringify(e.parameter)).setMimeType(ContentService.MimeType.JSON);
You get the response
"{"authKey":"HwZMVe3ZCGuPOhTSmdcfOqsl12345678","resource":"{parameters={userKey=waqar.ahmad#mydomain.com}}","method":"get","resourceType":"user"}"
so it looks like it is flattening any nests into a string. In the comments of a similar problem it is noted that the documentation for UrlFetchApp permits the payload to be "It can be a String, a byte array, or a JavaScript key/value map", but I assume this doesn't extend to nested key/value maps.
As noted in the other answer the solution would be to stringify the payload e.g.
var params = {
method : 'POST',
payload : {'data':JSON.stringify(jsonData)}
};
I had problems handling the payload just as a string which is why I used a key value
On the server side script you can handle with
function doPost(e) {
var data = JSON.parse(e.parameter.data);
var userKey = data.resource.parameters.userKey;
...
}
Maybe it arrives as JSON string? If that's the case you'd have to parse it back-end, something like:
resourceObj = JSON_PARSE_METHOD(e.parameter.resource);
for example in PHP this would be
$resourceObj = json_decode(e->parameter->resource);
You could find out if it's a JSON string by printing its value (or debugging it) on the backend.
Hope this helps. Cheers