I am currently working out the Microsoft Graph tutorial with C# .Net Core, and in the process I came across the following C#-method for Subscription:
[HttpGet]
public async Task<ActionResult<string>> Get()
{
var graphServiceClient = GetGraphClient();
var sub = new Microsoft.Graph.Subscription();
sub.ChangeType = "updated";
sub.NotificationUrl = config.Ngrok + "/api/notifications";
sub.Resource = "/users";
sub.ExpirationDateTime = DateTime.UtcNow.AddMinutes(15);
sub.ClientState = "SecretClientState";
var newSubscription = await graphServiceClient
.Subscriptions
.Request()
.AddAsync(sub);
Subscriptions[newSubscription.Id] = newSubscription;
if (subscriptionTimer == null)
{
subscriptionTimer = new Timer(CheckSubscriptions, null, 5000, 15000);
}
return $"Subscribed. Id: {newSubscription.Id}, Expiration: {newSubscription.ExpirationDateTime}";
}
and wanted to know how I can change it for sharepoint lists instead of users.
If I change it to /sites/{site-id} or similar it does not work. (see sub.Resource)
Github-Link: MS Repo
Microsoft Graph API uses a webhook mechanism to deliver change notifications to clients. Using the Microsoft Graph API, an app can subscribe to changes for list under a SharePoint site.
Resource Path - Changes to content within the list:
/sites/{id}/lists/{id}
For details round how to subscribe to and handle incoming notifications, see Set up notifications for changes in user data
Also make sure you check necessary permissions needed here.
I found the solution myself with the sub.Resource: /sites/{site-id}/lists/{list-id}
Related
So I want to receive a notification, when a call is happening callRecord (/communications/callRecords) so I grabed myself this example and changed the task function to this:
public async Task<ActionResult<string>> Get()
{
var graphServiceClient = GetGraphClient();
var sub = new Microsoft.Graph.Subscription();
sub.ChangeType = "created";
sub.NotificationUrl = config.Ngrok + "/api/notifications";
sub.Resource = "/communications/callRecords";
sub.ExpirationDateTime = DateTime.UtcNow.AddMinutes(5);
var newSubscription = await graphServiceClient
.Subscriptions
.Request()
.AddAsync(sub);
Subscriptions[newSubscription.Id] = newSubscription;
if (subscriptionTimer == null)
{
subscriptionTimer = new Timer(CheckSubscriptions, null, 5000, 15000);
}
return $"Subscribed. Id: {newSubscription.Id}, Expiration: {newSubscription.ExpirationDateTime}";
}
I also added the graph api permission CallRecords.Read.All to my app. Beforehand I testet the example with the updated users notification and it worked fine. But now it won't trigger the notification for a call.
Same here, everything worked yesterday but today my webhook endpoint didn't even trigger once.
I think there might be an issue on Microsoft's side. I follow this issue here of someone who has the same problem as us.
UPDATE: Someone from Microsoft answered in the linked Github Issue:
There is currently an ongoing issue.
The related post is TM220340 in the M365 Admin Center.
So they're confirming that the issue is on their end.
I want to link the existing work items which are already created in a project under Azure DevOps by writing a code or program in C#, so is there any kind of API or SDK which can be used to link the work items programmatically?
The Workitems can be of any type i.e.
Bug
User Story
Issue
Task etc.
The linking between the Workitems can also be of any type i.e. Relational, Parent-Child, etc.
Recently I referred to this link for my problem.
The link contains issue very much related and similar to mine, however it is not working as expected when I tried it.
Given:
//int relatedId = ...
//int id = ...
//CancellationToken token = ...
//string organization = ...
//string projectName = ...
//WorkItemTrackingHttpClient azureClient = ...
Create a JsonPatchDocument as below:
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Add(
new JsonPatchOperation
{
From = null,
Operation = Operation.Add,
Path = "/relations/-",
Value = new {
rel = "System.LinkTypes.Related",
url = $"https://dev.azure.com/{organization}/{projectName}/_workitems/edit/{relatedId}",
attributes = new
{
comment = $"Created programmatically on {DateTime.Now}."
}
}
}
);
and call this async method of Azure DevOps SDK:
await azureClient.UpdateWorkItemAsync(patchDoc, id, false, true, true, WorkItemExpand.All, cancellationToken: token);
In the case above we created a related link using System.LinkTypes.Related referenceName.
For a full link types reference guide in Azure DevOps refer to this so's question or this microsoft's doc.
For the Microsoft Bot framework chatbot application that I am working on, I have configured the "Bot Channel Registration" and have hosted it on Azure.
One of the scenarios expects the user to record a video on skype and send it as an answer. I have an Azure function that saves the recorded video from skype to the Azure Storage account.
The issue I am encountering is, When I record a video on skype ()via Video Messaging option.
To gain access to the uploaded video from skype, I am providing appropriate bearer token along with the above mentioned URL but failing to get access to it.
Though the file that is uploaded from skype to the Queue (Azure function Queue triggers), the accessibility to this file is denied.
Assuming the latest patches would help, I updated all the references to .NET core 3.0.1 as of today. Looking forward to the desired approach to resolve this.
Note: This issue is only happening in "Skype for Desktop" version.
Below is the code block for your reference.
private static async Task<HttpResponseMessage> RequestFile(string contentUrl, ILogger logger, string serviceUrl)
{
var credentials = DIContainer.Instance.GetService<MicrosoftAppCredentials>();
var token = await credentials.GetTokenAsync();
using (var connectorClient = new ConnectorClient(new Uri(serviceUrl), credentials.MicrosoftAppId, credentials.MicrosoftAppPassword))
{
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/octet-stream"));
var test = await httpClient.GetAsync(uri, HttpCompletionOption.ResponseContentRead);
return test;
}
}
}
Adding more code snippets:
private async Task<(string, string)> TrySaveAndGetContentUrl(IMessageActivity activity, string user)
{
var attachments = activity.Attachments;
if (attachments?.Any() ?? false)
{
var video = attachments.First();
return (await _attachmentsService.Save(video, user), video.ContentUrl);
}
return (null, null);
}
///_attachmentsService.Save method implementation
public async Task<string> Save(Attachment attachment, string user)
{
_logger.LogInformation("Enqueue save command. {#Attachment}", attachment);
var blobName = $"{user}/{Guid.NewGuid().ToString()}-{attachment.Name}";
var blob = _cloudBlobContainer.GetBlockBlobReference(blobName);
await EnqueueSaveCommand(attachment.ContentUrl, blobName, user);
return blob.Uri.ToString();
}
Please refer the below code block to save the attachments to Azure blob.
private async Task EnqueueSaveCommand(string contentUrl, string blobName, string user)
{
var queue = _queueClient.GetQueueReference(RouteNames.MediaAttachmentQueue); //RouteNames.MediaAttachmentQueue is "media-attachment-queue"
await queue.CreateIfNotExistsAsync();
var serializedMessage = JsonConvert.SerializeObject(new SaveMediaAttachmentCommand
{
FromUrl = contentUrl,
AttachmentName = blobName,
UserName = "userid#gmail.com",
});
var queueMessage = new CloudQueueMessage(serializedMessage);
await queue.AddMessageAsync(queueMessage);
}
Please suggest.
The Skype channel configuration contains the following message:
As of October 31, 2019 the Skype channel no longer accepts new Bot publishing requests. This means that you can continue to develop bots using the Skype channel, but your bot will be limited to 100 users. You will not be able to publish your bot to a larger audience. Current Skype bots will continue to run uninterrupted. Learn more
Many Skype features have been deprecated. If it was ever possible to send a video to a bot over Skype, it may not be possible any longer. It's recommended that you switch to other channels like Direct Line and Microsoft Teams.
I am getting an issue when creating subscription.
My steps are:
Register app at https://apps.dev.microsoft.com
Update permissions
Read Mail and User's info
Then update code, do same steps at https://learn.microsoft.com/en-us/outlook/rest/dotnet-tutorial
After login, I can get access token
Screen after login
Then I try to create a subscription for Inbox
var newSub = new Subscription
{
Resource = "me/mailFolders{'Inbox'}/messages",
ChangeType = "created,updated",
NotificationUrl = notificationUrl,
ClientState = clientState,
ExpirationDateTime = DateTime.Now.AddMinutes(15)
};
var result = await graphClient.Subscriptions.Request().AddAsync(newSub);
Implement for notification in notification URL - I can get validation token and return in plain text.
public async Task<ActionResult> Listen()
{
if (Request.QueryString["validationToken"] != null)
{
var token = Request.QueryString["validationToken"];
return Content(token, "plain/text");
}
}
But I always get this error.
Is there anyone know problem?
You must expose a public HTTPS endpoint to create a subscription and receive notifications from Microsoft Graph.
I would like to have some kind of people picker functionality with auto complete features in my asp.net mvc 5 app to search for a user in a specific Azure AD group. It's a demo "todo app" that allows to assign a todo to a user that is member of a the group.
I tried with both the Graph API directly and the Azure Graph Client library but I don't seem to find a way to achieve what I want. The graph api allows to get the members of a group but adding filter "startswith" fails as when adding the filter the api returns only directory object which don't include for example DisplayName property... the client library doesn't help much either except for the batch functionality which offers a way but with a lot of overhead... I then would have to get a filtered resultset of user regardless of group membership (using User List stuff in the api), all members of the group and then fish out using Linq the correct result set.... would work fine for dev/testing but in production with a couple of hundred users this would be insane...
Any ideas or suggestions would be much appreciated. Thanks!
EDIT
Below my code that is called from client side Javascript to search for user;
AccessGroupId is the Azure AD group used to authorize users. Only
members of this group can access the web app which I handle in custom
OWin Middleware
The method is intented to be used to find a user in that group
Code works fine as below only there is no filtering applied which is the intentaion with the input parameter pre (which comes from a textbox in the ui). I get all the members of the access group.
public async Task<JsonResult> FindUser(string pre)
{
string AccessGroupId = ConfigurationManager.AppSettings["AccessGroupId"];
AuthenticationContext authCtx = new AuthenticationContext(String.Format(CultureInfo.InvariantCulture, "{0}/{1}", SecurityConfiguration.LoginUrl, SecurityConfiguration.Tenant));
ClientCredential credential = new ClientCredential(SecurityConfiguration.ClientId, SecurityConfiguration.AppKey);
AuthenticationResult assertionCredential = await authCtx.AcquireTokenAsync(SecurityConfiguration.GraphUrl, credential);
var accessToken = assertionCredential.AccessToken;
var graphUrl = string.Format("https://graph.windows.net/mytenant.onmicrosoft.com/groups/{0}/members?api-version=2013-11-08, AccessGroupId );
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, graphUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
HttpResponseMessage response = await client.SendAsync(request);
String responseString = await response.Content.ReadAsStringAsync();
JObject jsonReponse = JObject.Parse(responseString);
var l = from r in jsonReponse["value"].Children()
select new
{
UserObjectId = r["objectId"].ToString(),
UserPrincipalName = r["userPrincipalName"].ToString(),
DisplayName = r["displayName"].ToString()
};
//users = Newtonsoft.Json.JsonConvert.DeserializeObject<List<User>>(responseString);
return Json(l, JsonRequestBehavior.AllowGet);
}
When I add a filter to the same api call instead of returning the members (users, groups and/or contacts), it returns directory objects (that doesn't have displayName) which are not really usefull in the above code, unless I would query the api again (in batch) to retrieve the users displayname but that looks like a lot of overhead to me.
var graphUrl = string.Format("https://graph.windows.net/mytenant.onmicrosoft.com/groups/{0}/members?api-version=2013-11-08&$filter=startswith(displayName,'{1}')", AccessGroupId, pre);
I'd highlight two possible approaches:
Execute requests to Graph API using a custom JS library.
You'd need still need to care for accesstokens and have a look at ADAL.js
A sample app (not finalized as of this writing) available at:
AzureADSamples WebApp-GroupClaims-DotNet
Have a look at AadPickerLibrary.js
Try using ActiveDirectoryClient
It would look something like:
public async Task<JsonResult> FindUser(string pre) {
ActiveDirectoryClient client = AADHelper.GetActiveDirectoryClient();
IPagedCollection<IUser> pagedCollection = await client.Users.Where(u => u.UserPrincipalName.StartsWith(pre, StringComparison.CurrentCultureIgnoreCase)).ExecuteAsync();
if (pagedCollection != null)
{
do
{
List<IUser> usersList = pagedCollection.CurrentPage.ToList();
foreach (IUser user in usersList)
{
userList.Add((User)user);
}
pagedCollection = await pagedCollection.GetNextPageAsync();
} while (pagedCollection != null);
}
return Json(userList, JsonRequestBehavior.AllowGet);
}
More detailed sample is available at:
AzureADSamples WebApp-GraphAPI-DotNet