I'm using the packages "FastEndpoints" & "FastEndpoints.Security" for creating the RESTApi.
This is my Endpoint:
public class LoginEndpoint : Endpoint<LoginRequest, LoginResponse>
{
IUserService _userSvc;
public LoginEndpoint(IUserService users)
{
_userSvc = users;
}
public override void Configure()
{
Verbs(Http.GET);
Routes("/api/login");
AllowAnonymous();
}
public override async Task HandleAsync(LoginRequest req, CancellationToken ct)
{
if (_userSvc.LoginValidByName(req.Name, req.Password))
{
var user = _userSvc.GetFromName(req.Name);
var expiresAt = DateTime.UtcNow.AddDays(1);
var token = JWTBearer.CreateToken(
GlobalSettings.TOKEN_SIGNING_KEY,
expiresAt,
user.Permissions.Select(p => p.Name));
await SendAsync(
new LoginResponse()
{
Token = token,
ExpiresAt = expiresAt
});
}
else
{
await SendUnauthorizedAsync();
}
}
}
Using Postman, the endpoints works as expected:
But when using RestSharp (and mind you, I'm very new to the whole RESTApi world), I get an error 'Request ended prematurely'.
This is my simple call:
public class ApiClient
{
private RestClient _restClient;
public ApiClient(string baseUrl)
{
_restClient = new RestClient(baseUrl);
//ServicePointManager.ServerCertificateValidationCallback += (s, c, ch, p) => true;
}
public async Task<bool> UserValid(string username, string password)
{
var request = new RestRequest("/api/login", Method.Get);
request.AddParameter("name", username);
request.AddParameter("password", password);
var result = await _restClient.GetAsync(request);
if (result.StatusCode == HttpStatusCode.OK)
return true;
else
return false;
}
}
Can someone fill me in?
Since it works with Postman, I suspect my call being bad.
Is:
_userSvc.LoginValidByName
Or any other function missing an await by chance?
Related
I did research and didn't find anything related to that. Please, I hope someone can help me.
What do I have?
I have HttpMessaheHandler that is inherited from DelegatingHandler
What do I do?
I'm trying to handle OAuth2 logic inside that HttpMessaheHandler
So I do another request inside HttpMessaheHandler from freshly created HttpClient
What is the problem?
The problem is that HttpResponseMessage is hit 2 times! It happens on line 70
HttpResponseMessage identityResponse = await this._client.SendAsync(requestMessage);
When this line is executed then we are coming back into HttpResponseMessage and starting this logic from the beginning.
What I have already tried?
I have tried to use ASP.NET Core native functionality services.AddHttpClient(...) the same issue.
What do I expect?
I do expect that my freshly created HttpClient does not hit this HttpMessageHandler as it is not bound to this HttpClient
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Common.Core.Abstractions;
using Common.Data;
using Common.Data.Enums;
using Common.Services.Shared;
using Hrimsoft.StringCases;
using Integrations.ECommerce.Storm.Configuration;
using Integrations.ECommerce.Storm.Models;
using Integrations.ECommerce.Storm.Models.Requests;
using Integrations.ECommerce.Storm.Services;
using Microsoft.Extensions.DependencyInjection;
using UrlCombineLib;
namespace Integrations.ECommerce.Storm
{
internal class StormIdentityHttpMessageHandler : DelegatingHandler
{
private readonly RequestOptions _requestOptions;
private readonly IContextProvider _contextProvider;
private readonly IStormIdentityService _stormIdentityService;
private readonly StormSettings _settings;
private readonly IServiceProvider _serviceProvider;
private readonly string _authorizationHeader = "Authorization";
private readonly string _applicationId = "ApplicationId";
private HttpClient _client;
private IJsonSerializer _jsonSerializer;
public StormIdentityHttpMessageHandler(
IContextProvider contextProvider,
IStormIdentityService stormIdentityService,
StormSettings settings,
IServiceProvider serviceProvider)
{
this._contextProvider = contextProvider;
this._stormIdentityService = stormIdentityService;
this._settings = settings;
this._serviceProvider = serviceProvider;
this._requestOptions = new RequestOptions { ContentType = RequestContentType.FormUrlEncoded, PropertyCaseType = PropertyNameCaseType.SnakeCase };
this._client = new HttpClient { BaseAddress = new Uri(this._settings.IdentityServerUrl) };
this._jsonSerializer = serviceProvider.GetRequiredService<IJsonSerializer>();
}
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
string culture = this._contextProvider.GetCulture();
var parameters = new StormOAuth2AuthenticationParameters
{
GrantType = this._settings.GrantType, ClientId = this._settings.ClientId, ClientSecret = this._settings.ClientSecret, Scope = this._settings.Scope
};
HttpRequestMessage requestMessage = this.Post(string.Empty, parameters, SetRequestOptions);
HttpResponseMessage identityResponse = await this._client.SendAsync(requestMessage);
StormAccessToken token = await RetrieveAccessToken(identityResponse);
// // Handle OAuth2. Requesting/Getting the access token.
// IStormIdentityService identityService = this._stormIdentityService;
// string accessToken = await identityService.RequestAccessToken();
//
// // Setting the AccessToken token to the outgoing request
// request.Headers.Add(this._applicationId, this._settings[culture].ApplicationId);
// request.Headers.Add(this._authorizationHeader, $"Bearer {accessToken}");
// HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
//
// if (response.StatusCode == HttpStatusCode.Unauthorized)
// {
// bool forceNewToken = true;
// accessToken = await identityService.RequestAccessToken(forceNewToken);
// request.Headers.Remove(this._authorizationHeader);
// request.Headers.Add(this._authorizationHeader, $"Bearer {accessToken}");
// response = await base.SendAsync(request, cancellationToken);
//
// if (response.StatusCode == HttpStatusCode.Unauthorized)
// {
// throw new ServiceException($"Can't authorize with current token. Token value:\n{accessToken}");
// }
// }
return response;
}
private async Task<StormAccessToken> RetrieveAccessToken(HttpResponseMessage httpResponse)
{
if (httpResponse.Content == null)
{
return default;
}
string content = await httpResponse.Content.ReadAsStringAsync();
if (string.IsNullOrEmpty(content))
{
return default;
}
return JsonSerializer.Deserialize<StormAccessToken>(content);
}
protected virtual async Task<Exception> OnNegativeResponse(HttpResponseMessage response)
{
HttpRequestMessage request = response.RequestMessage;
string details = null;
if (response.Content != null)
{
details = await response.Content.ReadAsStringAsync();
}
string message = $"REST request '{request.Method} - {request.RequestUri}' " +
$"error: {(int)response.StatusCode}, Message: {response.ReasonPhrase}{Environment.NewLine}{details}";
var exception = new Exception(message);
return exception;
}
protected HttpRequestMessage Post(string url = "", object data = null, Action<RequestOptions> setup = null)
=> BuildRequest(HttpMethod.Post, url, data, setup);
protected virtual string BuildResourceUrl(string baseUrl, string resource)
=> UrlCombine.Combine(baseUrl, resource);
private HttpRequestMessage BuildRequest(
HttpMethod method,
string resourceBase,
object body = null,
Action<RequestOptions> setup = null)
{
string fullUrl = BuildResourceUrl(this._client.BaseAddress.ToString(), resourceBase);
var request = new HttpRequestMessage(method, fullUrl);
if (body == null)
{
return request;
}
var options = new RequestOptions();
setup?.Invoke(options);
if (options.ContentType == RequestContentType.FormUrlEncoded)
{
Dictionary<string, string> content = GetFormContent(body, options);
request.Content = new FormUrlEncodedContent(content);
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
}
else
{
throw new Exception($"Unsupported content type {options.ContentType}");
}
return request;
}
private Dictionary<string, string> GetFormContent(object data, RequestOptions options)
{
var pairs = data
.GetType()
.GetProperties()
.Select(s => new { Name = ToRequiredCase(s.Name, options.PropertyCaseType), Value = s.GetValue(data, null)?.ToString() })
.Where(s => s.Value != null)
.ToDictionary(k => k.Name, v => v.Value);
return pairs;
string ToRequiredCase(string value, PropertyNameCaseType type)
{
string processedValue = string.Empty;
if (type == PropertyNameCaseType.CamelCase)
{
processedValue = value?.ToCamelCase();
}
else if (type == PropertyNameCaseType.SnakeCase)
{
processedValue = value?.ToSnakeCase();
}
return processedValue;
}
}
private void SetRequestOptions(RequestOptions options)
{
options.ContentType = this._requestOptions.ContentType;
options.PropertyCaseType = this._requestOptions.PropertyCaseType;
}
}
}
I tried make it as you suggested (answer below this). Unfortunately still I have this problem.
This is my new TokenService.cs:
class TokenService
{
TokenKeeper tokenkeeper;
public TokenService()
{
tokenkeeper = new TokenKeeper();
}
public async void AwaitGetToken()
{
tokenkeeper = new TokenKeeper();
tokenkeeper = await GetToken();
}
private async Task<TokenKeeper> GetToken()
{
string stringuri = string.Format("{0}/token", RestAccess.HostUrl);
var dict = new Dictionary<string, string>();
dict.Add("grant_type", "password");
dict.Add("username", RestAccess.User);
dict.Add("password", RestAccess.Pass);
dict.Add("client_id", RestAccess.Client_ID);
dict.Add("client_secret", RestAccess.Client_secret);
tokenkeeper=new TokenKeeper();
var httpclient = new HttpClient();
try
{
var req = new HttpRequestMessage(HttpMethod.Post, stringuri) { Content = new FormUrlEncodedContent(dict) };
var res = await httpclient.SendAsync(req);
if (res.IsSuccessStatusCode)
{
var content = await res.Content.ReadAsStringAsync();
tokenkeeper = JsonConvert.DeserializeObject<TokenKeeper>(content);
return tokenkeeper;
}
return tokenkeeper;
}
catch
{
return tokenkeeper;
}
finally
{
httpclient.CancelPendingRequests();
httpclient.Dispose();
}
}
}
My tokenkeeper is simple class:
public class TokenKeeper
{
public string access_token { get; set; }
public string refresh_token { get; set; }
public TokenKeeper()
{
access_token = "";
refresh_token = "";
}
}
In my calling code I have as below:
...
tokenservice.AwaitGetToken();
GetXFMapUserFromAzureAndSetVariable(RestAccess.User);
_navigationService.NavigateAsync("ZleceniaListContentPage", par);
...
tokenservice.AwaitGetToken() and GEtXFMapUserFromAzureAndSetVariable(RestAccess.User) they are similar. Both await but tokenservice.AwaitGetToken() is POST and GEtXFMapUserFromAzureAndSetVariable is GET.
GEtXFMapUserFromAzureAndSetVariable is working correctly but if I call tokenservice.AwaitGetToken() the aplication get the token but before it's continue. At the end the instruction GEtXFMapUserFromAzureAndSetVariable it is being done tokenservice.AwaitGetToken() responds.
How can I wait with call GEtXFMapUserFromAzureAndSetVariable until I receive a reply from tokenservice.AwaitGetToken() ???
Delete your AwaitGetToken method, it is useless.
Now make you GetToken method public and call it with await:
await tokenService.GetToken();
await GetXFMapUserFromAzureAndSetVariable(RestAccess.User);
await _navigationService.NavigateAsync("ZleceniaListContentPage", par);
Basically you always need to await methods returning Task, or it will run in a parallel fashion, and you will lose consistency in your code.
You seem very confused by Task and async/await, I will strongly advise you to read tutorials and docs on it:
https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/
I am having issues with testing Login Controller using IdentityServer4. It throws the following error:
{System.Net.Http.WinHttpException (0x80072EFD): A connection with the server could not be established
I am trying to generate the access Token using ResourceOwnerPassword, for which I have implemented IResourceOwnerPasswordValidator. I get the error in UserAccessToken.cs class when I call the RequestResourcePasswordAsync.
I am pretty sure it is because of the handler. Because if I use a handler in my test class and call the TokenClient with that handler I do get access Token but then I cannot test my Login Controller.
LoginController.cs
[HttpPost]
public async Task<IActionResult> Login([FromBody]LoginViewModel user)
{
var accessToken = await UserAccessToken.GenerateTokenAsync(user.Username, user.Password);
var loginToken = JsonConvert.DeserializeObject(accessToken);
return Ok(loginToken);
}
UserAccessToken.cs
public async Task<string> GenerateTokenAsync(string username, string password)
{
var tokenUrl = "http://localhost:5000/connect/token";
var tokenClient = new TokenClient(tokenUrl,"ClientId","ClientPassword");
var tokenResponse = await tokenClient.RequestResourceOwnerPasswordAsync(username, password, SecurityConfig.PublicApiResourceId);
if (tokenResponse.IsError)
{
throw new AuthenticationFailedException(tokenResponse.Error);
}
return tokenResponse.Json.ToString();
}
TestClass.cs
[Fact]
public async Task Login()
{
var client = _identityServer.CreateClient();
var data = new StringContent(JsonConvert.SerializeObject(new LoginViewModel { Username = "1206", Password = "5m{F?Hk92/Qj}n7Lp6" }), Encoding.UTF8, "application/json");
var dd = await client.PostAsync("http://localhost:5000/login", data);
var ss = dd;
}
IdentityServerSetup.cs //Integration Test Setup
public class IdentityServerSetup
{
private TestServer _identityServer;
private const string TokenEndpoint = "http://localhost:5000/connect/token";
public HttpMessageHandler _handler;
//IF I use this code I do get a AccessToken
public async Task<string> GetAccessTokenForUser(string userName, string password, string clientId, string clientSecret, string apiName = "integrapay.api.public")
{
var client = new TokenClient(TokenEndpoint, clientId, clientSecret, innerHttpMessageHandler: _handler);
var response = await client.RequestResourceOwnerPasswordAsync(userName, password, apiName);
return response.AccessToken;
}
}
Well, you have already answered the question yourself: The problem is with the HttpHandler the TokenClient uses. It should use the one provided by the TestServer to successfully communicate with it instead of doing actual requests to localhost.
Right now, UserAccessToken requires a TokenClient. This is a dependency of your class, so you should refactor the code to pass in a TokenClient instead of generating it yourself. This pattern is called Dependency Injection and is ideal for cases like yours, where you might have different requirements in your tests than in your production setup.
You could make the code look like this:
UserAccessToken.cs
public class UserAccessToken
{
private readonly TokenClient _tokenClient;
public UserAccessToken(TokenClient tokenClient)
{
_tokenClient = tokenClient;
}
public async Task<string> GenerateTokenAsync(string username, string password)
{
var tokenUrl = "http://localhost:5000/connect/token";
var tokenResponse = await _tokenClient.RequestResourceOwnerPasswordAsync(username, password, SecurityConfig.PublicApiResourceId);
if (tokenResponse.IsError)
{
throw new AuthenticationFailedException(tokenResponse.Error);
}
return tokenResponse.Json.ToString();
}
}
TestHelpers.cs
public static class TestHelpers
{
private static TestServer _testServer;
private static readonly object _initializationLock = new object();
public static TestServer GetTestServer()
{
if (_testServer == null)
{
InitializeTestServer();
}
return _testServer;
}
private static void InitializeTestServer()
{
lock (_initializationLock)
{
if (_testServer != null)
{
return;
}
var webHostBuilder = new WebHostBuilder()
.UseStartup<IntegrationTestsStartup>();
var testServer = new TestServer(webHostBuilder);
var initializationTask = InitializeDatabase(testServer);
initializationTask.ConfigureAwait(false);
initializationTask.Wait();
testServer.BaseAddress = new Uri("http://localhost");
_testServer = testServer;
}
}
}
IntegrationTestsStartup.cs
public class IntegrationTestsStartup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<TokenClient>(() =>
{
var handler = TestUtilities.GetTestServer().CreateHandler();
var client = new TokenClient(TokenEndpoint, clientId, clientSecret, innerHttpMessageHandler: handler);
return client;
};
services.AddTransient<UserAccessToken>();
}
}
LoginController.cs
public class LoginController : Controller
{
private readonly UserAccessToken _userAccessToken;
public LoginController(UserAccessToken userAccessToken)
{
_userAccessToken = userAccessToken;
}
[HttpPost]
public async Task<IActionResult> Login([FromBody]LoginViewModel user)
{
var accessToken = await _userAccessToken .GenerateTokenAsync(user.Username, user.Password);
var loginToken = JsonConvert.DeserializeObject(accessToken);
return Ok(loginToken);
}
}
Here's one of my GitHub projects that makes use of the TestServer class and shows how I'm using it. It's not using IdentityServer4, though.
I am using Moq in .net core(1.1) and having a bit of torrid time understanding this behavior as all the examples on interweb points to the fact the this should work with no issues.
I have already tried with:
Returns(Task.FromResult(...)
Returns(Task.FromResult(...)
ReturnsAsync(...)
Mocking a IHttpClient interface to wrap PostAsync, PutAsync and GetAsync. All of these return an ApiResponse object.
var mockClient = new Mock<IHttpClient>();
Does not work:
mockClient.Setup(x => x.PostAsync(url, JsonConvert.SerializeObject(body), null))
.Returns(Task.FromResult(new ApiResponse()));
PostSync definition:
public async Task<ApiResponse> PostAsync(string url, string body, string authToken = null)
Does work:
mockClient.Setup(x => x.PostAsync(url, JsonConvert.SerializeObject(body), null))
.Returns(Task.FromResult(bool));
PostSync definition:
public async Task<bool> PostAsync(string url, string body, string authToken = null)
Usage:
var api = new ApiService(mockClient.Object);
var response = api.LoginAsync(body.Username, body.Password);
UPDATE
[Fact]
public async void TestLogin()
{
var mockClient = new Mock<IHttpClient>();
mockClient.Setup(x => x.PostAsync(url, JsonConvert.SerializeObject(body), null)).Returns(Task.FromResult(new ApiResponse()));
var api = new ApiService(mockClient.Object);
var response = await api.LoginAsync(body.Username, body.Password);
Assert.IsTrue(response);
}
Return Type:
public class ApiResponse
{
public string Content { get; set; }
public HttpStatusCode StatusCode { get; set; }
public string Reason { get; set; }
}
LoginAsync:
public async Task<bool> LoginAsync(string user, string password)
{
var body = new { Username = user, Password = password };
try
{
var response = await _http.PostAsync(_login_url, JsonConvert.SerializeObject(body), null);
return response .State == 1;
}
catch (Exception ex)
{
Logger.Error(ex);
return false;
}
}
PostAsync:
public async Task<object> PostAsync(string url, string body, string authToken = null)
{
var client = new HttpClient();
var content = new StringContent(body, Encoding.UTF8, "application/json");
var response = await client.PostAsync(new Uri(url), content);
var resp = await response.Result.Content.ReadAsStringAsync();
return new ApiResponse
{
Content = resp,
StatusCode = response.Result.StatusCode,
Reason = response.Result.ReasonPhrase
};
}
Assuming a simple method under test like this based on minimal example provided above.
public class ApiService {
private IHttpClient _http;
private string _login_url;
public ApiService(IHttpClient httpClient) {
this._http = httpClient;
}
public async Task<bool> LoginAsync(string user, string password) {
var body = new { Username = user, Password = password };
try {
var response = await _http.PostAsync(_login_url, JsonConvert.SerializeObject(body), null);
return response.StatusCode == HttpStatusCode.OK;
} catch (Exception ex) {
//Logger.Error(ex);
return false;
}
}
}
The following test works when configured correctly
[Fact]
public async Task Login_Should_Return_True() { //<-- note the Task and not void
//Arrange
var mockClient = new Mock<IHttpClient>();
mockClient
.Setup(x => x.PostAsync(It.IsAny<string>(), It.IsAny<string>(), null))
.ReturnsAsync(new ApiResponse() { StatusCode = HttpStatusCode.OK });
var api = new ApiService(mockClient.Object);
//Act
var response = await api.LoginAsync("", "");
//Assert
Assert.IsTrue(response);
}
The above is just for demonstrative purposes only to show that it can work provided the test is configured properly and exercised based on the expected behavior.
Take some time and review the Moq quick start to get a better understanding of how to use the framework.
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);
}