Reputation: 33
I'm using C# Microsoft Graph to send an email but I'm facing the error "Object reference not set to an instance of an object" when I call the await graphClient.Me.SendMail(message).Request().PostAsync() method. I tried to call var request = graphClient.Me.SendMail(message).Request() first and see that the request object is not null but when I call request.PostAsync() it gives the error, so I think PostAsync() method has a problem or I am missing something.
I'm not sure that if we can use the access token from msal-browser to send an email in C#?
Here is the code workflow:
Notes: I want to send an email from the backend (C#) instead of javascript because I want to handle some logic code at the backend.
Here is SDK: https://alcdn.msauth.net/browser/2.7.0/js/msal-browser.min.js and Microsoft.Graph version 1.21.0
Here is javascript code:
var msalConfig = {
auth: {
clientId: "clientId",
authority: "https://login.microsoftonline.com/{tenantId}",
redirectUri: location.origin
}
};
var msalInstance = new msal.PublicClientApplication(msalConfig);
var loginRequest = {
scopes: ["User.Read", "Mail.Send"]
};
msalInstance.loginPopup(loginRequest)
.then((res) => {
methodCallAPIFromDotNet(res.accessToken); // This is method is used to call API from C# with the accessToken
})
.catch(error => {
console.error(error);
});
Here is C# code:
public async Task<bool> Send(string accessToken)
{
var message = new Message
{
Subject = "Meet for lunch?",
Body = new ItemBody
{
ContentType = BodyType.Text,
Content = "The new cafeteria is open."
},
ToRecipients = new List<Recipient>
{
new Recipient
{
EmailAddress = new EmailAddress
{
Address = "to email address"
}
}
},
Attachments = new MessageAttachmentsCollectionPage()
};
var authProvider = new DelegateAuthenticationProvider(requestMessage =>
{
requestMessage.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", accessToken);
return System.Threading.Tasks.Task.FromResult(0);
});
var graphClient = new GraphServiceClient(authProvider);
var me = await graphClient.Me.Request().GetAsync(); // I can get user info here.
await graphClient.Me.SendMail(message).Request().PostAsync(); // Error here
return true;
}
Here is exception:
at Microsoft.Graph.HttpProvider.d__18.MoveNext()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.Graph.BaseRequest.d__35.MoveNext()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.Graph.BaseRequest.d__30.MoveNext()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
Upvotes: 0
Views: 3996
Reputation: 15936
OP's resolution:
used login.microsoftonline.com/consumers instead of login.microsoftonline.com/ for the authority attribute so now I can read messages or send email.
Then it can still use code below to send email
var graphClient = new GraphServiceClient(authProvider);
await graphClient.Me.SendMail(message).Request().PostAsync();
==========================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="js/msal.js"></script>
<script src="js/jquery-3.5.1.min.js" type="text/javascript" charset="utf-8"></script>
<!-- <script type="text/javascript" src="https://js.monitor.azure.com/scripts/b/ai.2.min.js"></script> -->
</head>
<body>
<div style="margin-top: 15px; background-color: #DDDDDD;">
<button type="button" id="signIn" onclick="signIn()">Sign In</button>
<button type="button" id="getAccessToken" onclick="getAzureAccessToken()">getAccessToken</button>
<h5 class="card-title" id="welcomeMessage">Please sign-in to see your profile and read your mails</h5>
<div>
<div>
accesstoken :
<div id="accesstoken">
</div>
</div>
</div>
</div>
<script type="text/javascript">
const msalConfig = {
//clientsecret:"client_secret"
auth: {
clientId: "your_azuread_app_client_id",
authority: "https://login.microsoftonline.com/your_tenant_name.onmicrosoft.com",
redirectUri: "your_home_page_url_like_http://localhost:8848/new_file.html",
},
cache: {
cacheLocation: "sessionStorage", // This configures where your cache will be stored
storeAuthStateInCookie: false, // Set this to "true" if you are having issues on IE11 or Edge
}
};
//scope used for sign in
const loginRequest = {
scopes: ["openid", "profile", "User.Read"]
};
//scope used for generating access token
const AzureMgmtScops ={
scopes:["User.Read", ...]
}
let accessToken = '';
const myMSALObj = new Msal.UserAgentApplication(msalConfig);
function signIn() {
myMSALObj.loginPopup(loginRequest)
.then(loginResponse => {
console.log("id_token acquired at: " + new Date().toString());
console.log(loginResponse);
if (myMSALObj.getAccount()) {
showWelcomeMessage(myMSALObj.getAccount());
getAzureAccessToken();
}
}).catch(error => {
console.log(error);
});
}
function showWelcomeMessage(account) {
document.getElementById("welcomeMessage").innerHTML = `Welcome ${account.name}`;
}
function getAzureAccessToken(){
myMSALObj.acquireTokenSilent(AzureMgmtScops).then(tokenResponse => {
showAccesstoken(tokenResponse.accessToken)
console.info("======the accesstoken is ======:"+tokenResponse.accessToken);
}).catch(function (error) {
console.log(error);
})
}
function showAccesstoken(data){
document.getElementById("accesstoken").innerHTML = JSON.stringify(data, null, 2);
}
</script>
</body>
</html>
============================Update================================
You used var graphClient = new GraphServiceClient(authProvider);
to initial the graphClient
, and the access token is passed from the frontend, so I think you followed this sample to use on-behalf-flow here. But the code snippet you provided is different here. So I think this is the reason for the issue about sending email failed.
Here you have 2 options, one is read about the on-behalf-flow and if you accept to use the flow here, you just need to follow the tutorial I provided above to complete the code and try.
But I think you may prefer the second way. In my opinion, what you need your backend do is executing some logic to complete the email, then you call the api to send the email. If so, why not sending http request directly as you've got the access token.
You may check if the access token worked for sending email, just try calling api with the token in some tools like postman. And if you got 202 response, that means the token is ok.
Then you need to modify your send email method like this, it worked for me:
public async Task sendAsync() {
var mesg = new Message
{
Subject = "Meet for lunch?",
Body = new ItemBody
{
ContentType = BodyType.Text,
Content = "The new cafeteria is open."
},
ToRecipients = new List<Recipient>
{
new Recipient
{
EmailAddress = new EmailAddress
{
Address = "[email protected]"
}
}
},
Attachments = new MessageAttachmentsCollectionPage()
};
var temp = new MailContentModel
{
message = mesg
};
var jsonStr = JsonSerializer.Serialize(temp);
string token = "your_token";
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
HttpResponseMessage response = await httpClient.PostAsync("https://graph.microsoft.com/v1.0/me/sendMail", new StringContent(jsonStr, Encoding.UTF8, "application/json"));
}
using Microsoft.Graph;
public class MailContentModel
{
public Message message { get; set; }
}
Upvotes: 1