Assign a manager using Microsoft Graph .NET Client Library - c#

Can someone give me a hint how to set the User.Manager field using the Microsoft Graph .NET Client Library? I know how to do this via a direct REST call but want to avoid those calls (bypassing the wrapper) as much as possible. With the code below I was able to clear the manager property.
client = new GraphClient(...);
var builder = new DirectoryObjectWithReferenceRequestBuilder(
client.Users["<userid>"].Manager.Request().RequestUrl,client
);
builder.Request().DeleteAsync().Wait()
However, I still can't figure out which class allows me to build a PUT request for the member reference (as described here). I tried to the following code:
var mgr = new DirectoryObject();
mgr.Id = "<Id of the user that should be set as manager>";
var usrPatch = new User();
usrPatch.Manager = mgr;
client.Users["<Id of the user to be updated>"].Request().UpdateAsync(usrPatch).Wait();
This code doesn't throw an exception but it also doesn't update the manager. The request is wrong. The above code sends a PATCH instead of a PUT to the "base" object.
Request generated by the code above:
PATCH https://graph.microsoft.com/v1.0/users/[Id of the user to be updated] HTTP/1.1
SdkVersion: graph-dotnet-1.0.1
Content-Type: application/json
Host: graph.microsoft.com
Content-Length: 45 Expect: 100-continue
{"id":"[Id of the user that should be set as manager]"}
The response is a 204.

I see the following to remove a manager:
graphClient.Users[newUser.Id].Manager.Reference.Request().DeleteAsync();
But we should have something like the following to assign a manager:
graphClient.Users[newUser.Id].Manager.Reference.Request().AddAsync(manager);
I'll file a bug for this and update when fixed.

Workaround till updating the "manager" is fully supported:
var authToken = "<get your token here>";
var client = new GraphClient(...);
var usrId = "<id of the user to update>"
var mgrId = "<id of the manager>"
var url = client.Users[usrId].Manager.Reference.Request().RequestUrl;
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", authToken);
var content = new StringContent(
client.HttpProvider.Serializer.SerializeObject(
new ReferenceRequestBody
{
ODataId =
$"{client.BaseUrl}/directoryObjects/{mgrId}"
}),
Encoding.Default,
"application/json");
var resp = httpClient.PutAsync(url, content).Result;
if (!resp.IsSuccessStatusCode)
{
// throw exception/log etc
}

Two years later and this is still not working.
I'm getting the following exception when using await graphClient.Users[user.Id].Manager.Request().UpdateAsync(usrPatch);
Microsoft.Graph.ServiceException: 'Code: BadRequest
Message: Write requests are only supported on contained entities
I had to use stefboe's workaround logic to update the manager.
Microsoft.Graph DLL Version is 1.12.

Use PutAsync() to set the manager using the Graph Library:
graphClient.Users[user.Id].Manager.Reference.Request().PutAsync(manager.Id);

Related

How to use XRM in C#?

Hello I need to convert a JS Xrm web api function into C#
var opp = Xrm.Page.data.entity.getId();
Xrm.WebApi.retrieveMultipleRecords(
"dei_opportunityline",
"?$select=_dei_vendor_value,dei_multiplier,dei_iscommission,dei_opportunitylineid&$filter=_dei_opportunity_value eq " +
opp
).then(
function success(result) {
for (var i = 0; i < result.entities.length; i++) {
var newMulti = null;
if (result.entities[i].dei_iscommission) {
if (
result.entities[i]["_dei_vendor_value"] ==
"5fc8d03b-c41c-eb11-a813-000d3a31ed8d"
) {
if (jcimulti != null) newMulti = jcimulti;
} else {
if (commulti != null) newMulti = commulti;
}
For methods like .retrieveMultipleRecords, result.entities[i].dei_iscomission
is there a way to write this in C# plugin ?!
You cannot use Xrm in C#, this is useful inside Dynamics client scripting (JS) context only. ie web form context.
For Server side (C#), you have to use HttpClient. Sample code
HttpClient httpClient = null;
httpClient = new HttpClient();
//Default Request Headers needed to be added in the HttpClient Object
httpClient.DefaultRequestHeaders.Add("OData-MaxVersion", "4.0");
httpClient.DefaultRequestHeaders.Add("OData-Version", "4.0");
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
//Set the Authorization header with the Access Token received specifying the Credentials
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authToken);
httpClient.BaseAddress = new Uri("https://yourorg.api.crmx.dynamics.com/api/data/v9.0/");
var response = httpClient.GetAsync("dei_opportunityline?$select=_dei_vendor_value,dei_multiplier,dei_iscommission,dei_opportunitylineid&$filter=_dei_opportunity_value eq <guid>").Result;
if (response.IsSuccessStatusCode)
{
var records = response.Content.ReadAsStringAsync().Result;
//you can get dei_iscomission from above records
}
This is how we use in console application, but in plugin or workflow code - you can use Org Service to get data using Query Expression or fetchxml. Methods like service.RetrieveMultiple will suffice your need. Read more
Simply include the crmsdk nuget package in C#. Then you can connect like you want.
See docs
This packages gives you access to the official c# SDK for XRM. the methods are much easier to use then directly calling the ODATA Web API. For an example to fetch multiple records, see https://community.dynamics.com/crm/f/microsoft-dynamics-crm-forum/246665/how-to-retrieve-multiple-records-from-an-entity-in-crm-using-c
For a quick start, see https://learn.microsoft.com/en-us/powerapps/developer/data-platform/webapi/get-started-dynamics-365-web-api-csharp

How to diagnose a 401 error attempting to get an OAuth2 bearer token in c# .NET Core?

I have some limited skills in c++ and have recently moved in C# (asp.net) and azure Web services. As a PoC I'm trying to make REST calls into PayPal (which I'll need to be using professionally in 3 -6 months).
I've set up my personal PayPal account using the instructions here and I get a bearer token back using curl as described in the link. Awesome.
I'm now trying to do this from .NET Core C# and all I get is a 401 error. I've examined the request and it seems the same as the curl in terms of headers; the base64 encoded credentials I think I'm adding are the same as the ones in the verbose curl log (I examined the two base64 strings by eye) so it must be something I'm doing (or not doing) in the set up of the call. I'm looking for suggestions, pointers, or flat out laughter at the obvious mistake I've made.
I've set up what I believe to be a named client thus:
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("PayPal", c =>
{
c.BaseAddress = new Uri("https://api.sandbox.paypal.com/v1/");
c.DefaultRequestHeaders.Add("Accept", "application/json");
c.DefaultRequestHeaders.Add("Accept-Language", "en_US");
});
(with all the other stuff that comes free with VS under it omitted for brevity).
I attempt the call thus:
string clientCredString = CLIENTID + ":" + SECRET;
var clientCreds = System.Text.Encoding.UTF8.GetBytes(clientCredString);
var client = _clientFactory.CreateClient("PayPal");
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", System.Convert.ToBase64String(clientCreds));
var messageBody = new Dictionary<string,string > ();
messageBody.Add("grant_type", "client_credientials");
var request = new HttpRequestMessage(HttpMethod.Get, "oauth2/token")
{
Content = new FormUrlEncodedContent(messageBody)
};
string token;
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
var json = await response.Content.ReadAsStringAsync();
token = JsonConvert.DeserializeObject<string>(json);
}
else
{
throw new ApplicationException("Well that failed");
}
and get a 401 code for my trouble.
Suggestions for troubleshooting, better methods of doing this and laughter at my foolishness all welcomed.
Update:
I read the documentation, a couple of items stand out to me:
Requires a verb of post.
Uses FormUrlEncodedContent for client credentials.
Basic auth requires username and password (Client Id & Secret)
I believe the syntax should be:
var client = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Post, "...");
request.Content = new Dictionary<string, string>() { "grant_type", "client_credentials" };
request.Headers.Authorization = new AuthenticationHeaderValue("Basic", $"{Encoding.UTF8.GetBytes($"{id}:{secret}")}");
HttpResponseMEssage = response = await client.PostAsync(request);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
For the benefit of future readers:
It was, as suggested, an encoding problem. The line:
var clientCreds = System.Text.Encoding.UTF8.GetBytes(clientCredString);
needed to be
var clientCreds = System.Text.Encoding.ASCII.GetBytes(clientCredString);
It should also be noted that this particular operation requires a POST not a GET as I was using, but once I started sending properly encoded requests the errors started to make a lot more sense.

InvalidSignatureException with AWS Kinesis Video Stream C#

I am attempting to execute my own HTTP signed request since there is no SDK in C# for the PutMedia API for the AWS Kinesis Video Stream, but I am getting the following error message:
StatusCode: 403, ReasonPhrase: 'Forbidden'
x-amzn-ErrorType: InvalidSignatureException:http://internal.amazon.com/coral/com.amazon.coral.service/
Here is a gist of what my code looks like:
var streamName = "audio-stream-test";
var service = "kinesisvideo";
var endpoint = GetPutMediaEndpoint(streamName);
var host = GetHostFromEndpoint(endpoint);
var region = GetRegionFromEndpoint(endpoint);
var t = DateTime.UtcNow;
var canonical_uri = $"{endpoint}/putMedia";
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, new Uri(canonical_uri));
httpRequestMessage.Headers.Add("connection", "keep-alive");
httpRequestMessage.Headers.Add("host", host);
httpRequestMessage.Headers.Add("Transfer-Encoding", "chunked");
httpRequestMessage.Headers.Add("user-agent", "AWS-SDK-KVS/2.0.2");
httpRequestMessage.Headers.Add("x-amzn-fragment-acknowledgment-required", "1");
httpRequestMessage.Headers.Add("x-amzn-fragment-timecode-type", "ABSOLUTE");
httpRequestMessage.Headers.Add("x-amzn-producer-start-timestamp", (t - DateTime.MinValue).TotalMilliseconds.ToString());
httpRequestMessage.Headers.Add("x-amzn-stream-name", streamName);
httpRequestMessage.Headers.Add("x-amz-security-token", sessionToken);
var byteArray = File.ReadAllBytes(filePath);
var content = new ByteArrayContent(byteArray);
httpRequestMessage.Content = content;
var httpClient = new HttpClient();
var aws4RequestSigner = new AWS4RequestSigner(accessKey, secretAccessKey);
var signedHttpRequestMessage = aws4RequestSigner.Sign(httpRequestMessage, service, region).Result;
var httpResponseMessage = httpClient.SendAsync(signedHttpRequestMessage);
Screenshot of Error
I am using the Aws4RequestSigner NuGet package to sign the request. Any ideas what I am doing wrong here? Has anyone tried to use the AWS Kinesis Video Stream with C#/.NET successfully?
Two potential issues with the pseudo-code.
If using session token then the request signing should include the session token as well not only access key/secret access key combination.
The body of the PutMedia is "endless" as it streams out as a realtime stream. As such, the data shouldn't be included in the signature calculation.
This is answer to your question "the actual "content" is not being added to the stream. I see the Put Connection from KVS but no data added".
After you get 200 by setting http headers properly for the signing with below code, you need to have your content set in signedHttpRequestMessage.
var httpResponseMessage = httpClient.SendAsync(signedHttpRequestMessage);

PostAsync in UWP App not working (no content sent)

I'm a bit stuck, I am trying to create a UWP App that will post XML content to a web service. I can get this to work in a regular .net console app without an issue. Trying to re-create this using UWP is proving to be tricky. Using fiddler I've narrowed down that the web service end point isn't receiving my content. It looks like the headers are setup properly the content length is sent correctly but the actual content isn't sent. Here is the heart of the code, it crashes/throws an exception after:
HttpResponseMessage ResponseMessage = await request.PostAsync(requestUri, httpContent2).ContinueWith(
(postTask) => postTask.Result.EnsureSuccessStatusCode());
When I try to execute the PostASync, looking at fiddler, I'm getting:
HTTP/1.1 408 Request body incomplete
Date: Mon, 14 Nov 2016 15:38:53 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
Cache-Control: no-cache, must-revalidate
Timestamp: 10:38:53.430
The request body did not contain the specified number of bytes. Got 0, expected 617
I'm positive that I am getting content to post correct (I read it from a file, I send it to debug window to verify and it's correct). I think it might have to do with HttpContent httpContent2 - In regular .NET I've never needed to use this but with PostAsync I need to use it.
Any thoughts would be appreciated, thank you!
public async void PostWebService()
{
string filePath = "Data\\postbody.txt";
string url = "https://outlook.office365.com/EWS/Exchange.asmx";
Uri requestUri = new Uri(url); //replace your Url
var myClientHandler = new HttpClientHandler();
myClientHandler.Credentials = new NetworkCredential("user#acme.com", "password");
HttpClient request = new HttpClient(myClientHandler);
string contents = await ReadFileContentsAsync(filePath);
Debug.WriteLine(contents);
HttpContent httpContent2 = new StringContent(contents, Encoding.UTF8, "text/xml");
string s = await httpContent2.ReadAsStringAsync();
Debug.WriteLine(s); //just checking to see if httpContent has the correct data
//HttpResponseMessage ResponseMessage = await request.PostAsync(requestUri, httpContent);
request.MaxResponseContentBufferSize = 65000;
HttpResponseMessage ResponseMessage = await request.PostAsync(requestUri, httpContent2).ContinueWith(
(postTask) => postTask.Result.EnsureSuccessStatusCode());
Debug.WriteLine(ResponseMessage.ToString());
}
Well it seems like I found the root cause to my problem. This is appears to be a known bug with System.Net.Http.HttpClient when using network authentication. See this article here
My initial mistake was that I wasn't catching an exceptions thrown by PostAsync. once I wrapped that inside a try/catch block I got the following exception thrown:
“This IRandomAccessStream does not support the GetInputStreamAt method because it requires cloning and this stream does not support cloning.”
The first paragraph of the article I linked to above states:
When you use the System.Net.Http.HttpClient class from a .NET
framework based Universal Windows Platform (UWP) app and send a
HTTP(s) PUT or POST request to a URI which requires Integrated Windows
Authentication – such as Negotiate/NTLM, an exception will be thrown.
The thrown exception will have an InnerException property set to the
message:
“This IRandomAccessStream does not support the GetInputStreamAt method
because it requires cloning and this stream does not support cloning.”
The problem happens because the request as well as the entity body of
the POST/PUT request needs to be resubmitted during the authentication
challenge. The above problem does not happen for HTTP verbs such as
GET which do not require an entity body.
This is a known issue in the RTM release of the Windows 10 SDK and we
are tracking a fix for this issue for a subsequent release.
The recommendation and work around that worked for me was to use the Windows.Web.Http.HttpClient instead of System.Net.Http.HttpClient
Using that recommendation, the following code worked for me:
string filePath = "Data\\postbody.txt";
string url = "https://outlook.office365.com/EWS/Exchange.asmx";
Uri requestUri = new Uri(url); //replace your Url
string contents = await ReadFileContentsAsync(filePath);
string search_str = txtSearch.Text;
Debug.WriteLine("Search query:" + search_str);
contents = contents.Replace("%SEARCH%", search_str);
Windows.Web.Http.Filters.HttpBaseProtocolFilter hbpf = new Windows.Web.Http.Filters.HttpBaseProtocolFilter();
Windows.Security.Credentials.PasswordCredential pcred = new Windows.Security.Credentials.PasswordCredential(url, "username#acme.com", "password");
hbpf.ServerCredential = pcred;
HttpClient request = new HttpClient(hbpf);
Windows.Web.Http.HttpRequestMessage hreqm = new Windows.Web.Http.HttpRequestMessage(Windows.Web.Http.HttpMethod.Post, new Uri(url));
Windows.Web.Http.HttpStringContent hstr = new Windows.Web.Http.HttpStringContent(contents, Windows.Storage.Streams.UnicodeEncoding.Utf8, "text/xml");
hreqm.Content = hstr;
// consume the HttpResponseMessage and the remainder of your code logic from here.
try
{
Windows.Web.Http.HttpResponseMessage hrespm = await request.SendRequestAsync(hreqm);
Debug.WriteLine(hrespm.Content);
String respcontent = await hrespm.Content.ReadAsStringAsync();
}
catch (Exception ex)
{
string e = ex.Message;
Debug.WriteLine(e);
}
Hopefully this is helpful to someone else hitting this issue.

Delete email using mailinator API

I am creating application to access public emails in mailinator. I can view emails but I have difficulties when I am trying to delete them.
https://mailinator.com/apidocs.jsp all examples from documentacion worked except this one.
I have code to POST Http request:
using (var client = new HttpClient())
{
var values = new Dictionary<string, string>
{
{ "msgid", id}
};
var content = new FormUrlEncodedContent(values);
var response = await client.PostAsync("https://api.mailinator.com/api/delete?", content);
var responseString = await response.Content.ReadAsStringAsync();
}
Only error it throws is (405) Method Not Allowed. or Method is not supported by this URL.
So I guess either my url that I'm sending is bad, either my code.
I need some help to figure it out.
According to the API docs you need to pass a valid token with every call. The delete API example looks like this:
curl "https://api.mailinator.com/api/delete?id=1373143878-0-test22&token=..."
The elipsis (...) there needs to be a valid token. So, add the token to your values dictionary.

Categories