I cant seem to reach the client from the hub. All I have is a .on on the client side and I am just trying to reach a break point but its never being reached. I have others on the client side I can reach without issue. I am not receiving any errors and not seeing any script issues in the dev tools console. All I want to do is fire off an action to a specific user. I have tried using both .Client and .User with the same result
On my client I have
connection.on("SendRequest", function (requestmessage) {
var whatever = requestmessage;
});
then in the hub
public async Task RequestPrivateChat(string UserListJS)
{
var ConnectionID = "";
MyUser user = new MyUser();
string message = "This is my message";
dynamic UserList = JsonConvert.DeserializeObject(UserListJS);
foreach (string item in UserList)
{
//I get the user okay then use the user.id below
user = _db.Users.Where(x => x.UserName == item).FirstOrDefault();
//I get the connection ID okay
ConnectionID = _connections.GetConnections(item).First();
//Both of these are reached but the client is never reached out to. Both Connection ID and user.Id are populated correctly
await Clients.Client(ConnectionID).SendAsync("SendRequest", message);
await Clients.User(user.Id.ToString()).SendAsync("SendRequest", message);
}
}
This for sending messages to all clients works fine
Hub
await Clients.All.SendAsync("ReceiveMessage", name, message);
Client
connection.on("ReceiveMessage", function (user, message) {
var msg = message.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
var encodedMsg = user + " says " + msg;
var li = document.createElement("li");
li.textContent = encodedMsg;
document.getElementById("messagesList").appendChild(li);
});
UPDATE
I have tried moving this outside of visual studio into IIS just in case this was and IDE issue with same result. I also tried the following
await Clients.User("username").SendAsync("SendChatRequest", message);
and it still didn't work but oddly enough I realized when I first go to the hub
connection.on("SendRequest", function (requestmessage)
is being hit without ever being called. I have confirmed there are only 2 references to send request, one in the hub and one on the client. I am sure there is something I am missing.
Since your await this.Clients.All.SendAsync("SendRequest", message); works, you not passing correctly the connectionId to target the user.
What I suggest is:
Get the connection Id from the context like:
await Clients.Client(this.Context.ConnectionId).SendAsync("SendRequest", message);
Get the connection Id from the caller:
await Clients.Caller(this.Context.ConnectionId).SendAsync("SendRequest", message);
Add the client to specific group and call the method for the group (assuming you added the connection in to a group):
await Clients.Group("GroupName").SendAsync("SendRequest", message);
Related
I'm writing an integration test for an azure function triggered by a storage queue, and I'd like to be able to check if the function has successfully processed the message or moved the message to the poison queue.
Is there any way to search a queue for a specific message, without dequeuing the message?
My approach was to retrieve the messageId and popReceipt of the sent message, and then try to update the message, throwing an exception if not found.
public async Task<bool> IsMessageInQueue(string messageId, string popReceipt, string queueName)
{
try
{
var client = new QueueClient(_storageConnectionString, queueName);
_ = client.UpdateMessageAsync(messageId, popReceipt);
return true; //exists in queue
}
catch (Exception)
{
return false; //doesn't exist in queue
}
}
And then
var sendMessageResponse = await client.SendMessageAsync(queueMessage);
var messageId = sendMessageResponse.Value.MessageId;
var popReceipt = sendMessageResponse.Value.PopReceipt;
var isProcessing = IsMessageInQueue(messageId, popReceipt, "processing");
var isPoisoned = IsMessageInQueue(messageId, popReceipt, "processing-poison");
isProcessing does return true if the message hasn't yet been picked up by the function and is still in "processing", but the problem is that the messageId changes when the message is moved to the poison queue, so isPoisoned will always return false
Update:
Thanks to #gaurav-mantri's suggestion, i updated my method to use PeekMessagesAsync and added my answer below:
Is there any way to search a queue for a specific message, without
dequeuing the message?
It is only possible if the number of messages in your queue is less than 32. 32 is the maximum number of messages you can peek (or dequeue) at a time.
If the number of messages are less than 32, you can use QueueClient.PeekMessagesAsync and compare the message id of your messages with the messages returned. If you find a matching message id, that would mean the message exists in the queue.
Thanks to #gaurav-mantri's suggestion, i updated my method to use PeekMessagesAsync
public async Task<bool> IsMessageInQueue(BinaryData body, string queueName)
{
var client = new QueueClient(_storageConnectionString, queueName);
var messages = await client.PeekMessagesAsync();
return messages.Value.Any(x => x.Body.ToString().Equals(body.ToString()));
}
And this is the usage...
var queueMessage = JsonConvert.SerializeObject("hi");
var sendMessageResponse = await client.SendMessageAsync(queueMessage);
var isProcessing = IsMessageInQueue(new BinaryData(queueMessage), "processing");
var isPoisoned = IsMessageInQueue(new BinaryData(queueMessage), "processing-poison");
I need to send an email to users from an ASP.NET Core 2 application, following some business rules. However, I need to ensure that the account the email is being sent to actually exists (for some reason, it may be that the account stopped being valid). The customer is using Azure Active Directory, so I need to query AAD somehow so it lets me know whether the account exists or not.
So far I have been looking for Microsoft Graph as a way to do this, however every example I have seen so far requires prior authentication and use a delegate authentication mechanism. I don't want my users having to authenticate nor to prompt the authentication screen.
Given this situation, what would you recommend using? If you can also point me to an example, that would be great. Thanks!
You don't really need to throw/catch exception for every invalid user as you're doing in current code. I have nothing against exception handling in general for other reasons but to see if the user exists or not you can try using Filter.
So your graph query could look like -
https://graph.microsoft.com/v1.0/users?$filter=startswith(userPrincipalName,'someuser#mytenant.onmicrosoft.com')
I have shown startswith here becuase eq didn't work for me in a quick trial. Although I would recommend two things:
Go through Microsoft documentation on Filters here and see what works best for your requirements - Use query parameters to customize responses with Microsoft Graph
Play a little bit with different queries in Microsoft Graph Explorer it's very simple and easy to use.
Here is a modified version for your code.
Note that I'm checking for the collection count to be > 0 and not checking for it to be null, as even in case user is not found the UsersCollectionPage was not null for my test run.
using Microsoft.Identity.Client;
using Microsoft.Graph.Auth;
using Microsoft.Graph;
...
private async Task<bool> ValidateAccounts(string accounts) {
var confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create("clientId here")
.WithTenantId("tokenId here")
.WithClientSecret("secret here")
.Build();
var authProvider = new ClientCredentialProvider(confidentialClientApplication);
var graphClient = new GraphServiceClient(authProvider);
var valid = true;
try {
foreach (var account in accounts.Split(';')) {
var user = await graphClient.Users.Request().Filter("startswith(userPrincipalName, '" + account + "')").GetAsync();
if (user.Count <= 0) {
valid = false;
break;
}
}
} catch (ServiceException ex) {
valid = false;
}
return valid;
}
On a side note, I'm not not sure of your requirements but you could probably get creative by combining multiple user names in single query and then checking for result counts or other propertes. You could use or between multiple criteria or probably use any operator. I haven't really tried this out though.
Finally I came up with something workable. It's not nice, and it uses preview software. First, install Microsoft.Graph and Microsoft.Identity.Client packages. Then install Microsoft.Graph.Auth, which at the time of this writing, is in preview (v1.0.0-preview.1) so you'll need to tick "include prerelease" checkbox in nuget manager.
Then in your AAD, you need to get the ClientId, TenantId and SecretId. In my case, my app was already using AAD authentication so I already had ClientId and TenantId in my appsettings.json file. I only needed to create a new SecretId (in the Certificate & secrets section of my app registration). Then I needed to add permissions (in the API permissions section of my app registration) to include Microsoft.Graph with at least User.Read.All permission.
using Microsoft.Identity.Client;
using Microsoft.Graph.Auth;
using Microsoft.Graph;
...
private async Task<bool> ValidateAccounts(string accounts) {
var confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create("clientId here")
.WithTenantId("tokenId here")
.WithClientSecret("secret here")
.Build();
var authProvider = new ClientCredentialProvider(confidentialClientApplication);
var graphClient = new GraphServiceClient(authProvider);
var valid = true;
try {
foreach (var account in accounts.Split(';')) {
var user = await graphClient.Users[account]
.Request()
.GetAsync();
if (user == null) {
valid = false;
break;
}
}
} catch (ServiceException ex) {
valid = false;
}
return valid;
}
Here, the function takes a semicolon-separated string for each account. The GetAsync method will throw a ServiceException if the user does not exist. I don't like that, but couldn't find another way. So that's about it. Hope this helps someone else, and hope someone could come up with a better solution eventually.
Import following namespaces (You needs to install relevant packages using nuget):
using Microsoft.Graph;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
Set your Azure AD app values:
private string _tenant => "your_tenant_id";
private string _appId => "your_ad_app_client_id";
private string _appSecret => "your_app_client_secret";
Create Graph Service Client using this:
public static GraphServiceClient CreateGraphServiceClient()
{
var clientCredential = new ClientCredential(_appId, _appSecret);
var authenticationContext = new AuthenticationContext("https://login.microsoftonline.com/{_tenant}");
var authenticationResult = authenticationContext.AcquireTokenAsync("https://graph.microsoft.com", clientCredential).Result;
var delegateAuthProvider = new DelegateAuthenticationProvider((requestMessage) =>
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", authenticationResult.AccessToken);
return Task.FromResult(0);
});
return new GraphServiceClient(delegateAuthProvider);
}
var graphServiceClient = GraphServiceClientHelper.CreateGraphServiceClient();
Then call graph api and filter users by Email Address as follows:
var user = await graphServiceClient.Users.Request().Filter("mail eq '" + UserEmailAddress + "'").GetAsync();
if (user.Count == 0) {
//user not exist
}
Below code worked for me.
using Microsoft.Graph;
using Microsoft.Graph.Auth;
using Microsoft.Identity.Client;
private static async Task<bool> ValidateAccounts(string accounts)
{
var confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create("client id")
.WithTenantId("tenant id")
.WithClientSecret("client secret")
.Build();
var authProvider = new ClientCredentialProvider(confidentialClientApplication);
var graphClient = new GraphServiceClient(authProvider);
var valid = true;
try
{
foreach (var account in accounts.Split(';'))
{
var user = await
graphClient.Users.Request().Filter($"identities/any(c:c/issuerAssignedId eq
'{account}' and c/issuer eq 'xyz.onmicrosoft.com')").GetAsync();
if (user.Count <= 0)
{
valid = false;
break;
}
}
}
catch(Exception ex)
{
valid = false;
}
return valid;
}
Here's my code to send email via SendGrid.
public Task<Result> Execute(string apiKey, string subject, string message, string email)
{
var client = new SendGridClient(apiKey);
string senderEmail = this.Configuration["Email:Address"];
string senderName = this.Configuration["Email:Name"];
var msg = new SendGridMessage()
{
From = new EmailAddress(senderEmail, senderName),
Subject = subject,
PlainTextContent = message,
HtmlContent = message
};
msg.AddTo(new EmailAddress(email));
var response = client.SendEmailAsync(msg);
return response;
}
It is able to send email confirmation and password reset emails, when invoked by ASP.Net Core Identity code.
However, when I try to send email using my own controller action (for a Contact Us feature), nothing happens.
This is the controller action.
public async Task<JsonResult> SendContactEmail(int id, string message)
{
Page page = await this.Get(id);
var result = _emailSender.SendEmailAsync(page.Email, page.Subject, message);
return new JsonResult(result);
}
Is there a way to troubleshoot this? I don't get any exceptions and the response object doesn't seem to have any useful property like a message from the SendGrid server.
UPDATE:
SendGrid's API does return an informative result. I have updated my code to return this.
https://github.com/sendgrid/sendgrid-csharp/blob/master/src/SendGrid/Response.cs
NOTE: Using the Sign-in button is NOT an option
A year ago I was having a problem creating a moment. Back then I was using version 1.2 of the Google+ API .Net client. As I described in this post, I had it working although the code failed to insert a moment from time to time. I was hoping that the process is more stable and easier to implement now, and it seems like it as can be seen in the example that you can download here - the current version as of this writing is v1.8. So I created a simple project following the SimpleOAuth2 sample in the download, but implementing Google+. This is the code I came up:
public partial class _Default : System.Web.UI.Page
{
private PlusService service;
// Application logic should manage users authentication.
// This sample works with only one user. You can change
// it by retrieving data from the session.
private const string UserId = "user-id";
protected void Page_Load(object sender, EventArgs e)
{
GoogleAuthorizationCodeFlow flow;
var assembly = Assembly.GetExecutingAssembly();
using (var stream = assembly.GetManifestResourceStream(
"GPlusSample.client_secrets.json"))
{
flow = new GoogleAuthorizationCodeFlow(
new GoogleAuthorizationCodeFlow.Initializer
{
DataStore = new FileDataStore("GPlusSample.Store"),
ClientSecretsStream = stream,
//
// Tried only this scope but it did not work
//Scopes = new[] { PlusService.Scope.PlusMe }
//
// I tried the following: but did not work either
//Scopes = new[] { PlusService.Scope.PlusMe,
// "https://www.googleapis.com/auth/plus.moments.write" }
//
// I tried this as well and it failed
//Scopes = new[] { PlusService.Scope.PlusLogin }
//
// Maybe this... but still no joy
Scopes = new[] { PlusService.Scope.PlusLogin,
PlusService.Scope.PlusMe }
});
}
var uri = Request.Url.ToString();
var code = Request["code"];
if (code != null)
{
var token = flow.ExchangeCodeForTokenAsync(UserId, code,
uri.Substring(0, uri.IndexOf("?")), CancellationToken.None).Result;
// Extract the right state.
var oauthState = AuthWebUtility.ExtracRedirectFromState(
flow.DataStore, UserId, Request["state"]).Result;
Response.Redirect(oauthState);
}
else
{
var result = new AuthorizationCodeWebApp(flow, uri, uri)
.AuthorizeAsync(UserId, CancellationToken.None).Result;
if (result.RedirectUri != null)
{
// Redirect the user to the authorization server.
Response.Redirect(result.RedirectUri);
}
else
{
// The data store contains the user credential,
// so the user has been already authenticated.
service = new PlusService(new BaseClientService.Initializer
{
ApplicationName = "Plus API Sample",
HttpClientInitializer = result.Credential
});
}
}
}
/// <summary>Gets the TasksLists of the user.</summary>
public async System.Threading.Tasks.Task InsertMoment()
{
try
{
var me = service.People.Get("me").Execute();
var request = service.Moments.Insert(new Moment()
{
Target = new ItemScope {
Id=Guid.NewGuid().ToString(),
Image="http://www.google.com/s2/static/images/GoogleyEyes.png",
Type="",
Name = "test message",
Description="test",
Text="test message",
},
Type = "http://schemas.google.com/AddActivity",
}, me.Id, MomentsResource.InsertRequest.CollectionEnum.Vault);
var response =await request.ExecuteAsync();
output.Text = "<h1>" + response.Id + "</h1>";
}
catch (Exception ex)
{
var str = ex.ToString();
str = str.Replace(Environment.NewLine, Environment.NewLine + "<br/>");
str = str.Replace(" ", " ");
output.Text = string.Format("<font color=\"red\">{0}</font>", str);
}
}
protected async void createMomentButton_Click(object sender, EventArgs e)
{
await InsertMoment();
}
}
That code always give me a 401 Unauthorized error, even if I have the Google+ API turned on for my project. Here's the actual error I got:
The service plus has thrown an exception: Google.GoogleApiException:
Google.Apis.Requests.RequestError Unauthorized [401] Errors [
Message[Unauthorized] Location[ - ] Reason[unauthorized]
Domain[global] ]
It's interesting to see that the insert moment is failing even though the call to People.Get("me") works - get("me") works with all of the scope combinations I listed above. It's important to note that each time I try a new scope, I first log out of my Google account and delete the access token that is stored in GPlusSample.Store.
EDIT
I tried setting just the Url instead of individual items as suggested by Ian and I got the exact same error.
var request = service.Moments.Insert(new Moment()
{
Target = new ItemScope {
Url = "https://developers.google.com/+/web/snippet/examples/thing"
},
Type = "http://schemas.google.com/AddActivity",
}, me.Id, MomentsResource.InsertRequest.CollectionEnum.Vault);
var response =await request.ExecuteAsync();
https://www.googleapis.com/auth/plus.login is the right scope for writing moments, but you need to have requested the specific app activity types you want to write as well. The parameter for this is request_visible_actions, and it takes a space separated list of arguments of the types (Listed on https://developers.google.com/+/api/moment-types/ - e.g. http://schemas.google.com/AddActivity).
The client library may not have a method for adding request_visible_actions, so you may have to add it on to the auth URL you redirect the user to manually (remember to URLencode the app activity type URLs!)
While creating a GCM client application, asynctask is giving compilation errors.
OnCreate we are calling registerBackgrouod which will check whether gcm instance is running or not, if not create one.
But asyntask is giving error : "Asynctask cannot be resolved to a type"
private void registerBackground() {
new AsyncTask() {
protected String doInBackground(Void... params) {
String msg = "";
try {
if (gcm == null) {
gcm = GoogleCloudMessaging.getInstance(context);
}
regid = gcm.register(SENDER_ID);
msg = "Device registered, registration id=" + regid;
// You should send the registration ID to your server over HTTP,
// so it can use GCM/HTTP or CCS to send messages to your app.
// For this demo: we don't need to send it because the device
// will send upstream messages to a server that echo back the message
// using the 'from' address in the message.
// Save the regid - no need to register again.
setRegistrationId(context, regid);
} catch (IOException ex) {
msg = "Error :" + ex.getMessage();
}
return msg;
}
protected void onPostExecute(String msg) {
mDisplay.append(msg + "\n");
}
}.execute(null, null, null);
As already observed by the AlexBcn, and according to the documentation of AsyncTask, you would pass to the AsyncTask three types as param. Because you want to return the payload of the GCM push notification as a String, you would invoke AsyncTask<Void, Void, String>
So the correct code snippet of GCM client is:
private void registerInBackground() {
new AsyncTask<Void, Void, String>() {
#Override
protected String doInBackground(Void... params) {
String msg = "";
try {
if (gcm == null) {
gcm = GoogleCloudMessaging.getInstance(context);
}
regid = gcm.register(SENDER_ID);
msg = "Device registered, registration ID=" + regid;
// You should send the registration ID to your server over HTTP, so it
// can use GCM/HTTP or CCS to send messages to your app.
// For this demo: we don't need to send it because the device will send
// upstream messages to a server that echo back the message using the
// 'from' address in the message.
// Persist the regID - no need to register again.
storeRegistrationId(context, regid);
} catch (IOException ex) {
msg = "Error :" + ex.getMessage();
// If there is an error, don't just keep trying to register.
// Require the user to click a button again, or perform
// exponential back-off.
}
return msg;
}.execute(null, null, null);
}
This is because of the params you pass in to Async task.
For further help:
I recently uploaded the fully functional GCM java client to my Github Account:
GCM Android Client
It has got both server and client implementation.