Orleans two way communication between grain client and grain - orleans

I would like to implement a callback to a grain client inside a grain method Eg. during evaluation of Grain.Method1 a client method needs to be called to get some data.
I've tried to do so with streams, but when I subscribe the stream on client, the method does not fire.
Grain:
var config = ClusterConfiguration.LocalhostPrimarySilo();
config.AddMemoryStorageProvider();
config.Globals.RegisterStorageProvider<MemoryStorage>("PubSubStore");
config.Globals.RegisterStreamProvider<SimpleMessageStreamProvider>("MySMSProvider");
...
public override async Task OnActivateAsync() {
var streamProvider = GetStreamProvider("MySMSProvider");
var stream = streamProvider.GetStream<MyTypeMessage>(myGuid, "MyStream");
RegisterTimer(s => {
return stream.OnNextAsync(new MyTypeMessage());
}, null, TimeSpan.FromMilliseconds(1000), TimeSpan.FromMilliseconds(1000));
...
Client:
var clientConfiguration = ClientConfiguration.LocalhostSilo();
clientConfiguration.RegisterStreamProvider<SimpleMessageStreamProvider>("MySMSProvider");
GrainClient.Initialize(clientConfiguration);
...
var streamProvider = GrainClient.GetStreamProvider("MySMSProvider");
var stream = requestStreamProvider.GetStream<MyTypeMessage>(myGuid, "MyStream");
await stream.SubscribeAsync(
async (message, token) => { process message that does not fire });

Looks like you are not keeping the reference to StreamSubscriptionHandle<MyTypeMessage> which is returned by subscription
Try something like this :
var subscriptionHandle = await _factCalculationRequestStream
.SubscribeAsync(async (message, token) => { process message that does not fire });
and keep the subscriptionHandle from being garbage collected
In my case I ended up implementing IGrainObserver on my class (which is a websocketBehaviour in a WebAPI project and this subscription works well:
public class MySocketService: WebSocketBehavior, IGrainObserver, IDisposable
{
private StreamSubscriptionHandle<MyStreamEvent>
...
//inside some method invoked externally
_streamSubscriberHandle = await requestedStream
.SubscribeAsync(onStreamMessage, onStreamError, onStreamComplete);
}

Related

Identity Server 4 Multi-tenancy logout

I'm currently developing an identity server. It is multi-tenant with multiple user repositories.
I am able to pass (using Services.OpenIDConnect.Options) my tenant details from my MVC to the IDS in order to select the appropriate user repository on login
options.Events.OnRedirectToIdentityProvider = context =>
{
context.ProtocolMessage.SetParameter("Tenant", "TenantDetail");
return Task.CompletedTask;
};
I am attempting to obtain this same information for logout, however the initial call to logout has some back end process that calls CustomProfileService.IsActiveAsync(IsActiveContext context).
I am unable to obtain the tenant information from the IsActiveContext, nor am i able to read any kind of query string (as i was using for login).
Any suggestions, or even alternative methods that might be more correct than what I'm attempting, would be greatly appreciated.
OnRedirectToIdentityProvider will not be hit on signout. You'll need to pass the tenant information in the OnRedirectToIdentityProviderForSignOut event in your client instead.
Here's a snippet, that's far from complete:
services
.AddOpenIdConnect("oidc", options =>
{
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProviderForSignOut = context =>
{
context.ProtocolMessage.AcrValues = "tenant:TenantDetail";
return Task.CompletedTask;
},
}
}
In IdentityServer you'll need to lookup the acr_values in the query parameters from the request. Inject IHttpContextAccessor in order to access the context:
public class ProfileService : IProfileService
{
private readonly IHttpContextAccessor _httpContextAccessor;
public ProfileService(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
// ...
}
public async Task IsActiveAsync(IsActiveContext context)
{
// Please note that this method is called on many occasions. Check context.Caller
// This means that you'll have to make sure that the acr_valus are present on all
// ocassions, hence the question in my comment.
var request = _httpContextAccessor.HttpContext.Request;
if (request.Method == HttpMethods.Get)
{
// acr_values should be present on all ocassions.
var values = (string)request.Query["acr_values"];
// This is just a sample, you'll need to parse the values.
var tenant = values.Split(':')[1];
}
// Your code where you link the repository ...
var sub = context.Subject.GetSubjectId();
var user = await userManager.FindByIdAsync(sub);
context.IsActive = user != null;
}
}
Please let me know if this solves the issue for you.

Is it possible to use Audit.Net with httpClient to capture external requests

Using Audit.Net is it possible to create an audit scope for httpClient requests, in a similar way to the MVC.Core or WebAPI.Core Middleware?
I've tried something like this but not having much success, generally it'll timeout the app.
AuditScope scope = null;
try {
using(HttpClient client = new HttpClient) {
scope = await AuditScope.CreateAsync("",() => client)
// code to initialise the Httpclient
}
}
finally {
await scope.DisposeAsync();
}
I think the only option to hook into the HttpClient is to use a custom HttpClientHandler so you can intercept the rest calls.
Just as an example:
public class AuditClientHandler : HttpClientHandler
{
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var options = new AuditScopeOptions()
{
EventType = $"{request.Method.Method} {request.RequestUri.AbsoluteUri}",
CreationPolicy = EventCreationPolicy.InsertOnStartReplaceOnEnd,
ExtraFields = new
{
request = GetRequestAudit(request)
}
};
using (var scope = AuditScope.Create(options))
{
var response = await base.SendAsync(request, cancellationToken);
scope.SetCustomField("response", GetResponseAudit(response));
return response;
}
}
}
I've used the InsertOnStartReplaceOnEnd creation policy, so the request is saved before it's sent to the server, and the response is added to the event and saved afterwards.
The implementation of GetRequestAudit / GetResponseAudit is up to you, just return an object (that can be serialized) with the information you want to log.
So each time you need to audit an HttpClient instance, you need to pass the handler to its constructor:
var cli = new HttpClient(new AuditClientHandler());
var response = await cli.GetAsync("http://google.com");
Anyway I will evaluate providing a new library (Audit.HttpClient?) with a configurable Handler so the implementation could be cleaner.
Update
You can now use the Audit.HttpClient extension for a cleaner implementation. Take a look at the documentation here

Can't call web api controller inside the signalr OnDisconnected method

I have an mvc web apllication with signalr and i want to update the table in the published web api.
calling web api controller to get users inside the Onconnected method works fine:
public override async Task OnConnected()
{
var users = await _client.GetAsync("chats/users");
Clients.All.userConnected();
}
But when i place the code inside the OnDisconnected method it gives me an error:
public override async Task OnDisconnected(bool stopCalled)
{
var users = await _client.GetAsync("chats/users");
}
Why is this happening? this is the whole Hub code:
private static IHubContext hubContext = GlobalHost.ConnectionManager.GetHubContext<ChatHub>();
private HttpClient _client;
public ChatHub()
{
AccessDelegatingHandler handler = new AccessDelegatingHandler();
_client = HttpClientFactory.Create(handler);
_client.BaseAddress = new Uri(ClsConfig.GetConfiguration().APIBaseAddress);
}
// Send new message to group chat.
public static void SendGroupMessage(MessageDetailModel messageDetail)
{
hubContext.Clients.All.newGroupMessageReceived(messageDetail);
}
public override async Task OnConnected()
{
var users = await _client.GetAsync("chats/users");
Clients.All.userConnected();
}
public override Task OnReconnected()
{
return base.OnReconnected();
}
public override async Task OnDisconnected(bool stopCalled)
{
var users = await _client.GetAsync("chats/users");
}
EDIT:
I found out that when i place var user = Context.User.Identity; inside the OnDisconnected method the user is IsAuthenticated = true but when i place a break point inside the AccessDelegatingHandler class the var identity = (ClaimsIdentity)HttpContext.Current.User.Identity; line gives an error and is IsAuthenticated = false
By the time the onDisconnected event fires, you are likely already disconnected, and there is no guarantee that your code will run, (its a known issue with Signalr) also are you monitoring the onDisconnected in the client or the server? It looks like you are trying to handle it from the server, and you should be handling it from the client.
This link should help to understand why this is the way it is.
https://learn.microsoft.com/en-us/aspnet/signalr/overview/guide-to-the-api/handling-connection-lifetime-events#clientdisconnect

Async tast not working as it should

I am trying to do webservice async so ui can respond and websevice call is done in background, but since i am little inexpirienced with async stuff i need little help.
Here is my code:
Inside my action result i have call to method which have asyinc stuff in it
public ActionResult SavePackage(string ParcelNumber)
{
/////some other stuff
SaveAsync(ParcelNumber);
}
And
then i have async method :
public async Task SaveAsync(string ParcelNumber)
{
await api.RegisterPackage(ParcelNumber);
}
Which calls api:
public async Task RegisterPackage(string ParcelNumber)
{
var rk = "some api http";
HttpWebRequest request = WebRequest.Create(rk) as HttpWebRequest;
request.Headers.Add("cache-control", "no-cache");
request.Method = "POST";
request.ContentType = "application/json";
string data = "{\n \"ParcelNumber\": \"" + ParcelNumber+ "\"}";
byte[] dataStream = Encoding.UTF8.GetBytes(data);
Stream newStream = request.GetRequestStream();
// Send the data.
newStream.Write(dataStream, 0, dataStream.Length);
newStream.Close();
Task<WebResponse> getResponseTask = Task<WebResponse>.Factory.FromAsync(request.BeginGetResponse, request.EndGetResponse, null);
await getResponseTask.ContinueWith(getResponseAntecedent =>
{
WebResponse webResponse = getResponseAntecedent.Result;
using (var reader = new StreamReader(webResponse.GetResponseStream()))
{
string error = reader.ReadToEnd();
//TODO: use JSON.net to parse this string and look at the error message
var myDeserializedObjList3 = Newtonsoft.Json.JsonConvert.DeserializeObject<RootObjectAtt>(error);
var isValid = myDeserializedObjList3.IsValid;
var ModelErrors = myDeserializedObjList3.ModelErrors;
var ValidationErrors = myDeserializedObjList3.ValidationErrors;
}
});
}
My problem is that the above code is still waiting for response to finish (and that can take about 20 second), and i would like if possible when i call the api i could go back to my ui and let ppl do their stuff while api is proccessed in background.
Anybody have any suggestion?
As I describe on my blog, async does not change the HTTP protocol.
First, the best solution to your problem is to not change your ASP.NET action method at all. Instead, have the actual UI application issue the call asynchronously. If your UI app is a .NET app, then it can use async/await; if it's a web app (i.e., JavaScript), then it can use an AJAX call. Another good option is to introduce SignalR, which allows the server to signal when the work is done.
If you really want to run ASP.NET code outside of a request (which is never recommended), then you can use one of the techniques I describe on my blog for ASP.NET fire-and-forget.

ServiceStack authentication request fails

I am trying to set up authentication with my ServiceStack service by following this tutorial.
My service is decorated with the [Authenticate] attribute.
My AppHost looks like this:
public class TestAppHost : AppHostHttpListenerBase
{
public TestAppHost() : base("TestService", typeof(TestService).Assembly) { }
public static void ConfigureAppHost(IAppHost host, Container container)
{
try
{
// Set JSON web services to return idiomatic JSON camelCase properties.
ServiceStack.Text.JsConfig.EmitCamelCaseNames = true;
// Configure the IOC container
IoC.Configure(container);
// Configure ServiceStack authentication to use our custom authentication providers.
var appSettings = new AppSettings();
host.Plugins.Add(new AuthFeature(() =>
new AuthUserSession(), // use ServiceStack's session class but fill it with our own data using our own auth service provider
new IAuthProvider[] {
new UserCredentialsAuthProvider(appSettings)
}));
}
}
where UserCredentialsAuthProvider is my custom credentials provider:
public class UserCredentialsAuthProvider : CredentialsAuthProvider
{
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
try
{
// Authenticate the user.
var userRepo = authService.TryResolve<IUserRepository>();
var user = userRepo.Authenticate(userName, password);
// Populate session properties.
var session = authService.GetSession();
session.IsAuthenticated = true;
session.CreatedAt = DateTime.UtcNow;
session.DisplayName = user.FullName;
session.UserAuthName = session.UserName = user.Username;
session.UserAuthId = user.ID.ToString();
}
catch (Exception ex)
{
// ... Log exception ...
return false;
}
return true;
}
}
In my user tests I initialize and start my TestAppHost on http://127.0.0.1:8888, then use JsonServiceClient to authenticate itself to the service like so:
var client = new JsonServiceClient("http://127.0.0.1:8888/")
var response = client.Send<AuthResponse>(new Auth
{
provider = UserCredentialsAuthProvider.Name,
UserName = username,
Password = password,
RememberMe = true
});
But getting the following exception:
The remote server returned an error: (400) Bad Request.
at System.Net.HttpWebRequest.GetResponse()
at ServiceStack.ServiceClient.Web.ServiceClientBase.Send[TResponse](Object request)...
The ServiceStack.ServiceInterface.Auth.Auth request contains the correct username and passsword, and the request is being posted to:
http://127.0.0.1:8888/json/syncreply/Auth
I am not sure why the URL is not /json/auth/credentials or what I might be doing wrong. Any suggestions?
UPDATE
Tracing the chain of events up the stack I found the following:
JsonDataContractSerializer.SerializeToStream correctly serializes the Auth request into Json. However, the System.Net.HttpRequestStream passed to JsonDataContractDeserializer by EndpointHandlerBase has a stream of the correct length that is filled with nulls (zero bytes). As a result, the request object passed to CredentialsAuthProvider.Authenticate has nulls in all its properties.
How can the HTTP stream get stripped of its data?
Got it!!!
The problem was the following pre-request filter that I added for logging purposes in TestAppHost.Configure:
PreRequestFilters.Add((httpReq, httpRes) =>
{
LastRequestBody = httpReq.GetRawBody();
});
as seen here.
When the GetRawBody() method reads the request InputStream it leaves it in the EOS state, and all subsequent read attempts return nothing.
So obviously GetRawBody() can only be safely used with buffered streams, but unfortunately it quietly causes a very nasty bug instead of throwing an exception when used with a non-buffered stream.

Resources