sending messages to single clients NOT identified by name (Identity) - signalr

I know similar questions have been asked before, but here goes
I have an ASP.NET app that serves images to connected clients. All clients are connected via owin with username and password and there could multiple clients connected with the same username and password. However, each client may need to be served with unique images. This means that I need to use a unique "hub ID" to serve each image.
The problem comes from retrieving this "hub ID" on the GetUserID method of the CustomUserProvider class. The IRequest parameter doesn't provide me with enough information to uniquely identify the connection. If I can get (which I can't (??)) to the Session state of the page then problem solved.
Has anyone got any ideas. I'm thinking of perhaps using the url - which I can make unique for each connection....
(Does anyone know how to get the original url of the page in the GetUserID)
I solved this as follows. I append a unique id on the URL. Then in the GetUserID of the CustomUserProvider
public string GetUserId(IRequest request)
{
string id = "";
try
{
HttpContextBase requestContext = request.Environment[typeof(HttpContextBase).FullName] as HttpContextBase;
string url = requestContext.Request.UrlReferrer.AbsoluteUri;
var parsedQuery = HttpUtility.ParseQueryString(url);
id = parsedQuery["HUBID"];
}
catch { }
return id;
This HUBID is the one referenced in the code behind:
var hubContext = GlobalHost.ConnectionManager.GetHubContext<Hubs.MimicHub>();
hubContext.Clients.User(HubID).addImage(MimicImage,
ImageWidth, ImageHeight
);

Every Signalr connection (client) will have its own ConnectionId.
You could use this ID to Identify the same user foreach connection.
You can receive this unique connectionId:
public override Task OnConnected()
{
var connectionId = Context.ConnectionId;
}
more info:
http://www.asp.net/signalr/overview/guide-to-the-api/mapping-users-to-connections

Please see amended question. There may have been a better one, but this works perfectly.

Related

Show a message after redirecting after a successful POST request without using TempData

I am using the Post-Redirect-Get pattern.
In my asp.net core MVC web app, this is what happens:
User submits a form via POST which adds an item to db.
Controller adds the new item and redirects with 302/303 to "/Home/Index/xxxx", where xxxx is the id of the item.
The new request (/Home/Index/xxxx) is served by the controller, and it displays the item. And the item url in the address bar is something the user can copy and share.
At step 3 above, I would like to show the user a message saying "Item was successfully added".
This is my code (without the success message):
public async Task<IActionResult> Index(string id)
{
ItemView itemView = null;
if (string.IsNullOrEmpty(id))
itemView = new ItemView(); // Create an empty item.
else
itemView = await itemService.GetItemAsync(id);
return View(itemView);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Index(ItemView itemView)
{
string id = await itemService.AddItemAsync(itemView);
return RedirectToAction("Index", "Home", new { id = id });
}
There are few ways to do this that I found in other answers on stackoverflow.
Redirect to "/Home/Index/xxxx?success=true". When action sees a success=true param, it can display the success message. But I don't want to use an extra param because I would like users to be able to just copy the url from the address bar and share it. And I don't want them sharing the url that has success param, because then everyone who clicks on the shared link will see the message "Item was successfully added".
This post suggests using TempData, which is a good solution. I think that would need me to enable sticky behavior on the server, which I would like to avoid if possible.
I can probably use referrer url to determine if the request came after a form submission, and in that case I can show the message.
The original answer by "snoopy" did point me in the right direction. But for some unknown reason, that answer no longer exists, so I am posting the answer myself in the hope it would benefit someone in future.
ASP .NET Core 1.1 and higher supports Cookie based Tempdata provider called CookieTempDataProvider. Link to Microsoft Docs.
This is similar to Session based Tempdata, but no data is stored on the server side. The response from the server set's a cookie in the browser with the data you want to store. The next request from the browser will include this cookie. The framework automatically parses this and populates this in TempData, which the controller can use. Once the controller reads this data, then the CookieTempDataProvider automatically adds the appropriate headers in the response to clear this cookie.
In your Startup class's ConfigureServices method, you need to register CookieTempDataProvider:
services.AddSingleton<ITempDataProvider, CookieTempDataProvider>();
To store some data in cookie based temp data, you simple set the value like this in your controller:
TempData["key"] = "value";
To read the data in your controller, you read it like this:
string value = TempData["key"];
if (value != null)
{
// Do something with the the value.
}
The check for non-null tells you if that key exists in TempData or not. Note that you can also check using .ContainsKey() method, but that is not counted as a read. The data (& the cookie) will not be cleared unless you read it. For example this will not clear the data:
if (TempData.ContainsKey("key"))
{
// Do something without actually reading the value of TempData["key"].
}

Send to certain connections only Spring Websockets

I am using grails/groovy so excuse the odd syntax, i am also new to using websockets so please let me know if i am going about this in the wrong way:
Using spring websockets i am able to send messages to certain subscribed users via
SimpMessagingTemplate brokerMessagingTemplate
users.each {
brokerMessagingTemplate.convertAndSendToUser(it.id,"/topic/path",data)
}
However, i want to send messages only to subscribed users have passed to the server a certain value/id over and above their user id. A connection is initialised on wepage load so i figured perhaps that i could add a STOMP header value which passes this information to the server, and the server only sends messages to connections which match this.
var socket = new SockJS("/url/stomp");
var client = Stomp.over(socket);
var headers = {'additionalId': additionalId};
client.connect({}, function() {
client.subscribe("/user/topic/path", function (data) {
}, headers);
firstly, i dont know whether adding a header value is the right way to do this, and secondly im not sure how to make the SimpMessagingTemplate send to those that have specifically provided the additional Id in the header.
Instead of using a header you can use DestinationVariable as so:
brokerMessagingTemplate.convertAndSend("/topic/something.${additionalId}".toString(), data)
and use
#MessageMapping("/something.{additionalId}")
protected String chatMessage(#DestinationVariable String additionalId, Principal principal, String data) { ... }
Additionally you may want to limit who subscribe to a specific /something.{additionalId} by implementing a TopicSubscriptionInterceptor() where you can validate the Principal

Retrieve names of Online users connected to a Server

I asked this question before which has got a very good response. But as I am new to asp.net (and jquery) cant understand how the program is flowing.
Summary:
I have created a Basic chat application. Now I am trying to add a advanced function like whenever a user is online (connected to a server), the server should show or broadcast the available online user's username to all the users connected to that server.
By referring the responses to the previous question (s0nica and VinayC), I modified my class file and jquery file, which are giving errors as shown in the below links. (I think I am very close)
Chat.cs (Two errors, I mentioned errors in between code comments)
file.js (Working fine, refer it if you need to)
Please have a look to the above files and assist me.
PS: In the previous post, I was thinking that if I change the Global.asax code, my problem will be solved.. which I realized later as wrong..
Your first error from:
Clients.joins(Context.ConnectionId, Caller.username, DateTime.Now);
Shold be:
Clients.All.joins(Context.ConnectionId, Clients.Caller.username, DateTime.Now);
Other errors associated with it: In your JS file it should be:
Line 15
chat.state.username = chatUsername;
Second error:
The error is exactly as it states, you do not have a toList function off of your dictionary object. Secondly you can't plainly convert a List or a string directly to a Chat object.
Based on your setup you currently dont have a proper "user" list to return. Right now you're saving a List to represent an individual user. You might want to try changing your dictionary object to be something like
static ConcurrentDictionary<string, User> _users = new ConcurrentDictionary<string, User>();
Where User is:
public class User
{
public string Name { get; set; }
public string ConnectionID { get; set; }
}
Then on your Joined function you could just do:
public void Joined()
{
User user = new User
{
Name = Clients.Caller.username,
ConnectionID = Context.ConnectionId
};
_users.TryAdd(user.ConnectionID, user);
Clients.All.joins(user.ConnectionID, user.Name, DateTime.Now);
}
Lastly your GetConnectedUsers would end up(make sure you're 'using System.Linq;'):
public List<User> GetConnectedUsers()
{
return _users.Values.ToList();
}
I probably went a little bit overboard but hopefully this helps!
If you need a reference to the change log from SignalR 0.5.3 to 1.0 alpha here's a great post on all of the modifications:
http://weblogs.asp.net/davidfowler/archive/2012/11/11/microsoft-asp-net-signalr.aspx

How to get HealthVault to work with multiple ApplicationID in same application

We may never know why Microsoft decided to limit developers by making HealthVault applications constrained to a single web/app.config entry for a HealthVault application. However I need to be able to make 2 (or more) HealthVault ApplicationID’s work with one ASP.NET website? I’m looking for an effective and reliable way to do this.
I won’t go into the details of the reasoning behind 2 different HealthVault applications, but other than to say we need it to work. I still cannot login correctly with MSDN Forums (think infinite redirection sign in loop) so I am hoping for a post here that will help me.
I did contact a HealthVault developer on how to achieve this however the developer gave a suggestion that I don’t believe would be reliable (if I’m wrong let me know).
The developer’s suggestion was to do the following in code when you needed to connect to HealthVault, but prior to connecting:
ConfigurationSettings.AppSettings[“ApplicationId”] = “[YOUR APP ID]”;
The problem is that this is a static property and I do see this as an issue as our web application will have different users accessing both HealthVault applications at the same time.
Does anyone have any suggestions to make 2 (or more) HealthVault ApplicationID’s work with one ASP.NET website? I’m looking for an effective and reliable way to do this.
There is a way to dynamically switch app ids on runtime. Both applications must be created, both certificates must be installed. Few things to keep in mind. For every authenticated connection, user will be granted a token (aka wctoken). This token is consumed when user is redirect back from Live ID (in case live id is used...) by your redirect.aspx page (assuming your redirect page inherits from HealthServiceActionPage.This means that everytime you switch applications, you must redirect user back to Live ID with new app id to receive new token.
Here is code sample that can be user to dynamically change settings:
public class ConfigurationManager : HealthWebApplicationConfiguration
{
private string appid;
public ConfigurationManager(string appid)
{
this.appid = appid;
}
public override Guid ApplicationId
{
get
{
return AppManager.Current.GetCurrentAppId(this.appid);
}
}
}
public class AppManager
{
private static readonly Object lck = new Object();
public Guid? App;
public static AppManager Current
{
get
{
AppManager mgr = null;
if (_current == null)
{
lock (lck)
{
mgr = new AppManager();
}
}
return mgr;
}
}
private static AppManager _current;
public Guid GetCurrentAppId(string id)
{
return new Guid(id);
}
}
Usage:
ConfigurationManager cm = new ConfigurationManager(your-app-id-here);
HealthWebApplicationConfiguration.Current = cm;

Windows Azure access POST data

Ok, so I can't seem to find decent Windows Azure examples. I have a simple hello world application that's based on this tutorial. I want to have custom output instead of JSON or XML. So I created my interface like:
[ServiceContract]
public interface IService
{
[OperationContract]
[WebInvoke(UriTemplate = "session/create", Method = "POST")]
string createSession();
}
public class MyService : IService
{
public string createSession()
{
// get access to POST data here: user, pass
string sessionid = Session.Create(user, pass);
return "sessionid=" + sessionid;
}
}
For the life of me, I can't seem to figure out how to access the POST data. Please help. Thanks!
If you have an HttpContext there may be a Request object that would have the form data. I'm basing part of this off the ASP.Net tag on this question, so if that is incorrect then there may be the need to handle this another way but it looks a lot like a web service to my mind.
EDIT: HttpRequest is the class that has the Form property that should be where the POST data is stored if this is an HTTP request. This is part of System.Web so it should be ready to be used pretty easily, as I recall.
Sample code showing the Request.Form property:
int loop1;
NameValueCollection coll;
//Load Form variables into NameValueCollection variable.
coll=Request.Form;
// Get names of all forms into a string array.
String[] arr1 = coll.AllKeys;
for (loop1 = 0; loop1 < arr1.Length; loop1++)
{
Response.Write("Form: " + arr1[loop1] + "<br>");
}
This presumed there was an HttpRequest instance around.
WCF Simplified Part 4: Comparing the Request/Reply and One-Way Patterns passes in a parameter so that your "createSession" method would have to take in those strings it would appear. I'm used to the ASP.Net world where there are some built-in objects like Request, Response, Server, Application and Session.
Yes, if you did try changing the method signature as there are ways to pass in parameters in that last example I linked though I don't know if that would work in your case or not.

Resources