Stream Attachments from Microsoft Graph API to SOAP WebService - microsoft-graph-api

First I would like to describe our architecture, it consists of:
Outlook Web Addin (hosted on IIS on-prem)
an ASP.NET Web API (hosted on-prem)
the Microsoft Graph REST API in the cloud
a SOAP WebService (not under my control, on-prem).
The flow can be described with these steps:
User clicks a button in the Outlook Web Add-in
An Ajax request is made from the Web Add-in to the Web API with the ID of the message as a parameter.
The Web API requests the message from Graph via GraphServiceClient
The Web API packs the attachments from the message into a SOAP request and sends submits it to the SOAP service.
The attachments in Microsoft Graph are returned as a Byte Array and stored in memory. So we can get run into memory and performance issues.
My question is, would it be possible to stream the attachments from Microsoft Graph directly to the SOAP service?
Getting attachments from MS Graph Rest API
var mail = graphClient
.Users["mailbox"]
.Messages["id"]
.Request()
.Expand("attachments");
foreach (var attachment in message.Attachments)
{
if (attachment.GetType() == typeof(FileAttachment))
{
var fileAttachment = (FileAttachment) attachment;
// ==> fileAttachment.ContentBytes already contains the bytes of the attachment
}
}
Should the bytes of the attachment be fetched with a second request?
How can it be streamed down?
The definition of the attachment part in the soap service wsdl
<xsd:complexType name="DocumentData">
<xsd:sequence>
<xsd:element name="name" type="xsd:string"/>
<xsd:element name="extension" type="xsd:string"/>
<xsd:element name="content" type="xsd:base64Binary"/>
</xsd:sequence>
</xsd:complexType>
... is generated in the service reference as
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.7.3062.0")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://is.uniqa.at/UniqaDocumentWF.WS:startDoWFProcess")]
public partial class DocumentData : object, System.ComponentModel.INotifyPropertyChanged
{
private string nameField;
private string extensionField;
private byte[] contentField;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified, Order = 0)]
public string name
{
get
{
return this.nameField;
}
set
{
this.nameField = value;
this.RaisePropertyChanged("name");
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified, Order = 1)]
public string extension
{
get
{
return this.extensionField;
}
set
{
this.extensionField = value;
this.RaisePropertyChanged("extension");
}
}
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified, DataType = "base64Binary", Order = 2)]
public byte[] content
{
get
{
return this.contentField;
}
set
{
this.contentField = value;
this.RaisePropertyChanged("content");
}
}
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
System.ComponentModel.PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
if ((propertyChanged != null))
{
propertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
}
... and can be created like this:
DoWF.DocumentData data = new DocumentData();
data.name = name;
data.extension = extension;
// At the moment content is set to
// "fileAttachment.ContentBytes" before
// the request is send to the soap service
data.content = content;
How and where can I stream the attachment bytes from Rest API over "my" Web API and then to the SOAP service?
I would also be grateful for a completely different approach to solve this problem.

Given the number of layers involved and your inability to control the APIs on either end of the workflow, I'm not sure this approach would work. More importantly, I suspect even if you can get this working, you'll find it extremely fragile.
I would suggest something along the lines of this article from the documentation: Get attachments of an Outlook item from the server. This uses example uses Exchange Web Services rather than Microsoft Graph but the general flow would be the same:
User clicks the button in the Add-in
Add-in passes the userPrincipalName and the message id to Web API
Web API downloads the attachments to temporary storage.
Web API uploads the attachments from temporary storage to the SOAP service.
In order to make this work, your Web API will need to use Client Credentials to connect to Microsoft Graph using the Mail.Read application scope. This will allow the Web API to connect to any mailbox and download the attachments.

Related

How to handle user OIDC tokens in Blazor Server when the browser is refreshed and the cookie’s tokens are invalid?

Microsoft recommend against using HttpContext in Blazor Server (here). To work around the issue of how to pass user tokens to a Blazor Server app, Microsoft recommend storing the tokens in a Scoped service (here). Jon McGuire’s blog suggests a similar approach that stores the tokens in Cache (here).
Microsoft’s approach above works just fine as long as the user stays within the same Blazor Server connection. However if the access_token is refreshed and the user then reloads the page either by pressing F5 or by pasting a URL into the address bar, then an attempt is made to retrieve the tokens from the cookie. By this time, the access_token and refresh_token in the cookie are no longer valid. Jon McGuire mentions this problem at the end of his blog post and refers to it as Stale Cookies (here). He gives hints about a possible solution, but is very light on implementation instructions. There are many comments at the bottom of that post from people unable to implement a solution, with no apparent working solution suggested. I spent a lot of time searching for a solution and all I found were people asking for one and not receiving any answers that worked.
Having found a solution that seems to work well, and also seems fairly principled, I thought it might be worth sharing my solution here. I would welcome any constructive criticism or suggestions for any significant improvements.
Edit 20220715: After some feedback on our approach from Dominic Baier we removed our Scoped UserSubProvider service in favour of using AuthenticationStateProvider instead. This has simplified our approach. I have edited the following answer to reflect this change.
This approach combines advice from Microsoft on how to pass tokens to a Blazor Server app (here), with server side storage of tokens in a Singleton service for all users (inspired by Dominick Baier’s Blazor Server sample project on GitHub here).
Instead of capturing the tokens in the _Host.cshtml file and storing them in a Scoped service (like Microsoft do in their example), we use the OnTokenValidated event in a similar way to Dominick Baier’s sample, storing the tokens in a Singleton service that holds tokens for all Users, we call this service ServerSideTokenStore.
When we use our HttpClient to call an API and it needs an access_token (or refresh_token), then it retrieves the User’s sub from an injected AuthenticationStateProvider, uses it to call ServerSideTokenStore.GetTokensAsync(), which returns a UserTokenProvider (similar to Microsoft’s TokenProvider) containing the tokens. If the HttpClient needs to refresh the tokens then it populates a UserTokenProvider and saves it by calling ServerSideTokenStore.SetTokensAsync().
Another issue we had was if a separate instance of the web browser is open while the app restarts (and therefore loses the data held in ServerSideTokenStore) the user would still be authenticated using the cookie, but we’ve lost the access_token and refresh_token. This could happen in production if the application is restarted, but happens a lot more frequently in a dev environment. We work around this by handling OnValidatePrincipal and calling RejectPrincipal() if we cannot get a suitable access_token. This forces a round trip to IdentityServer which provides a new access_token and refresh_token. This approach came from this stack overflow thread.
(For clarity/focus, some of the code that follows excludes some standard error handling, logging, etc.)
Getting the User sub claim from AuthenticationStateProvider
Our HttpClient gets the user's sub claim from an injected AuthenticationStateProvider. It uses the userSub string when calling ServerSideTokenStore.GetTokensAsync() and ServerSideTokenStore.SetTokensAsync().
var state = await AuthenticationStateProvider.GetAuthenticationStateAsync();
string userSub = state.User.FindFirst("sub")?.Value;
UserTokenProvider
public class UserTokenProvider
{
public string AccessToken { get; set; }
public string RefreshToken { get; set; }
public DateTimeOffset Expiration { get; set; }
}
ServerSideTokenStore
public class ServerSideTokenStore
{
private readonly ConcurrentDictionary<string, UserTokenProvider> UserTokenProviders = new();
public Task ClearTokensAsync(string userSub)
{
UserTokenProviders.TryRemove(userSub, out _);
return Task.CompletedTask;
}
public Task<UserTokenProvider> GetTokensAsync(string userSub)
{
UserTokenProviders.TryGetValue(userSub, out var value);
return Task.FromResult(value);
}
public Task StoreTokensAsync(string userSub, UserTokenProvider userTokenProvider)
{
UserTokenProviders[userSub] = userTokenProvider;
Return Task.CompletedTask;
}
}
Startup.cs ConfigureServices (or equivalent location if using .NET 6 / whatever)
public void ConfigureServices(IServiceCollection services)
{
// …
services.AddAuthentication(…)
.AddCookie(“Cookies”, options =>
{
// …
options.Events.OnValidatePrincipal = async context =>
{
if (context.Principal.Identity.IsAuthenticated)
{
// get user sub
var userSub = context.Principal.FindFirst(“sub”).Value;
// get user's tokens from server side token store
var tokenStore =
context.HttpContext.RequestServices.GetRequiredService<IServerSideTokenStore>();
var tokens = await tokenStore.GetTokenAsync(userSub);
if (tokens?.AccessToken == null
|| tokens?.Expiration == null
|| tokens?.RefreshToken == null)
{
// if we lack either an access or refresh token,
// then reject the Principal (forcing a round trip to the id server)
context.RejectPrincipal();
return;
}
// if the access token has expired, attempt to refresh it
if (tokens.Expiration < DateTimeOffset.UtcNow)
{
// we have a custom API client that takes care of refreshing our tokens
// and storing them in ServerSideTokenStore, we call that here
// …
// check the tokens have been updated
var newTokens = await tokenStore.GetTokenAsync(userSubProvider.UserSub);
if (newTokens?.AccessToken == null
|| newTokens?.Expiration == null
|| newTokens.Expiration < DateTimeOffset.UtcNow)
{
// if we lack an access token or it was not successfully renewed,
// then reject the Principal (forcing a round trip to the id server)
context.RejectPrincipal();
return;
}
}
}
}
}
.AddOpenIdConnect(“oidc”, options =>
{
// …
options.Events.OnTokenValidated = async n =>
{
var svc = n.HttpContext.RequestServices.GetRequiredService<IServerSideTokenStore>();
var culture = new CultureInfo(“EN”) ;
var exp = DateTimeOffset
.UtcNow
.AddSeconds(double.Parse(n.TokenEndpointResponse !.ExpiresIn, culture));
var userTokenProvider = new UserTokenProvider()
{
AcessToken = n.TokenEndpointResponse.AccessToken,
Expiration = exp,
RefreshToken = n.TokenEndpointResponse.RefreshToken
}
await svc.StoreTokensAsync(n.Principal.FindFirst(“sub”).Value, userTokenProvider);
};
// …
});
// …
}

Spring Cloud Gateway: Rate Limiting by parsing body

We've been using Spring Cloud Gateway for about 2 years in production. We're in the process of adding rate limiting support but we've run into a road block. One of the key endpoints we want to rate limit is our /oauth2/token endpoint. The trick here is this endpoint has credentials contained in the body that we need for our KeyResolver (to determine alleged identity). So we implemented the following KeyResolver:
public class OAuth2KeyResolver implements KeyResolver {
#Override
public Mono<String> resolve(ServerWebExchange exchange) {
String ipAddress = exchange.getRequest().getRemoteAddress().getHostName();
ServerRequest serverRequest = ServerRequest.create(exchange,
HandlerStrategies.withDefaults().messageReaders());
return serverRequest.bodyToMono(String.class).flatMap(requestBody -> {
JsonNode json = jackson.readTree(requestBody);
String clientId = json.path("clientId").asText(null);
return clientId != null ?
Mono.just(String.format("%s/%s", ipAddress, clientId) :
Mono.empty();
});
}
}
This works great to read the body and produce the desired key. However we quickly discovered that doing this consumes the body from the DataBuffer contained within the ServerHttpRequest. This seems to leave Spring Cloud Gateway unable to send the request to the downstream auth service.
Given the interface of this class, I don't think I can mutate the exchange instance to return the contents to the buffer.
How can I safely parse the body here?

How are Web API access tokens validated on the server?

Once a WebAPI access token is generated, how does WebAPI validate that token for the next request? I wonder if I can use an [Authorize] attribute, it must compare the token sent by the client with the token at the server side, if stored somewhere. Does it just check if token is present and not its value?
Bearer token
First of all, your Identity Provider or Token Provider which issues the authorization tokens needs to have the same machine key settings as the Web Api application for encryption/decryption:
<machineKey
decryptionKey="B7EFF1C5839A624ED0268917EDE82F408D2ECBFAC817"
validation="SHA1"
validationKey="C2B8DF31AB9624D8066DFDA1A479542825F3B48865C4E47AF6A026F22D853DEC2B3248DF268599BF89EF78B9E86CA05AC73577E0D5A14C45E0267588850B"
/> </system.web>
Because under the hood Bearertoken uses MachineKey encryption.
In other words if you dont have the same settings, your web api won't be able to decrypt the token (validate it).
This is done automatically by:
Microsoft.Owin.Security.OAuth.dll
using middleware.
You can use the Authorize Attribute on your web api controllers/actions if you want simple authorization with Usernames or roles like this:
[Authorize(Roles="Administrators,Managers",Users ="Mike,Laura")]
If you want custom authorization, then you have to implement a custom authorization attribute which will handle the custom authorization in your web api. If the user is not allowed to pass you will return a 401 UnAuthorized Response:
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
actionContext.Response.Headers.Add("WWW-Authenticate","Bearer location='http://localhost:8323/account/login'");
For example:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class CustomAuthorizeAttribute : System.Web.Http.Filters.AuthorizationFilterAttribute
{
public RulesExampleEnum[] Rules { get; set; }
public string Id { get; set; }
.....
// Summary:
// Calls when a process requests authorization.
//
// Parameters:
// actionContext:
// The action context, which encapsulates information for using System.Web.Http.Filters.AuthorizationFilterAttribute.
public virtual void OnAuthorization(HttpActionContext actionContext);
public virtual Task OnAuthorizationAsync(HttpActionContext actionContext, CancellationToken cancellationToken);
and register it in your webApiConfig.cs
config.Filters.Add(new CustomAuthorizeAttribute());
and apply it on Web Api controller or action:
[CustomAuthorize(Id = "AnyId", Rules = new RulesExampleEnum[] { RulesExampleEnum.Rule1, RulesExampleEnum.Rule3 })]
public IEnumerable<object> Get()
{...
Once access token is generated, client must include the access token inside Header for each request.
Client may set the access token inside Authorization HTTP Header.
On the server side, you should create class to handle the Authorization, which is a derived class from System.Web.Http.AuthorizeAttribute, something like below :
public class AuthorizationHandlerAttribute : AuthorizeAttribute
{
string AccessTokenFromRequest = "";
if (actionContext.Request.Headers.Authorization != null)
{
// get the access token
AccessTokenFromRequest = actionContext.Request.Headers.Authorization.Parameter;
}
string AccessTokenStored = "";
// write some code to get stored access token, probably from database
// then assign the value to a variable for later use
// compare access token
if (AccessTokenFromRequest != AccessTokenStored)
{
// if the token is not valid then return 401 Http Stasus
// or simply call base method
base.HandleUnauthorizedRequest(actionContext);
}
}
Then you use the newly created class and attach it on controller or action you wished to protect from unauthorized access.
public class UsersController : ApiController
{
[AuthorizationHandler]
public User Get(int id)
{
// only request with valid access token will reach this
}
}
The secret key is transmitted in the header of the request from the client to the server, and the contents are validated at the server at each request where the [Authorize] attribute is used.
You can use a tool like Fiddler from Telerik (free) to see the data being transported, but not the content (since its encrypted). Inspecting raw web traffic is invaluable when working with MVC / WebAPI, so I highly recommend it. Here's a link to Fiddler, although other similar tools exist as well.
http://www.telerik.com/fiddler
To answer the second part of your question, the server absolutely checks the contents of the secret key before allowing the request to proceed as authorized.

How do I authorize access to ServiceStack resources using OAuth2 access tokens via DotNetOpenAuth?

I've created an OAuth2 authorization server using DotNetOpenAuth, which is working fine - I'm using the resource owner password flow, and successfully exchanging user credentials for an access token.
I now want to use that access token to retrieve data from secure endpoints in a ServiceStack API, and I can't work out how to do so. I've examined the Facebook, Google, etc. providers included with ServiceStack but it's not clear whether I should be following the same pattern or not.
What I'm trying to achieve (I think!) is
OAuth client (my app) asks resource owner ('Catherine Smith') for credentials
Client submits request to authorization server, receives an access token
Client requests a secure resource from the resource server (GET /users/csmith/photos)
The access token is included in an HTTP header, e.g. Authorization: Bearer 1234abcd...
The resource server decrypts the access token to verify the identity of the resource owner
The resource server checks that the resource owner has access to the requested resource
The resource server returns the resource to the client
Steps 1 and 2 are working, but I can't work out how to integrate the DotNetOpenAuth resource server code with the ServiceStack authorization framework.
Is there an example somewhere of how I would achieve this? I've found a similar StackOverflow post at How to build secured api using ServiceStack as resource server with OAuth2.0? but it isn't a complete solution and doesn't seem to use the ServiceStack authorization provider model.
EDIT: A little more detail. There's two different web apps in play here. One is the authentication/authorisation server - this doesn't host any customer data (i.e. no data API), but exposes the /oauth/token method that will accept a username/password and return an OAuth2 access token and refresh token, and also provides token-refresh capability. This is built on ASP.NET MVC because it's almost identical to the AuthorizationServer sample included with DotNetOpenAuth. This might be replaced later, but for now it's ASP.NET MVC.
For the actual data API, I'm using ServiceStack because I find it much better than WebAPI or MVC for exposing ReSTful data services.
So in the following example:
the Client is a desktop application running on a user's local machine, the Auth server is ASP.NET MVC + DotNetOpenAuth, and the Resource server is ServiceStack
The particular snippet of DotNetOpenAuth code that's required is:
// scopes is the specific OAuth2 scope associated with the current API call.
var scopes = new string[] { "some_scope", "some_other_scope" }
var analyzer = new StandardAccessTokenAnalyzer(authServerPublicKey, resourceServerPrivateKey);
var resourceServer = new DotNetOpenAuth.OAuth2.ResourceServer(analyzer);
var wrappedRequest = System.Web.HttpRequestWrapper(HttpContext.Current.Request);
var principal = resourceServer.GetPrincipal(wrappedRequest, scopes);
if (principal != null) {
// We've verified that the OAuth2 access token grants this principal
// access to the requested scope.
}
So, assuming I'm on the right track, what I need to do is to run that code somewhere in the ServiceStack request pipeline, to verify that the Authorization header in the API request represents a valid principal who has granted access to the requested scope.
I'm starting to think the most logical place to implement this is in a custom attribute that I use to decorate my ServiceStack service implementations:
using ServiceStack.ServiceInterface;
using SpotAuth.Common.ServiceModel;
namespace SpotAuth.ResourceServer.Services {
[RequireScope("hello")]
public class HelloService : Service {
public object Any(Hello request) {
return new HelloResponse { Result = "Hello, " + request.Name };
}
}
}
This approach would also allow specifying the scope(s) required for each service method. However, that seems to run rather contrary to the 'pluggable' principle behind OAuth2, and to the extensibility hooks built in to ServiceStack's AuthProvider model.
In other words - I'm worried I'm banging in a nail with a shoe because I can't find a hammer...
OK, after a lot of stepping through the various libraries with a debugger, I think you do it like this: https://github.com/dylanbeattie/OAuthStack
There's two key integration points. First, a custom filter attribute that's used on the server to decorate the resource endpoints that should be secured with OAuth2 authorization:
/// <summary>Restrict this service to clients with a valid OAuth2 access
/// token granting access to the specified scopes.</summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true)]
public class RequireOAuth2ScopeAttribute : RequestFilterAttribute {
private readonly string[] oauth2Scopes;
public RequireOAuth2ScopeAttribute(params string[] oauth2Scopes) {
this.oauth2Scopes = oauth2Scopes;
}
public override void Execute(IHttpRequest request, IHttpResponse response, object requestDto) {
try {
var authServerKeys = AppHostBase.Instance.Container.ResolveNamed<ICryptoKeyPair>("authServer");
var dataServerKeys = AppHostBase.Instance.Container.ResolveNamed<ICryptoKeyPair>("dataServer");
var tokenAnalyzer = new StandardAccessTokenAnalyzer(authServerKeys.PublicSigningKey, dataServerKeys.PrivateEncryptionKey);
var oauth2ResourceServer = new DotNetOpenAuth.OAuth2.ResourceServer(tokenAnalyzer);
var wrappedRequest = new HttpRequestWrapper((HttpRequest)request.OriginalRequest);
HttpContext.Current.User = oauth2ResourceServer.GetPrincipal(wrappedRequest, oauth2Scopes);
} catch (ProtocolFaultResponseException x) {
// see the GitHub project for detailed error-handling code
throw;
}
}
}
Second, this is how you hook into the ServiceStack HTTP client pipeline and use DotNetOpenAuth to add the OAuth2 Authorization: Bearer {key} token to the outgoing request:
// Create the ServiceStack API client and the request DTO
var apiClient = new JsonServiceClient("http://api.mysite.com/");
var apiRequestDto = new Shortlists { Name = "dylan" };
// Wire up the ServiceStack client filter so that DotNetOpenAuth can
// add the authorization header before the request is sent
// to the API server
apiClient.LocalHttpWebRequestFilter = request => {
// This is the magic line that makes all the client-side magic work :)
ClientBase.AuthorizeRequest(request, accessTokenTextBox.Text);
}
// Send the API request and dump the response to our output TextBox
var helloResponseDto = apiClient.Get(apiRequestDto);
Console.WriteLine(helloResponseDto.Result);
Authorized requests will succeed; requests with a missing token, expired token or insufficient scope will raise a WebServiceException
This is still very much proof-of-concept stuff, but seems to work pretty well. I'd welcome feedback from anyone who knows ServiceStack or DotNetOpenAuth better than I do.
Update
On further reflection, your initial thought, to create a RequiredScope attribute would be a cleaner way to go. Adding it to the ServiceStack pipeline is as easy as adding the IHasRequestFilter interface, implementing a custom request filter, as documented here: https://github.com/ServiceStack/ServiceStack/wiki/Filter-attributes
public class RequireScopeAttribute : Attribute, IHasRequestFilter {
public void RequireScope(IHttpRequest req, IHttpResponse res, object requestDto)
{
//This code is executed before the service
//Close the request if user lacks required scope
}
...
}
Then decorate your DTO's or Services as you've outlined:
using ServiceStack.ServiceInterface;
using SpotAuth.Common.ServiceModel;
namespace SpotAuth.ResourceServer.Services {
[RequireScope("hello")]
public class HelloService : Service {
public object Any(Hello request) {
return new HelloResponse { Result = "Hello, " + request.Name };
}
}
}
Your RequireScope custom filter would be almost identical to ServiceStack's RequiredRoleAttribute implementation., so use it as a starting point to code from.
Alternately, you could map scope to permission. Then decorate your DTO or service accordingly (see SS wiki for details) for example:
[Authenticate]
[RequiredPermission("Hello")]
public class HelloService : Service {
public object Any(Hello request) {
return new HelloResponse { Result = "Hello, " + request.Name };
}
}
Normally ServiceStack calls the method bool HasPermission(string permission) in IAuthSession. This method checks if the list List Permissions in IAuthSession contains the required permission, so, in a custom IAuthSession you could override HasPermission and put your OAuth2 scopes checking there.

SingnalR with Redis Send Msg to Specific ConnectionID

I am using SingalR for my Chat Application. Wanted to play with Redis
and SignalR but I cannot find an working example where i can send msg to
specific connectionId. Below Code that works for a single server instance.
But when i make it a Web Garden with 3 process it stops working as my
server instance that gets the message cannot find the connectionId
for that destination UserId to send the message.
private readonly static ConnectionMapping<string> _connections = new ConnectionMapping<string>();
public void Send(string sendTo, string message, string from)
{
string fromclientid = Context.QueryString["clientid"];
foreach (var connectionId in _connections.GetConnections(sendTo))
{
Clients.Client(connectionId).send(fromclientid, message);
}
Clients.Caller.send(sendTo, "me: " + message);
}
public override Task OnConnected()
{
int clientid = Convert.ToInt32(Context.QueryString["clientid"]);
_connections.Add(clientid.ToString(), Context.ConnectionId);
}
I have used the example below to setup my box and code but none of
them have examples for sending from one client to specific client or
group of specific clients.
http://www.asp.net/signalr/overview/performance-and-scaling/scaleout-with-redis
https://github.com/mickdelaney/SignalR.Redis/tree/master/Redis.Sample
The ConnectionMapping instance in your Hub class will not synced across different SignalR server instances. You need to use permanent external storage such as a database or a Windows Azure table. Refer to this link for more details:
http://www.asp.net/signalr/overview/hubs-api/mapping-users-to-connections

Resources