I have been working on a requirement, i.e. when a bug is created/inprogress in TFS post a HTTP call to Slack (third party collaboration tool).
When a bug is closed post one more HTTP call to Slack.
I had implemented TFS server side plugin, unfortunately we don't have complete access to TFS and cannot implement. So, planning to implement Webapi and host it (say in Docker container) and whenever bug created / closed event happens in TFS it should post HTTP call.
I have created a simple console app with a method and it's working fine.
any sample code or thoughts to convert it to web api?
if I host, can it monitor TFS events and posts some HTTP calls?
public class GetWI
{
static void Main(string[] args)
{
GetWI ex = new GetWI();
ex.GetWorkItemsByWiql();
}
public void GetWorkItemsByWiql()
{
string _personalAccessToken = "xxxx";
string _credentials = Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(string.Format("{0}:{1}", "", _personalAccessToken)));
//this is needed because we want to create a project scoped query
string project = "Agileportfolio";
//create wiql object
var wiql = new
{
query = "Select [State], [Title] " +
"From WorkItems " +
"Where [Work Item Type] = 'Bug' " +
"And [System.TeamProject] = '" + project + "' " +
"And [System.State] = 'New' " +
"Order By [State] Asc, [Changed Date] Desc"
};
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("https://test.visualstudio.com");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", _credentials);
//serialize the wiql object into a json string
var postValue = new StringContent(JsonConvert.SerializeObject(wiql), Encoding.UTF8, "application/json"); //mediaType needs to be application/json for a post call
var method = new HttpMethod("POST");
var httpRequestMessage = new HttpRequestMessage(method, "https://abrahamdhanyaraj.visualstudio.com/_apis/wit/wiql?api-version=2.2") { Content = postValue };
var httpResponseMessage = client.SendAsync(httpRequestMessage).Result;
if (httpResponseMessage.IsSuccessStatusCode)
{
WorkItemQueryResult workItemQueryResult = httpResponseMessage.Content.ReadAsAsync<WorkItemQueryResult>().Result;
//now that we have a bunch of work items, build a list of id's so we can get details
var builder = new System.Text.StringBuilder();
foreach (var item in workItemQueryResult.WorkItems)
{
builder.Append(item.Id.ToString()).Append(",");
}
//clean up string of id's
string ids = builder.ToString().TrimEnd(new char[] { ',' });
HttpResponseMessage getWorkItemsHttpResponse = client.GetAsync("_apis/wit/workitems?ids=" + ids + "&fields=System.Id,System.Title,System.State&asOf=" + workItemQueryResult.AsOf + "&api-version=2.2").Result;
if (getWorkItemsHttpResponse.IsSuccessStatusCode)
{
var result = getWorkItemsHttpResponse.Content.ReadAsStringAsync().Result;
//Read title
}
}
// Create Channel
string name = "xyzz3";
var payload = new
{
token = "xoxp-291239704800-292962676087-297314229698-a80e720d98e443c8afb0c4cb2c09e745",
name = "xyzz3",
};
var serializedPayload = JsonConvert.SerializeObject(payload);
var response = client.PostAsync("https://slack.com/api/channels.create" + "?token=test&name=" + name + "&pretty=1",
new StringContent(serializedPayload, Encoding.UTF8, "application/json")).Result;
if (response.IsSuccessStatusCode)
{
dynamic content = JsonConvert.DeserializeObject(
response.Content.ReadAsStringAsync()
.Result);
}
}
}
I use wcf service to listen events from TFS. You may find my project here: https://github.com/ashamrai/tfevents
For wcf service:
Update your ServiceName.svc file and add:
Factory="System.ServiceModel.Activation.WebServiceHostFactory"
Create web method to use json:
[OperationContract]
[WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Bare)]
void WorkItemChangedEvent(Stream EventData);
Convert stream with Newtonsoft.Json to get information about event and work item:
StreamReader _reader = new StreamReader(pEventData, Encoding.UTF8);
string _eventStr = _reader.ReadToEnd();
WorkItemEventCore _wieventcorre = JsonConvert.DeserializeObject(_eventStr);
Then you have to create the subscription with url "http://host:port/service.svc/webmethod": https://learn.microsoft.com/en-us/vsts/service-hooks/services/webhooks
Instead of using a query and manually polling Visual Studio Team Services (VSTS), you can use a concept called WebHooks. You configure a WebHook in VSTS to listen for events and send these to a public endpoint. One event type is for Work Items. The endpoint can be any type of public endpoint, for example an Azure Function.
If the only thing you want to do is post the events to Slack, it's even easier because that's a standard integration point: Slack with VSTS.
This is much easier then using a server side plugin or writing your own Web API.
Related
As part of a user action, we are using the MS Graph Java SDK to first list all permissions of a file, then iterating over the list of permissions to delete each one individually. This seems to have some performance issues. We were wondering if there is any way to batch the calls using the IGraphServiceClient.
Relevant APIs used:
https://learn.microsoft.com/en-us/graph/api/driveitem-list-permissions?view=graph-rest-1.0&tabs=http
https://learn.microsoft.com/en-us/graph/api/permission-delete?view=graph-rest-1.0&tabs=http
You can make batch requests.
1. Create MSBatch Request Steps (examples below)
Request requestGetMe = new Request.Builder().url("https://graph.microsoft.com/v1.0/me/").build();
List<String> arrayOfDependsOnIdsGetMe = null;
MSBatchRequestStep stepGetMe = new MSBatchRequestStep("1", requestGetMe, arrayOfDependsOnIdsGetMe);
Request requestGetMePlannerTasks = new Request.Builder().url("https://graph.microsoft.com/v1.0/me/planner/tasks").build();
List<String> arrayOfDependsOnIdsGetMePlannerTasks = Arrays.asList("1");
MSBatchRequestStep stepMePlannerTasks = new MSBatchRequestStep("2", requestGetMePlannerTasks, arrayOfDependsOnIdsGetMePlannerTasks);
String body = "{" +
"\"displayName\": \"My Notebook\"" +
"}";
RequestBody postBody = RequestBody.create(MediaType.parse("application/json"), body);
Request requestCreateNotebook = new Request
.Builder()
.addHeader("Content-Type", "application/json")
.url("https://graph.microsoft.com/v1.0/me/onenote/notebooks")
.post(postBody)
.build();
MSBatchRequestStep stepCreateNotebook = new MSBatchRequestStep("3", requestCreateNotebook, Arrays.asList("2"));
2. Create MSBatch Request Content and get content
List<MSBatchRequestStep> steps = Arrays.asList(stepGetMe, stepMePlannerTasks, stepCreateNotebook);
MSBatchRequestContent requestContent = new MSBatchRequestContent(steps);
String content = requestContent.getBatchRequestContent();
3. Make call to $batch endpoint
OkHttpClient client = HttpClients.createDefault(auth);
Request batchRequest = new Request
.Builder()
.url("https://graph.microsoft.com/v1.0/$batch")
.post(RequestBody.create(MediaType.parse("application/json"), content))
.build();
Response batchResponse = client.newCall(batchRequest).execute();
4. Create MSBatch Response Content
MSBatchResponseContent responseContent = new MSBatchResponseContent(batchResponse);
Response responseGetMe = responseContent.getResponseById("1");
// Use the response of each request
Have been working on creating a custom TFS Service Hook which 'll post (HTTP) notification to Slack.
My requirement is to POST a HTTP call to slack when a Bug workitem's status is changed to Inprogress . (I have Implemented the same with TFS Server side plugin .unfortunately , I had to go for Service Hook over Plugin)
I tried the below code with TFS 2017 onprem with PAT, unfortunately it was breaking. am I doing anything wrong ? I want my code to work in 2015.2 without PAT.
Can someone help please ?
public async static void CreateServiceHook(string collName, string projName, string projId, string AccessToken)
{
try
{
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", AccessToken);
string url = "https://" + collName + ".visualstudio.com/DefaultCollection/_apis/projects/" + projName + "?includecapabilities=true&api-version=1.0";
HttpRequestMessage req = new HttpRequestMessage(new HttpMethod("GET"), url);
var response = client.SendAsync(req).Result;
string contents = await response.Content.ReadAsStringAsync();
}
//create service hook
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", AccessToken);
var request = new
{
publisherId = "tfs",
eventType = "workitem.created",
consumerId = "webHooks",
consumerActionId = "httpRequest",
scope = "project",
publisherInputs = new {
// buildStatus = "",
projectId = projId
},
consumerInputs = new
{
url = "https://slack.api.com/services/XXXXXXXXXX
}
};
var response = client.PostAsync("https://" + collName + ".visualstudio.com/DefaultCollection/_apis/hooks/subscriptions/?api-version=1.0",
new StringContent(JsonConvert.SerializeObject(request).ToString(),
Encoding.UTF8, "application/json"))
.Result;
if (response.IsSuccessStatusCode)
{
dynamic content = JsonConvert.DeserializeObject(
response.Content.ReadAsStringAsync()
.Result);
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
TFS has default slack service hook which can post a message to a channel. So instead of using web hooks consumer, you can use slack consumer. And you can only achieve is posting messages to Slack when a Bug workitem's status is updated.
The API looks like below:
POST http://tfsserver:8080/tfs/DefaultCollection/_apis/hooks/subscriptions?api-version=3.2
Content-Type: application/json
{
"consumerActionId":"postMessageToChannel",
"consumerId":"slack",
"consumerInputs":{
"url":"https://hooks.slack.com/services/xxxxxx"},
"eventType":"workitem.updated",
"publisherId":"tfs",
"publisherInputs":{
"areaPath":"",
"workItemType":"Bug",
"changedFields":"System.State",
"projectId":"77e3c775-dc30-4354-xxxx-xxxxxxxxxxxx"},
"scope":1
}
Hello Office / SharePoint Developers,
I am working on a project based on the Office Developer Patterns and Practices Sample where a console application accesses a WebAPI which then access SharePoint Online as the logged in user: The sample is here: https://github.com/SharePoint/PnP/tree/master/Samples/AzureAD.WebAPI.SPOnline
Question:
When I attempt to upload a file to the document library, I get an error 401 "The remote server returned an error: (401) Unauthorized".
The file read options such as listing the documents and querying for documents works fine.
The user credentials I supply are of a user that is the site collection admin, owner, and global admin on the tenant.
I get an access token from SharePoint online based on the token I get in the native client.
public string GetAccessToken(string accessToken)
{
string clientID = _clientId;
string clientSecret = _clientSecret;
var appCred = new ClientCredential(clientID, clientSecret);
var authContext = new Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext("https://login.windows.net/common");
AuthenticationResult authResult = authContext.AcquireToken(new Uri(_spoUrl).GetLeftPart(UriPartial.Authority), appCred, new UserAssertion(accessToken));
return authResult.AccessToken;
}
This is the CSOM that uploads the file. I know it works as I can paste it into a console app and using (SharePointOnlineCredentails) it works fine.
string newToken = _tokenSvc.GetAccessToken(accessToken);
using (ClientContext cli = new ClientContext(_spoUrl))
{
cli.ExecutingWebRequest += (s, e) => e.WebRequestExecutor.WebRequest.Headers.Add("Authorization", "Bearer " + newToken);
cli.AuthenticationMode = ClientAuthenticationMode.Default;
using (var fs = new FileStream(#"c:\test.txt", FileMode.Open))
{
var fi = new FileInfo("test.txt");
var list = cli.Web.Lists.GetByTitle("documents");
cli.Load(list.RootFolder);
cli.ExecuteQuery();
var fileUrl = String.Format("{0}/{1}", list.RootFolder.ServerRelativeUrl, fi.Name);
Microsoft.SharePoint.Client.File.SaveBinaryDirect(cli, fileUrl, fs, true);
Web web = cli.Web;
Microsoft.SharePoint.Client.File newFile = web.GetFileByServerRelativeUrl(fileUrl);
cli.Load(newFile);
cli.ExecuteQuery();
ListItem item = newFile.ListItemAllFields;
item["CRUID"] = "CRU_1337";
item.Update();
cli.ExecuteQuery();
}
}...
TLDR: I get 401 on file upload. Reads work. I am using CSOM with an access token that is supposed to be a webAPI on behalf of the logged in user.
I look forward to hearing your advice!
Chris
I am not sure whether we could upload/download files from SP using access tokens with CSOM now , see discussion here two years ago . But we could use sharepoint online rest api to upload files to sharepoint online , i tried below code and it works fine in the code sample AzureAD.WebAPI.SPOnline :
string sharePointUrl = ConfigurationManager.AppSettings["SharePointURL"];
string newToken = GetSharePointAccessToken(sharePointUrl, this.Request.Headers.Authorization.Parameter);
using (ClientContext cli = new ClientContext(sharePointUrl))
{
cli.AuthenticationMode = ClientAuthenticationMode.Default;
cli.ExecutingWebRequest += (s, e) => e.WebRequestExecutor.WebRequest.Headers.Add("Authorization", "Bearer " + newToken);
cli.AuthenticationMode = ClientAuthenticationMode.Default;
byte[] bytefile = System.IO.File.ReadAllBytes(#"e:\log.txt");
HttpWebRequest endpointRequest = (HttpWebRequest)HttpWebRequest.Create("https://xxx.sharepoint.com/sites/xxx/" + "/_api/web/GetFolderByServerRelativeUrl('Shared%20Documents')/Files/add(url='log.txt',overwrite=true)");
endpointRequest.Method = "POST";
endpointRequest.Headers.Add("binaryStringRequestBody", "true");
endpointRequest.Headers.Add("Authorization", "Bearer " + newToken);
endpointRequest.GetRequestStream().Write(bytefile, 0, bytefile.Length);
HttpWebResponse endpointresponse = (HttpWebResponse)endpointRequest.GetResponse();
}
The code below is ended up being the solution to my question:
/* Beginning CSOM Magic */
using (ClientContext cli = new ClientContext(_spoUrl))
{
/* Adding authorization header */
cli.ExecutingWebRequest += (s, e) => e.WebRequestExecutor.WebRequest.Headers.Add("Authorization", "Bearer " + newToken);
cli.AuthenticationMode = ClientAuthenticationMode.Default;
//Get Document List
List documentsList = cli.Web.Lists.GetByTitle(_libraryName);
var fileCreationInformation = new FileCreationInformation();
//Assign to content byte[] i.e. documentStream
var data = System.IO.File.ReadAllBytes(#"c:\test.txt");
fileCreationInformation.Content = data;
//Allow owerwrite of document
fileCreationInformation.Overwrite = true;
//var siteURL = _spoUrl;
var documentListURL = "shared documents";
//var documentName = "/test.txt";
//Upload URL
fileCreationInformation.Url = string.Concat(_spoUrl,"/",documentListURL,"/",documentName);
Microsoft.SharePoint.Client.File uploadFile = documentsList.RootFolder.Files.Add(
fileCreationInformation);
//Update the metadata for a field having name "DocType"
uploadFile.ListItemAllFields["CRUID"] = cruId;
uploadFile.ListItemAllFields.Update();
cli.ExecuteQuery();
}
I am trying to get activity stream of my jira instance using the below api and it is not working , can anybody point me in the right direction ?
You should check this page out: https://developer.atlassian.com/docs/atlassian-platform-common-components/activity-streams/consuming-an-activity-streams-feed
The Atom feed of the activity stream works well only if you also log in in your feed reader.
Here is an example of consuming the activity stream through the Jira API using Basic Authentication. This is in C#, but the basic pattern can be applied anywhere:
string myJiraUsername = "username";
string myJiraPassword = "password"; //or API token
string authenticationHeaderValue = Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(myJiraUsername + ":" + myJiraPassword));
System.Net.Http.HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authenticationHeaderValue);
Task<HttpResponseMessage> task = client.GetAsync("https://mycompany.atlassian.net/activity");
task.Wait();
HttpResponseMessage response = task.Result;
string resultOfApiCall = "";
if (response.IsSuccessStatusCode)
{
resultOfApiCall = response.Content.ReadAsStringAsync().Result;
Console.WriteLine("This was returned by your API request:\n" + resultOfApiCall);
}
I've created a project in Big Query and within API Access pane, I've created Service Account so that i can make Big Query API accessible through windows application on behalf of user interaction. Since i am new to using and accessing Google API, I want to know the basic steps so that i can access Big Query via Windows Service.
There are some .NET code samples here using web-based OAuth flows:
Google BigQuery with .NET documentation/ samples
For an example from another dev using service accounts in .NET, see this:
Google OAuth2 Service Account Access Token Request gives 'Invalid Request' Response
These manually implement service accounts on top of the .NET library. The .NET library has recently added native support for service accounts, though I haven't yet found an official example.
Here's an unofficial sample working with the Analytics API. It should be directly analogous to using service accounts with BigQuery:
How do I use a Service Account to Access the Google Analytics API V3 with .NET C#?
Finally here's the working code for Authentication process to Big Query API and retrieving records from Big Query table:-
I've created a class having a method of return type OAuth2Authenticator<AssertionFlowClient>.
internal class clsGetOAuth2Authentication
{
public OAuth2Authenticator<AssertionFlowClient> objGetOAuth2(string strPrivateFilePath, string strPrivateFilePassword, string strServiceAccEmailId,string strScope)
{
AuthorizationServerDescription objAuthServerDesc;
X509Certificate2 objKey;
AssertionFlowClient objClient;
OAuth2Authenticator<AssertionFlowClient> objAuth = null;
string ScopeUrl = "https://www.googleapis.com/auth/" + strScope;
string strSrvAccEmailId = strServiceAccEmailId;
string strKeyFile = strPrivateFilePath; //KeyFile: This is the physical path to the key file you downloaded when you created your Service Account.
string strKeyPassword = (strPrivateFilePassword != "") ? strPrivateFilePassword : "notasecret"; //key_pass: This is probably the password for all key files, but if you're given a different one, use that.
objAuthServerDesc = GoogleAuthenticationServer.Description; //objAuthServerDesc: Description of the server that will grant Authentiation.
objKey = new X509Certificate2(strKeyFile, strKeyPassword, X509KeyStorageFlags.Exportable); //objkey: Load up and decrypt the key.
objClient = new AssertionFlowClient(objAuthServerDesc, objKey) { ServiceAccountId = strSrvAccEmailId, Scope = ScopeUrl }; //objClient: Using the AssertionFlowClient, because we're logging in with our certificate.
objAuth = new OAuth2Authenticator<AssertionFlowClient>(objClient, AssertionFlowClient.GetState); //objAuth: Requesting Authentication.
return objAuth;
}
}
Now, another class calls the above method:-
clsGetOAuth2Authentication objOAuth2 = new clsGetOAuth2Authentication();
try
{
var objAuth = objOAuth2.objGetOAuth2(strKeyFile, strKeyPassword, strSrvAccEmailId, strScope); //Authentication data returned
objBQServ = new BigqueryService(objAuth); //Instantiate BigQueryService with credentials(objAuth) as its parameter
#region Retrieving Records:-
JobsResource j = objBQServ.Jobs;
QueryRequest qr = new QueryRequest();
qr.Query = strQuery;
DateTime dtmBegin = DateTime.UtcNow;
QueryResponse response = j.Query(qr, strProjId).Fetch();
DateTime dtmEnd = DateTime.UtcNow;
string strColHead = "";
foreach (var colHeaders in response.Schema.Fields)
{
strColHead += colHeaders.Name.ToString() + "\t";
}
Console.WriteLine(strColHead);
int intCount = 0;
foreach (TableRow row in response.Rows)
{
intCount += 1;
List<string> list = new List<string>();
foreach (var field in row.F)
{
list.Add(field.V);
}
Console.WriteLine(String.Join("\t", list));
}
TimeSpan tsElapsed = dtmEnd - dtmBegin;
Console.WriteLine("\n" + "Total no. of records:- " + intCount + ". Time taken:- " + tsElapsed);
#endregion
}
catch (Exception ex)
{
Console.WriteLine("Error Occured!" + "\n\n" + "Statement:- " + ex.Message.ToString() + "\n\n" + "Description:- " + ex.ToString() + "\n\n");
Console.WriteLine("\nPress enter to exit");
Console.ReadLine();
}