I currently have an Azure Function that I would like to have update a QnaMaker Knowledge Base every day or so. Currently everything is connected and working fine, however I can only send Qna Objects (qna pairs) and not urls to files on a website of mine. So in the example I provided below, while it should populate the KB with 2 questions and the file from the url, it only populates the questions.
Currently this is not giving me any kind of error, in fact the response code from my call to the KB comes back as 204. So it it getting through, but still not adding the file to the KB as it should.
NOTE: The file being imported in this example (alice-I.html) is a random one for this demonstration (not mine, for security), but the issue is the same. If I directly add this file to the QnaMaker from the KB site itself it works fine, but it won't update from the Azure Function Code.
Any insights into what is happening would be great.
Content Being Sent To Knowledge Base
string replace_kb = #"{
'qnaList': [
{
'id': 0,
'answer': 'A-1',
'source': 'Custom Editorial',
'questions': [
'Q-1'
],
'metadata': []
},
{
'id': 1,
'answer': 'A-2',
'source': 'Custom Editorial',
'questions': [
'Q-2'
],
'metadata': [
{
'name': 'category',
'value': 'api'
}
]
}
],
'files': [
{
'fileName': 'alice-I.html',
'fileUri': 'https://www.cs.cmu.edu/~rgs/alice-I.html'
}
]
}";
Code Sending Content To Knowledge Base
using (var clientF = new HttpClient())
using (var requestF = new HttpRequestMessage())
{
requestF.Method = HttpMethod.Put;
requestF.RequestUri = new Uri(<your-uri>);
requestF.Content = new StringContent(replace_kb, Encoding.UTF8, "application/json");
requestF.Headers.Add("Ocp-Apim-Subscription-Key", <your-key>);
var responseF = await clientF.SendAsync(requestF);
if (responseF.IsSuccessStatusCode)
{
log.LogInformation("{'result' : 'Success.'}");
log.LogInformation($"------------>{responseF}");
}
else
{
await responseF.Content.ReadAsStringAsync();
log.LogInformation($"------------>{responseF}");
}
}
So I still don't know how to get the above working, but I got it to work a different way. Basically I used the UpdateKbOperationDTO Class listed here: class
This still isn't the perfect solution, but it allows me to update my KB with files using code instead of the interface.
Below is my new code:
QnAMakerClient qnaC = new QnAMakerClient(new ApiKeyServiceClientCredentials(<subscription-key>)) { Endpoint = "https://<your-custom-domain>.cognitiveservices.azure.com"};
log.LogInformation("Delete-->Start");
List<string> toDelete = new List<string>();
toDelete.Add("<my-file>");
var updateDelete = await qnaC.Knowledgebase.UpdateAsync(kbId, new UpdateKbOperationDTO
{
// Create JSON of changes ///
Add = null,
Update = null,
Delete = new UpdateKbOperationDTODelete(null, toDelete)
});
log.LogInformation("Delete-->Done");
log.LogInformation("Add-->Start");
List<FileDTO> toAdd = new List<FileDTO>();
toAdd.Add(new FileDTO("<my-file>", "<url-to-file>"));
var updateAdd = await qnaC.Knowledgebase.UpdateAsync(kbId, new UpdateKbOperationDTO
{
// Create JSON of changes ///
Add = new UpdateKbOperationDTOAdd(null, null, toAdd),
Update = null,
Delete = null
});
log.LogInformation("Add-->Done");
Related
I'm struggling with creating a message from a device to the IotHub in the correct format.
I'm using the Azure Client SDK (Microsoft.Azure.Devices.Client)
For better understanding lets start with a small example, we have the following string:
var TableName = "table01";
var PartitionKey = "key01";
string messagePayload = $"{{\"tablename\":\"{TableName}\",\"partitionkey\":\"{PartitionKey}\"}}";
( Taken from the example Send device to cloud telemetry) we create an eventMessage
using var eventMessage = new Microsoft.Azure.Devices.Client.Message(Encoding.UTF8.GetBytes(messagePayload))
{
ContentEncoding = Encoding.UTF8.ToString(),
ContentType = "application/json"
};
And then send it to the Cloud:
Console.WriteLine(messagePayload);
await deviceClient.SendEventAsync(eventMessage);
Output from the writeline, which is what I wanted in the first place:
{"tablename":"table01","partitionkey":"key01"}
What I can see in the shell after following the answer about watching incoming IotHub Messages:
{
"event": {
"origin": "WinSensorTest",
"module": "",
"interface": "",
"component": "",
"payload": "{\"tablename\":\"table01\",\"partitionkey\":\"key01\"}"
}
}
The Problem is, that I want it to either look like the code below or completely without the "event" etc, just the string above.
{
"event":{
"origin":"WinSensorTest",
"module":"",
"interface":"",
"component":"",
"payload":{
"tablename":"table01",
"partitionkey":"key01"
}
}
}
Where did I go wrong, how can the payload be correct json format?
Edit:
I just tried the same in Java, with the same result. Why does this not work, or is the data seen in the shell not correctly parsed?
If you create a proper Json object first it works and also shows up correct in the shell - interestingly only for this c# project, I tried doing the same in Java on Android and the same wierd formatting stuff still happens even after making an object with gson.
For the solution:
class JsonMessage
{
public string tablename { get; set; }
public string partitionkey { get; set; }
}
And then Used JsonMessage and JsonConvert to get the desired payload.
JsonMessage newMsg = new JsonMessage()
{
tablename = "table01",
partitionkey = "key01",
};
string payload = JsonConvert.SerializeObject(newMsg);
using var eventMessage = new Microsoft.Azure.Devices.Client.Message(Encoding.UTF8.GetBytes(payload))
{
ContentEncoding = Encoding.UTF8.ToString(),
ContentType = "application/json"
};
You'd think this would be easy as falling off a log, but I'm stumped. I have an ASP.NET 3.1 core web api (using System.Text.Json):
[HttpPost]
public async Task<ActionResult> CreateAsync(JsonElement dzc)
Which in turn calls a service:
public async Task AddItemAsync(JsonElement dzc)
{
var id = dzc.GetProperty("id").GetString(); // these are both valid
var userid = dzc.GetProperty("userid").GetString();
await this._container.CreateItemAsync<JsonElement>(dzc, new PartitionKey(userid));
}
This results in: Message: {"Errors":["The input content is invalid because the required properties - 'id; ' - are missing"]}
Or if I get the raw text:
public async Task AddItemAsync(JsonElement dzc)
{
var id = dzc.GetProperty("id").GetString(); // these are all valid
var userid = dzc.GetProperty("userid").GetString();
var raw = dzc.GetRawText();
await this._container.CreateItemAsync<string>(raw, new PartitionKey(userid));
}
and raw is:
{
"id": "foozy",
"userid": "foozy",
"name": "Jayb",
"fc": {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"capacity": "10",
},
"geometry": {
"type": "Point",
"coordinates": [
-71.073283,
42.417500
]
}
}
]
}
}
This results in: Message: {"Errors":["One of the specified inputs is invalid"]}
All I'm really trying to do is shovel some Json directly from an API into CosmosDB without using C# classes. What is the magic incantation?
Short answer...you can't via CreateItemAsync. The documentation explicitly states that an id prop is required, which by definition does not exist on on either JsonElement or string.
A better option would be to use CreateItemStreamAsync which allows you to pass in the raw stream directly and appears to bypass an Id property requirement, per the example in the documentation.
using (Response response = await this.Container.CreateItemStreamAsync(partitionKey: new PartitionKey("streamPartitionKey"), streamPayload: stream))
{
using (Stream responseStream = await response.ContentStream)
{
//Read or do other operations with the stream
using (StreamReader streamReader = new StreamReader(responseStream))
{
string responseContentAsString = await streamReader.ReadToEndAsync();
}
}
}
Thanks David L, your suggestion worked great! Here's what I ended up with:
public async Task AddItemAsync(JsonElement dzc)
{
var id = dzc.GetProperty("id").GetString();
var userid = dzc.GetProperty("userid").GetString();
var raw = dzc.GetRawText();
byte[] bytes = Encoding.UTF8.GetBytes(raw);
using (var stream = new MemoryStream(bytes))
{
await this._container.CreateItemStreamAsync(stream, new PartitionKey(userid));
}
}
I'm curious if the copy can be avoided.
Below is error that I could not update refreshSchedule of the datasets:
{
"error": {
"code": "InvalidRequest",
"message": "Invalid NotifyOption value 'MailOnFailure' for app only owner requests"
}
}
Below is code to call it:
var datasets = await client.Datasets.GetDatasetsAsync(new Guid(_workspaceId));
var days = new List<Days?> { Days.Monday, Days.Tuesday, Days.Wednesday, Days.Thursday, Days.Friday, Days.Saturday, Days.Sunday };
var times = new List<string> { "00:00" };
var refreshSchedule = new RefreshSchedule(days, times, true, "UTC");
var id = "XXX";
await client.Datasets.TakeOverAsync(new Guid(_workspaceId), id);
var refreshRequest = new RefreshRequest(NotifyOption.NoNotification);
// refresh datasets
await client.Datasets.RefreshDatasetAsync(new Guid(_workspaceId), id, refreshRequest);
// Target: Update RefreshSchedule (Exception for calling this)
await client.Datasets.UpdateRefreshScheduleInGroupAsync(new Guid(_workspaceId), id, refreshSchedule);
Can you pls let me know how the app is consuming the endpoint - User Interventaion or Completeley done through AppOnly or using the master user Credential?
Alternatively, if you don't want the MailOnfailure, can you set up explicitly No Notification for the Refresh Schedule and Try ?
just modified your piece of code and presented below :
var refreshSchedule = new RefreshSchedule(days, times, true, "UTC", ScheduleNotifyOption.NoNotification);
The snippet :
var days = new List<Days?> { Days.Monday, Days.Tuesday, Days.Wednesday, Days.Thursday, Days.Friday, Days.Saturday, Days.Sunday };
var times = new List<string> { "00:00" };
//Fixed code
var refreshSchedule = new RefreshSchedule(days, times, true, "UTC", ScheduleNotifyOption.NoNotification);
await client.Datasets.UpdateRefreshScheduleInGroupAsync(WorkspaceId, datasets.Id, refreshSchedule);
UPDATE
When i used ServicePrincipal (without any UserIntervention/Master User Credential)
I was able to repro your issue
Error
Status: BadRequest (400) Response: {"error":{"code":"InvalidRequest","message":"Invalid NotifyOption
value 'MailOnFailure' for app only owner requests"}}
I was able to get past the error by making use of the ScheduleNotifyOption.NoNotification in the refreshSchedule mentioned above.
Or if i use the app through cred of a mailenabled account.
I want to send a POST request in c# and i need the following sent through
"jsonrpc": "2.0",
"id": "12345",
"method": "my method",
"params": {
"api_key": "my api key",
"preset_id": "my preset id"
}
I tried using
using (WebClient client = new WebClient ())
{
byte [] response =
client.UploadValues ("my url", new NameValueCollection ()
{
{ "jsonrpc", "2.0" },
{ "id", "12345"},
{ "method", "my method"},
{ "params", ""}
});
string result = System.Text.Encoding.UTF8.GetString (response);
}
But i couldnt make the params an array, Please help, Thank you
It appears that you are asking for the parameters to be in an array, but they are actually shown as a "subclass". If the values were in an array, they should have square brackets around them.
However, both results are easy to achieve using anonymous (or real) classes (which I much prefer over embedding the property names in quoted text (makes future modifications much easier to implement).
var parameters = new
{
api_key = "my api key",
preset_id = "my preset id"
};
var json = new
{
jsonrpc = "2.0",
id = "12345",
method = "my method",
#params = parameters
};
string sResult = (new System.Web.Script.Serialization.JavaScriptSerializer()).Serialize(json);
The above code will result in the same output that you have shown. If you want an actual array instead, you can change the parameters definition to:
var parameters = new NameValueCollection();
parameters.Add("api_key", "my api key");
parameters.Add("preset_id", "my preset id");
Note that I used the .Net framework json serializer (from System.Web.Extensions), but you can use the serializer of your choice (we generally use NewtonSoft's JsonConvert).
I want to attach a work item on tfs to a build. i am reading SVN logs which contain the work item number and try to update the actual build to attach them.
workitems.Add(workItemStore.GetWorkItem(workitemid));
buildDetail.Information.AddAssociatedWorkItems(workitems.ToArray());
When I try to hit buildDetail.Information.Save(); or buildDetail.Save();, I get an AccessDeniedException.
See another post.
So I wanted to try with REST...
After loads of pages on MSDN, I concluded there is no .NET Client Library that takes care of builds. It looks like my only option is to patch a json into the TFS:
PATCH https://{instance}/DefaultCollection/{project}/_apis/build/builds/{buildId}?api-version={version}
How do I add the workitems the right way?
EDIT: I've found an old post which mentioned a dll in the TFS namespace which has the same capabilities like the call from above. Unluckily, it's not refered in MSDN. Same problem here: no way to add workitems.
To spread the issue and adress it to MS: Patrick has created a post on uservoice
UPDATE:
I managed to link a build in the work item. Here is an approach in c#:
var json = new JsonPatchDocument
{
new JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/relations/-",
Value = new WorkItemRelation()
{
Rel = "ArtifactLink",
Url = build.Uri.ToString()
}
}
};
var client = new WebClient { UseDefaultCredentials = true };
client.Headers.Add(HttpRequestHeader.ContentType, "application/json-patch+json");
client.UploadData(
options.CollectionUri + "/_apis/wit/workitems/" + workitemid + "?api-version=1.0",
"PATCH",
Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(json)));
But there is still no direct binding in the build:
And the work items says unknown executable linktype
According to the message given in the workitem, I assume that I am using the false linktype. Does anybody have a reference for me, what types I am able to and should use?
URI UPDATE:
I am already using the mentioned uri:
MINOR SOLUTION:
I had to add the name "Build" to the Attributes of the patch. I still does not recognize it in the build itself but for now, I can work with the link as build type.
var json = new JsonPatchDocument
{
new JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/relations/-",
Value = new WorkItemRelation()
{
Rel = "ArtifactLink",
Url = build.Uri.ToString()
Attributes = new Dictionary<string, object>()
{
{ "name", "Build" },
{ "comment", build.Result.ToString() }
}
}
}
};
You could add a workitem to a build by updating the workitem to add a relation link to the build via Rest API. Refer to this link for details: Add a Link.
After you add a link to the build in the workitem, the workitem would be show in the build summary.
Following is the content sample of the body,
[
{
"op": "test",
"path": "/rev",
"value": "2"
},
{
"op": "add",
"path": "/relations/-",
"value":
{
"rel": "ArtifactLink",
"url": "vstfs:///Build/Build/12351"
}
}
]
Add code sample:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using System.Net.Http;
using System.Net.Http.Headers;
namespace AssociateWorkItemToBuild
{
class Program
{
static void Main(string[] args)
{
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(
ASCIIEncoding.ASCII.GetBytes(
string.Format("{0}:{1}", "username", "password"))));
string Url = "https://xxxxxx.visualstudio.com/_apis/wit/workitems/149?api-version=1.0";
StringBuilder body = new StringBuilder();
body.Append("[");
body.Append("{");
body.Append("\"op\":\"add\",");
body.Append(" \"path\":\"/relations/-\",");
body.Append("\"value\":");
body.Append("{");
body.Append("\"rel\": \"ArtifactLink\",");
body.Append("\"url\": \"vstfs:///Build/Build/12596\"");
body.Append("}");
body.Append("}");
body.Append("]");
var method = new HttpMethod("PATCH");
var request = new HttpRequestMessage(method, Url)
{
Content = new StringContent(body.ToString(), Encoding.UTF8,
"application/json-patch+json")
};
using (HttpResponseMessage response = client.SendAsync(request).Result)
{
response.EnsureSuccessStatusCode();
string responseBody = response.Content.ReadAsStringAsync().Result;
}
}
}
}
}
Update
Just as Eddie replied, you could add a workitem to a build by updating the workitem to add a relation link to the build via Rest API.
About LinkType there has been a clear demo in Eddie's answer. You need to use build uri.
The format needs to be vstfs:///Build/Build/8320
The BuildUri for the TFS Build tasks is a property that needs to be set so that those tasks can communicate with the server about the build they are performing actions for.
You can also use $env:BUILD_BUILDURI in a powershell script, more detail info and ways you can also refer this blog from MSDN: Build association with work Items in vNext Finally will get :