This is my problem :
I want call signalR function from controller to get list of users connected.
In my OnConnected(), OnDisconnected method I collect the correct data but when I create my own method i can't get the list of Users.
I feel I have a problem with my context because my list is empty but I don't know really why.
I have try somes test :
This a counter of number users connected in my application with SignalR.
The list of users is fill with the OnConnected() method and clean with OnDisconnected()
OnConnected() an user is add:
public override async Task OnConnected()
{
var currentCollab = Context.User.Identity.Name.Length > 0 ? Context.User.Identity.Name : "";
var module = Context.QueryString["module"];
if (!string.IsNullOrEmpty(module))
{
SignalRUsers.Add(new UserConnected()
{
ConnectionId = Context.ConnectionId,
UserName = currentCollab,
ModuleActif = module
});
}
await Clients.All.UpdateCountAccueil(SignalRUsers.Count(x => x.ModuleActif.ToUpper().Equals(ModuleName.Accueil.Value)), SignalRUsers.Where(x => x.ModuleActif.ToUpper().Equals(ModuleName.Accueil.Value)));
await base.OnConnected();
}
In this method SignalRUsers the list is filled with all previous connections but as soon as I try to retrieve the values from this list in a method to call it in my controller this list is completely empty (while there are many active connections)
Complete Hub Class :
using System;
using System.Activities.Statements;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Util;
using System.Windows.Forms;
using ConvergenceCore.ServiceSettingsManager;
using ConvergenceDataAccess.ActiveDirectory;
using Microsoft.AspNet.SignalR;
namespace Main
{
public class ChatHub : Hub
{
private static long counterAccueil = 0;
private static long counterParam = 0;
static Dictionary<string, int> CurrentConnections = new Dictionary<string, int>();
static List<UserConnected> SignalRUsers = new List<UserConnected>();
public void Send(string module, string etat, string message)
{
// Call the broadcastMessage method to update clients.
Clients.All.broadcastMessage(module, etat, message);
}
public override async Task OnReconnected()
{
var currentCollab = Context.User.Identity.Name.Length > 0 ? Context.User.Identity.Name : "";
var module = Context.QueryString["module"];
if (!string.IsNullOrEmpty(module))
{
SignalRUsers.Add(new UserConnected()
{
ConnectionId = Context.ConnectionId,
UserName = currentCollab,
ModuleActif = module
});
}
await Clients.All.UpdateCountAccueil(SignalRUsers.Count(x => x.ModuleActif.ToUpper().Equals(ModuleName.Accueil.Value)), SignalRUsers.Where(x => x.ModuleActif.ToUpper().Equals(ModuleName.Accueil.Value)));
await Clients.All.UpdateCountParam(SignalRUsers.Count(x => x.ModuleActif.ToUpper().Equals(ModuleName.Param.Value)), SignalRUsers.Where(x => x.ModuleActif.ToUpper().Equals(ModuleName.Param.Value)));
await Clients.All.UpdateCountRh(SignalRUsers.Count(x => x.ModuleActif.ToUpper().Equals(ModuleName.Rh.Value)), SignalRUsers.Where(x => x.ModuleActif.ToUpper().Equals(ModuleName.Rh.Value)));
await Clients.All.UpdateCountFacturation(SignalRUsers.Count(x => x.ModuleActif.ToUpper().Equals(ModuleName.Facturation.Value)), SignalRUsers.Where(x => x.ModuleActif.ToUpper().Equals(ModuleName.Facturation.Value)));
await base.OnReconnected();
}
public override async Task OnConnected()
{
var currentCollab = Context.User.Identity.Name.Length > 0 ? Context.User.Identity.Name : "";
var module = Context.QueryString["module"];
if (!string.IsNullOrEmpty(module))
{
SignalRUsers.Add(new UserConnected()
{
ConnectionId = Context.ConnectionId,
UserName = currentCollab,
ModuleActif = module
});
}
await Clients.All.UpdateCountAccueil(SignalRUsers.Count(x => x.ModuleActif.ToUpper().Equals(ModuleName.Accueil.Value)), SignalRUsers.Where(x => x.ModuleActif.ToUpper().Equals(ModuleName.Accueil.Value)));
await Clients.All.UpdateCountParam(SignalRUsers.Count(x => x.ModuleActif.ToUpper().Equals(ModuleName.Param.Value)), SignalRUsers.Where(x => x.ModuleActif.ToUpper().Equals(ModuleName.Param.Value)));
await Clients.All.UpdateCountRh(SignalRUsers.Count(x => x.ModuleActif.ToUpper().Equals(ModuleName.Rh.Value)), SignalRUsers.Where(x => x.ModuleActif.ToUpper().Equals(ModuleName.Rh.Value)));
await Clients.All.UpdateCountFacturation(SignalRUsers.Count(x => x.ModuleActif.ToUpper().Equals(ModuleName.Facturation.Value)), SignalRUsers.Where(x => x.ModuleActif.ToUpper().Equals(ModuleName.Facturation.Value)));
await base.OnConnected();
}
public override async Task OnDisconnected(bool stopCalled)
{
var userToDelete = SignalRUsers.FirstOrDefault(x => x.ConnectionId == Context.ConnectionId);
SignalRUsers.Remove(userToDelete);
//CurrentConnections.Remove(module + Context.ConnectionId);
await Clients.All.UpdateCountAccueil(SignalRUsers.Count(x => x.ModuleActif.ToUpper().Equals(ModuleName.Accueil.Value)), SignalRUsers.Where(x => x.ModuleActif.ToUpper().Equals(ModuleName.Accueil.Value)));
await Clients.All.UpdateCountParam(SignalRUsers.Count(x => x.ModuleActif.ToUpper().Equals(ModuleName.Param.Value)), SignalRUsers.Where(x => x.ModuleActif.ToUpper().Equals(ModuleName.Param.Value)));
await Clients.All.UpdateCountRh(SignalRUsers.Count(x => x.ModuleActif.ToUpper().Equals(ModuleName.Rh.Value)), SignalRUsers.Where(x => x.ModuleActif.ToUpper().Equals(ModuleName.Rh.Value)));
await Clients.All.UpdateCountFacturation(SignalRUsers.Count(x => x.ModuleActif.ToUpper().Equals(ModuleName.Facturation.Value)), SignalRUsers.Where(x => x.ModuleActif.ToUpper().Equals(ModuleName.Facturation.Value)));
await base.OnDisconnected(stopCalled);
}
public List<UserConnected> GetListUsersConnected(string module)
{
return SignalRUsers.Where(x => x.ModuleActif == module).ToList();
}
}
public class UserConnected
{
public string UserName { get; set; }
public string ConnectionId { get; set; }
public string ModuleActif { get; set; }
}
}
In my Controller :
public PartialViewResult DetailUsersConnected(string module)
{
ChatHub hub = new ChatHub();
var listUsers = hub.GetListUsersConnected(module);
return PartialView("../Parametrages/Content/_DetailsUserConnected", listUsers);
}
Why is my list empty in my GetListUsersConnected method ?
Why I do not recover met previous connection as in OnConnected() for example ?
Every time a client connects a new instance of the Hub object is created. If your SignalRUsers is a property of the hub, then is created from scratch every time. Try make it a static property or use a Singleton in a container.
Related
I have had some problems with my code is throwing lots of errors during the testing phase (2/10). But I don't understand why. Whereas when I launch my code and access those path everything works fine.
Here is a screenshot of the errors:
Here is my System.cs:
using Shard.Shared.Core;
using System;
using System.Collections.Generic;
namespace Shard.Shared.Web.API
{
public class Systems
{
public List<SystemSpecification> MySystems { get; set; }
}
}
Here is my SystemController.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Shard.Shared.Core;
using Xunit;
namespace Shard.Shared.Web.API.Controllers
{
[ApiController]
[Route("[controller]")]
public class SystemsController : ControllerBase
{
public Systems systemList = new Systems();
public void CreateUniverse()
{
MapGeneratorOptions options = new MapGeneratorOptions
{
Seed = "Val Halla"
};
MapGenerator map = new MapGenerator(options);
var array = map.Generate().Systems.ToArray();
systemList.MySystems = array.ToList();
}
[HttpGet]
public IEnumerable<Systems> Get()
{
CreateUniverse();
/*return Enumerable.Range(1, 5).Select(index => new Systems {
Sectors = map.Generate(),
MySystems = array.ToList()
})*/ yield return systemList;
}
[HttpGet("{sectorName}")]
public IActionResult GetSector(string sectorName)
{
CreateUniverse();
if (String.IsNullOrWhiteSpace(sectorName))
{
return StatusCode(404);
}
SystemSpecification findSector = systemList.MySystems.FirstOrDefault((p) => p.Name == sectorName);
if (findSector == null)
{
return StatusCode(404);
}
return StatusCode(200, findSector);
}
[HttpGet("{sectorName}/planets")]
public IActionResult GetSectorPlanets(string sectorName)
{
CreateUniverse();
if (String.IsNullOrWhiteSpace(sectorName))
{
return StatusCode(404);
}
var findSector = systemList.MySystems.FirstOrDefault((p) => p.Name == sectorName);
if (findSector == null)
{
return StatusCode(404);
}
var planets = findSector.Planets;
if (planets == null)
{
return StatusCode(404);
}
Assert.NotNull(findSector);
return StatusCode(200,planets);
}
[HttpGet("{sectorName}/planets/{planetName}")]
public IActionResult GetSectorPlanets_SpecificPlanet(string sectorName, string planetName)
{
CreateUniverse();
if (sectorName.Trim().Length == 0 || planetName.Trim().Length == 0)
{
return StatusCode(404);
}
else
{
/*var findSector = systemList.MySystems.FirstOrDefault((p) => p.Name == sectorName);
var findPlanetName = findSector.Planets.FirstOrDefault((p) => p.Name == planetName);*/
foreach (SystemSpecification system in systemList.MySystems)
{
if (system.Name == sectorName)
{
foreach (PlanetSpecification planet in system.Planets)
{
if (planet.Name == planetName)
{
return StatusCode(200, planet);
}
}
}
}
return StatusCode(404);
/*if (findSector == null || findPlanetName == null)
{
return StatusCode(404);
}*/
//return StatusCode(200, findPlanetName);
}
}
}
}
I tested all these cases when I launch the program but when it comes to respecting the test cases it's mainly the errors it's displaying that I barely understand. So please I will really appreciate if you guys can help me out.
These are the test cases (BaseIntegrationTests.SectorTests.cs):
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Xunit;
namespace Shard.Shared.Web.IntegrationTests
{
public partial class BaseIntegrationTests<TEntryPoint, TWebApplicationFactory>
{
[Fact]
[Trait("grading", "true")]
[Trait("version", "1")]
public async Task CanReadSystems()
{
using var client = factory.CreateClient();
using var response = await client.GetAsync("systems");
await response.AssertSuccessStatusCode();
var array = await response.Content.ReadAsAsync<JArray>();
Assert.NotEmpty(array);
}
[Fact]
[Trait("grading", "true")]
[Trait("version", "1")]
public async Task SystemsHaveNames()
{
using var client = factory.CreateClient();
using var response = await client.GetAsync("systems");
await response.AssertSuccessStatusCode();
var array = await response.Content.ReadAsAsync<JArray>();
Assert.NotNull(array[0]["name"]);
Assert.Equal(JTokenType.String, array[0]["name"].Type);
}
[Fact]
[Trait("grading", "true")]
[Trait("version", "1")]
public async Task SystemsHavePlanets()
{
using var client = factory.CreateClient();
using var response = await client.GetAsync("systems");
await response.AssertSuccessStatusCode();
var array = await response.Content.ReadAsAsync<JArray>();
Assert.NotNull(array[0]["planets"]);
Assert.Equal(JTokenType.Array, array[0]["planets"].Type);
Assert.NotEmpty(array[0]["planets"]);
}
[Fact]
[Trait("grading", "true")]
[Trait("version", "1")]
public async Task PlanetsHaveNames()
{
using var client = factory.CreateClient();
using var response = await client.GetAsync("systems");
await response.AssertSuccessStatusCode();
var array = await response.Content.ReadAsAsync<JArray>();
Assert.NotNull(array[0]["planets"][0]["name"]);
Assert.Equal(JTokenType.String, array[0]["planets"][0]["name"].Type);
var names = array.SelectTokens("$[*].planets[*].name").Select(token => token.Value<string>());
}
[Fact]
[Trait("grading", "true")]
[Trait("version", "1")]
public async Task PlanetsHaveSizes()
{
using var client = factory.CreateClient();
using var response = await client.GetAsync("systems");
await response.AssertSuccessStatusCode();
var array = await response.Content.ReadAsAsync<JArray>();
Assert.NotNull(array.SelectToken("[0].planets[0].size"));
Assert.Equal(JTokenType.Integer, array.SelectToken("[0].planets[0].size").Type);
Assert.Equal(JTokenType.Integer, array.SelectToken("[0].planets[0].size").Type);
}
[Fact]
[Trait("grading", "true")]
[Trait("version", "2")]
public async Task PlanetsDoNotHaveResources()
{
using var client = factory.CreateClient();
using var response = await client.GetAsync("systems");
await response.AssertSuccessStatusCode();
var array = await response.Content.ReadAsAsync<JArray>();
var allPlanets = array.SelectTokens("[*].planets[*]").Cast<IDictionary<string, JToken>>();
var allProperties = allPlanets.SelectMany(planet => planet.Keys).Distinct();
Assert.DoesNotContain("resource", string.Join(",", allProperties));
}
public async Task<JToken> GetFirstSystem()
{
using var client = factory.CreateClient();
using var systemsResponse = await client.GetAsync("systems");
await systemsResponse.AssertSuccessStatusCode();
var systems = await systemsResponse.Content.ReadAsAsync<JArray>();
var system = systems.FirstOrDefault();
Assert.NotNull(system);
return system;
}
[Fact]
[Trait("grading", "true")]
[Trait("version", "1")]
public async Task CanFetchOneSystem()
{
var system = await GetFirstSystem();
using var client = factory.CreateClient();
using var response = await client.GetAsync("systems/" + system["name"].Value<string>());
await response.AssertSuccessStatusCode();
Assert.Equal(system.ToString(), (await response.Content.ReadAsAsync<JToken>()).ToString());
}
[Fact]
[Trait("grading", "true")]
[Trait("version", "1")]
public async Task CanFetchPlanetsOfOneSystem()
{
var system = await GetFirstSystem();
using var client = factory.CreateClient();
using var response = await client.GetAsync("systems/" + system["name"].Value<string>() + "/planets");
await response.AssertSuccessStatusCode();
Assert.Equal(system["planets"].ToString(), (await response.Content.ReadAsAsync<JToken>()).ToString());
}
[Fact]
[Trait("grading", "true")]
[Trait("version", "1")]
public async Task CanFetchOnePlanet()
{
var system = await GetFirstSystem();
var planet = system["planets"][0];
using var client = factory.CreateClient();
using var response = await client.GetAsync(
"systems/" + system["name"].Value<string>() + "/planets/" + planet["name"].Value<string>());
await response.AssertSuccessStatusCode();
Assert.Equal(planet.ToString(), (await response.Content.ReadAsAsync<JToken>()).ToString());
}
[Fact]
[Trait("grading", "true")]
public async Task NonExistingSectorReturns404()
{
using var client = factory.CreateClient();
using var response = await client.GetAsync("systems");
await response.AssertSuccessStatusCode();
var array = await response.Content.ReadAsAsync<JArray>();
Assert.NotNull(array.SelectToken("[0].planets[0].size"));
Assert.Equal(JTokenType.Integer, array.SelectToken("[0].planets[0].size").Type);
Assert.Equal(JTokenType.Integer, array.SelectToken("[0].planets[0].size").Type);
}
}
}
I can't really figure out the error messages.
Thanks
I can't seem to get my code work, although I tried several different approaches. Here is my preferred code snippet:
var client = await ApiClientProvider.GetApiClient();
var freeToPlayChampions = await client.GetChampionsAsync(true, Region);
var championsData = freeToPlayChampions.Select(x =>
client.GetStaticChampionByIdAsync(
(int)x.Id,
platformId: Region));
ConsoleTable.From(await Task.WhenAll(championsData)).Write();
When debugging I see that the code hangs on await Task.WhenAll(championsData). So i tried to make the code more easy:
var client = await ApiClientProvider.GetApiClient();
var freeToPlayChampions = await client.GetChampionsAsync(true, Region);
var table = new ConsoleTable();
foreach(var freeToPlayChampion in freeToPlayChampions)
{
var championsData = client.GetStaticChampionByIdAsync(
(int)freeToPlayChampion.Id,
platformId: Region);
table.AddRow(await championsData);
}
table.Write();
Unfortunately this hangs, as well. Again on the same code part, e.g. await championsData.
How can this 'easy' usage of async/await lead to an deadlock? Thanks in advance for help!
EDIT:
Here is the whole class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ConsoleTables;
using Mono.Options;
using RiotNet.Models;
using RiotShell.Properties;
namespace RiotShell
{
public class FreeToPlay : IShellCommand
{
public IEnumerable<string> Names { get; }
public OptionSet Options { get; }
public bool ShowHelp { get; private set; }
public string Region { get; private set; }
public FreeToPlay()
{
Names = new List<string>
{
"freetoplay",
"ftp"
};
Options = new OptionSet
{
{ "r|region=" , "The region to execute against", x => Region = x},
{ "h|help|?" , "Show help", x => ShowHelp = true }
};
}
public async Task Execute(IEnumerable<string> args)
{
if (ShowHelp)
{
Options.WriteOptionDescriptions(Console.Out);
return;
}
if (args.Any())
{
throw new Exception(Resources.TooManyArgumentsProvided);
}
if (Region == null)
{
throw new Exception(string.Format(Resources.RequiredOptionNotFound, "region"));
}
if (!PlatformId.All.Contains(Region))
{
throw new Exception(string.Format(Resources.InvalidRegion, Region));
}
var client = await ApiClientProvider.GetApiClient();
var freeToPlayChampions = await client.GetChampionsAsync(true, Region);
var championsData = freeToPlayChampions.Select(x =>
client.GetStaticChampionByIdAsync(
(int)x.Id,
platformId: Region));
ConsoleTable.From(await Task.WhenAll(championsData)).Write();
}
}
}
And here is the caller code, my main method:
using System;
using System.Threading.Tasks;
using RiotShell.Properties;
namespace RiotShell
{
public class Program
{
public static async Task Main()
{
while (true)
{
Console.Write(Resources.RiotShellLineString);
var input = Console.ReadLine();
try
{
var parsedArgs = InputParser.Parse(input);
(var command, var commandArgs) = ArgsToIShellCommandCaster.GetCommand(parsedArgs);
await command.Execute(commandArgs);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
}
}
Since it was wished, here the code for the ApiProvider:
using RiotNet;
using System.Threading.Tasks;
namespace RiotShell
{
public class ApiClientProvider
{
private static IRiotClient _client;
public static async Task<IRiotClient> GetApiClient()
{
if (_client != null)
{
_client.Settings.ApiKey = await KeyService.GetKey();
return _client;
}
_client = new RiotClient(new RiotClientSettings
{
ApiKey = await KeyService.GetKey()
});
return _client;
}
}
}
I implemented an external login for my BOT. When external site calls Bot CallBack method I need to set token and username in PrivateConversationData and then resume chat with a message like "Welcome back [username]!".
To display this message I send a MessageActivity but this activity never connects to my chat and won't fire the appropriate [LuisIntent("UserIsAuthenticated")].
Other intents, out of login-flow, works as expected.
This is the callback method:
public class OAuthCallbackController : ApiController
{
[HttpGet]
[Route("api/OAuthCallback")]
public async Task OAuthCallback([FromUri] string userId, [FromUri] string botId, [FromUri] string conversationId,
[FromUri] string channelId, [FromUri] string serviceUrl, [FromUri] string locale,
[FromUri] CancellationToken cancellationToken, [FromUri] string accessToken, [FromUri] string username)
{
var resumptionCookie = new ResumptionCookie(TokenDecoder(userId), TokenDecoder(botId),
TokenDecoder(conversationId), channelId, TokenDecoder(serviceUrl), locale);
var container = WebApiApplication.FindContainer();
var message = resumptionCookie.GetMessage();
message.Text = "UserIsAuthenticated";
using (var scope = DialogModule.BeginLifetimeScope(container, message))
{
var botData = scope.Resolve<IBotData>();
await botData.LoadAsync(cancellationToken);
botData.PrivateConversationData.SetValue("accessToken", accessToken);
botData.PrivateConversationData.SetValue("username", username);
ResumptionCookie pending;
if (botData.PrivateConversationData.TryGetValue("persistedCookie", out pending))
{
botData.PrivateConversationData.RemoveValue("persistedCookie");
await botData.FlushAsync(cancellationToken);
}
var stack = scope.Resolve<IDialogStack>();
var child = scope.Resolve<MainDialog>(TypedParameter.From(message));
var interruption = child.Void<object, IMessageActivity>();
try
{
stack.Call(interruption, null);
await stack.PollAsync(cancellationToken);
}
finally
{
await botData.FlushAsync(cancellationToken);
}
}
}
}
public static string TokenDecoder(string token)
{
return Encoding.UTF8.GetString(HttpServerUtility.UrlTokenDecode(token));
}
}
This is the controller:
public class MessagesController : ApiController
{
private readonly ILifetimeScope scope;
public MessagesController(ILifetimeScope scope)
{
SetField.NotNull(out this.scope, nameof(scope), scope);
}
public async Task<HttpResponseMessage> Post([FromBody] Activity activity, CancellationToken token)
{
if (activity != null)
{
switch (activity.GetActivityType())
{
case ActivityTypes.Message:
using (var scope = DialogModule.BeginLifetimeScope(this.scope, activity))
{
var postToBot = scope.Resolve<IPostToBot>();
await postToBot.PostAsync(activity, token);
}
break;
}
}
return new HttpResponseMessage(HttpStatusCode.Accepted);
}
}
This is how I registered components:
protected override void Load(ContainerBuilder builder)
{
base.Load(builder);
builder.Register(
c => new LuisModelAttribute("myId", "SubscriptionKey"))
.AsSelf()
.AsImplementedInterfaces()
.SingleInstance();
builder.RegisterType<MainDialog>().AsSelf().As<IDialog<object>>().InstancePerDependency();
builder.RegisterType<LuisService>()
.Keyed<ILuisService>(FiberModule.Key_DoNotSerialize)
.AsImplementedInterfaces()
.SingleInstance();
}
This is the dialog:
[Serializable]
public sealed class MainDialog : LuisDialog<object>
{
public static readonly string AuthTokenKey = "TestToken";
public readonly ResumptionCookie ResumptionCookie;
public static readonly Uri CloudocOauthCallback = new Uri("http://localhost:3980/api/OAuthCallback");
public MainDialog(IMessageActivity activity, ILuisService luis)
: base(luis)
{
ResumptionCookie = new ResumptionCookie(activity);
}
[LuisIntent("")]
public async Task None(IDialogContext context, LuisResult result)
{
await context.PostAsync("Sorry cannot understand!");
context.Wait(MessageReceived);
}
[LuisIntent("UserAuthenticated")]
public async Task UserAuthenticated(IDialogContext context, LuisResult result)
{
string username;
context.PrivateConversationData.TryGetValue("username", out username);
await context.PostAsync($"Welcome back {username}!");
context.Wait(MessageReceived);
}
[LuisIntent("Login")]
private async Task LogIn(IDialogContext context, LuisResult result)
{
string token;
if (!context.PrivateConversationData.TryGetValue(AuthTokenKey, out token))
{
context.PrivateConversationData.SetValue("persistedCookie", ResumptionCookie);
var loginUrl = CloudocHelpers.GetLoginURL(ResumptionCookie, OauthCallback.ToString());
var reply = context.MakeMessage();
var cardButtons = new List<CardAction>();
var plButton = new CardAction
{
Value = loginUrl,
Type = ActionTypes.Signin,
Title = "Connetti a Cloudoc"
};
cardButtons.Add(plButton);
var plCard = new SigninCard("Connect", cardButtons);
reply.Attachments = new List<Attachment>
{
plCard.ToAttachment()
};
await context.PostAsync(reply);
context.Wait(MessageReceived);
}
else
{
context.Done(token);
}
}
}
What I miss?
Update
Also tried with ResumeAsync in callback method:
var container = WebApiApplication.FindContainer();
var message = resumptionCookie.GetMessage();
message.Text = "UserIsAuthenticated";
using (var scope = DialogModule.BeginLifetimeScope(container, message))
{
var botData = scope.Resolve<IBotData>();
await botData.LoadAsync(cancellationToken);
botData.PrivateConversationData.SetValue("accessToken", accessToken);
botData.PrivateConversationData.SetValue("username", username);
ResumptionCookie pending;
if (botData.PrivateConversationData.TryGetValue("persistedCookie", out pending))
{
botData.PrivateConversationData.RemoveValue("persistedCookie");
await botData.FlushAsync(cancellationToken);
}
await Conversation.ResumeAsync(resumptionCookie, message, cancellationToken);
}
but it give me the error Operation is not valid due to the current state of the object.
Update 2
Following Ezequiel idea I changed my code this way:
[HttpGet]
[Route("api/OAuthCallback")]
public async Task OAuthCallback(string state, [FromUri] string accessToken, [FromUri] string username)
{
var resumptionCookie = ResumptionCookie.GZipDeserialize(state);
var message = resumptionCookie.GetMessage();
message.Text = "UserIsAuthenticated";
await Conversation.ResumeAsync(resumptionCookie, message);
}
resumptionCookie seems to be ok:
but await Conversation.ResumeAsync(resumptionCookie, message); continue to give me the error Operation is not valid due to the current state of the object.
You need to resume the conversation with the bot that's why the message is likely not arriving.
Instead of using the dialog stack, try using
await Conversation.ResumeAsync(resumptionCookie, message);
Depending on your auth needs, you might want to consider AuthBot. You can also take a look to the logic on the OAuthCallback controller of the library to get an idea of how they are resuming the conversation with the Bot after auth.
The ContosoFlowers example, is also using the resume conversation mechanism. Not for auth purposes, but for showing how to handle a hypotethical credit card payment.
I found how to make it works.
Controller:
public class MessagesController : ApiController
{
public async Task<HttpResponseMessage> Post([FromBody] Activity activity, CancellationToken token)
{
if (activity != null)
{
switch (activity.GetActivityType())
{
case ActivityTypes.Message:
var container = WebApiApplication.FindContainer();
using (var scope = DialogModule.BeginLifetimeScope(container, activity))
{
await Conversation.SendAsync(activity, () => scope.Resolve<IDialog<object>>(), token);
}
break;
}
}
return new HttpResponseMessage(HttpStatusCode.Accepted);
}
}
Global.asax
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
var builder = new ContainerBuilder();
builder.RegisterModule(new DialogModule());
builder.RegisterModule(new MyModule());
var config = GlobalConfiguration.Configuration;
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
builder.RegisterWebApiFilterProvider(config);
var container = builder.Build();
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
}
public static ILifetimeScope FindContainer()
{
var config = GlobalConfiguration.Configuration;
var resolver = (AutofacWebApiDependencyResolver)config.DependencyResolver;
return resolver.Container;
}
}
MyModule:
public sealed class MyModule : Module
{
protected override void Load(ContainerBuilder builder)
{
base.Load(builder);
builder.Register(
c => new LuisModelAttribute("MyId", "SubId"))
.AsSelf()
.AsImplementedInterfaces()
.SingleInstance();
builder.RegisterType<MainDialog>().AsSelf().As<IDialog<object>>().InstancePerDependency();
builder.RegisterType<LuisService>()
.Keyed<ILuisService>(FiberModule.Key_DoNotSerialize)
.AsImplementedInterfaces()
.SingleInstance();
}
}
Callback method:
public class OAuthCallbackController : ApiController
{
[HttpGet]
[Route("api/OAuthCallback")]
public async Task OAuthCallback(string state, [FromUri] CancellationToken cancellationToken, [FromUri] string accessToken, [FromUri] string username)
{
var resumptionCookie = ResumptionCookie.GZipDeserialize(state);
var message = resumptionCookie.GetMessage();
message.Text = "UserIsAuthenticated";
using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, message))
{
var dataBag = scope.Resolve<IBotData>();
await dataBag.LoadAsync(cancellationToken);
dataBag.PrivateConversationData.SetValue("accessToken", accessToken);
dataBag.PrivateConversationData.SetValue("username", username);
ResumptionCookie pending;
if (dataBag.PrivateConversationData.TryGetValue("persistedCookie", out pending))
{
dataBag.PrivateConversationData.RemoveValue("persistedCookie");
await dataBag.FlushAsync(cancellationToken);
}
}
await Conversation.ResumeAsync(resumptionCookie, message, cancellationToken);
}
I have a function that populates a combo box in my winform app. The operation takes a few seconds so I'd like to make it async.
Here is the function:
public static List<string> GetAllPostCodeRegions(string country)
{
using (var db = new PlaceDBContext())
{
var regions = db.PostCodes.Where(pc => pc.Country == country).OrderBy(pc => pc.Region).Select(pc => pc.Region).Distinct().ToList();
return regions;
}
}
I tried just adding in async keywords like below:
public async static Task<List<string>> GetAllPostCodeRegions(string country)
{
using (var db = new PlaceDBContext())
{
var regions = db.PostCodes.Where(pc => pc.Country == country).OrderBy(pc => pc.Region).Select(pc => pc.Region).Distinct().ToList();
return regions;
}
}
But this isn't awaiting anything and the UI still locks when I call:
var regions = await DataFunctions.GetAllPostCodeRegions("UK");
How do I make this operation async and stop UI locking up?
you can Try .ToListAsync or:
public static Task<List<string>> GetAllPostCodeRegions(string country)
{
return Task.Run(() =>
{
using (var db = new PlaceDBContext())
{
return db.PostCodes
.Where(pc => pc.Country == country)
.OrderBy(pc => pc.Region)
.Select(pc => pc.Region).Distinct().ToList();
}
});
}
This is a followup question to the following question:
Volatile IEnlistmentNotification and TransactionScope.AsyncFlowEnabled = true
The approach accepted in the question above works as long as you don't await multiple statements. Let me show an example:
public class SendResourceManager : IEnlistmentNotification
{
private readonly Action onCommit;
public SendResourceManager(Action onCommit)
{
this.onCommit = onCommit;
}
public void Prepare(PreparingEnlistment preparingEnlistment)
{
preparingEnlistment.Prepared();
}
public void Commit(Enlistment enlistment)
{
Debug.WriteLine("Committing");
this.onCommit();
Debug.WriteLine("Committed");
enlistment.Done();
}
public void Rollback(Enlistment enlistment)
{
enlistment.Done();
}
public void InDoubt(Enlistment enlistment)
{
enlistment.Done();
}
}
public class AsyncTransactionalMessageSender : ISendMessagesAsync
{
private readonly List<Message> sentMessages = new List<Message>();
public IReadOnlyCollection<Message> SentMessages
{
get { return new ReadOnlyCollection<Message>(this.sentMessages); }
}
public async Task SendAsync(Message message)
{
if (Transaction.Current != null)
{
await Transaction.Current.EnlistVolatileAsync(
new SendResourceManager(async () => await this.SendInternal(message)),
EnlistmentOptions.None);
}
else
{
await this.SendInternal(message);
}
}
private async Task SendInternal(Message message)
{
Debug.WriteLine("Sending");
await Task.Delay(1000);
this.sentMessages.Add(message);
Debug.WriteLine("Sent");
}
}
[Test]
public async Task ScopeRollbackAsync_DoesntSend()
{
var sender = new AsyncTransactionalMessageSender();
using (var tx = new System.Transactions.TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
await sender.SendAsync(new Message("First"));
await sender.SendAsync(new Message("Second"));
await sender.SendAsync(new Message("Last"));
// We do not commit the scope
}
sender.SentMessages.Should().BeEmpty();
}
[Test]
public async Task ScopeCompleteAsync_Sends()
{
var sender = new AsyncTransactionalMessageSender();
using (var tx = new System.Transactions.TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
await sender.SendAsync(new Message("First"));
await sender.SendAsync(new Message("Second"));
await sender.SendAsync(new Message("Last"));
tx.Complete();
}
sender.SentMessages.Should().HaveCount(3)
.And.Contain(m => m.Value == "First")
.And.Contain(m => m.Value == "Second")
.And.Contain(m => m.Value == "Last");
}
As soon as you introduce a Task.Delay like shown in the example above the generated asynchronous statemachine will never come back and invoke the this.sentMessages.Add(message) and Debug.WriteLine("Sent")
The problem is I currently see now way to properly enlist asynchronous code inside the enlistment notification. Any ideas how to tackle this challenge?