ContinueConversationAsync() requires users to chat first after publishing bot - c#

After publishing the bot, the user needs to chat with the bot again first in order send a proactive message. I followed this sample but instead of storing the conversation reference in the variable, I stored it in cosmosDB. However I still can't send proactive message if the user has not chatted with the bot after publishing. Is there a way to send proactive message even when the user has not chatted with the bot after publishing?
DialogBot
private async void AddConversationReference(ITurnContext turnContext)
{
var userstate = await _userProfileAccessor.GetAsync(turnContext, () => new BasicUserState());
userstate.SavedConversationReference = turnContext.Activity.GetConversationReference();
_conversationReferences.AddOrUpdate(userstate.SavedConversationReference.User.Id, userstate.SavedConversationReference, (key, newValue) => userstate.SavedConversationReference);
}
protected override Task OnConversationUpdateActivityAsync(ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
AddConversationReference(turnContext);
return base.OnConversationUpdateActivityAsync(turnContext, cancellationToken);
}
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
Logger.LogInformation("Running dialog with Message Activity.");
AddConversationReference(turnContext);
await Dialog.RunAsync(turnContext, ConversationState.CreateProperty<DialogState>("DialogState"), cancellationToken);
}
api/notify
namespace SabikoBotV2.Controllers
{
[Route("api/notify")]
public class NotifyController : ControllerBase
{
private readonly IBotFrameworkHttpAdapter _adapter;
private readonly string _appId;
private readonly ConcurrentDictionary<string, ConversationReference> _conversationReferences;
private readonly IStatePropertyAccessor<BasicUserState> _userProfileAccessor;
public NotifyController(UserState userState, IBotFrameworkHttpAdapter adapter, ICredentialProvider credentials, ConcurrentDictionary<string, ConversationReference> conversationReferences)
{
_userProfileAccessor = userState.CreateProperty<BasicUserState>("UserProfile");
_adapter = adapter;
_conversationReferences = conversationReferences;
_appId = ((SimpleCredentialProvider)credentials).AppId;
if (string.IsNullOrEmpty(_appId))
{
_appId = Guid.NewGuid().ToString(); //if no AppId, use a random Guid
}
}
public async Task<IActionResult> Get()
{
try
{
foreach (var conversationReference in _conversationReferences.Values)
{
await ((BotAdapter)_adapter).ContinueConversationAsync(_appId, conversationReference, BotCallback, default(CancellationToken));
}
return new ContentResult()
{
Content = "<html><body><h1>Proactive messages have been sent.</h1></body></html>",
ContentType = "text/html",
StatusCode = (int)HttpStatusCode.OK,
};
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}
private async Task BotCallback(ITurnContext turnContext, CancellationToken cancellationToken)
{
var userstate = await _userProfileAccessor.GetAsync(turnContext, () => new BasicUserState(), cancellationToken);
if (userstate.SavedConversationReference.ServiceUrl != null && userstate.SavedConversationReference.ServiceUrl != string.Empty)
{
MicrosoftAppCredentials.TrustServiceUrl(userstate.SavedConversationReference.ServiceUrl);
}
else if (turnContext.Activity.ServiceUrl != null && turnContext.Activity.ServiceUrl != string.Empty)
{
MicrosoftAppCredentials.TrustServiceUrl(turnContext.Activity.ServiceUrl);
}
else
{
MicrosoftAppCredentials.TrustServiceUrl("https://facebook.botframework.com/");
}
if(userstate.Reminders != null)
{
foreach (var reminder in userstate.Reminders)
{
if (reminder.DateAndTime.TrimMilliseconds() == DateTimeNowInGmt().TrimMilliseconds())
{
var timeProperty = new TimexProperty(reminder.DateAndTimeTimex);
var naturalDate = timeProperty.ToNaturalLanguage(DateTimeNowInGmt().TrimMilliseconds());
await turnContext.SendActivityAsync($"It's {DateTimeNowInGmt().ToLongDateString()}. \n\nReminding you to {reminder.Subject}.");
}
}
}
}
public static DateTime DateTimeNowInGmt()
{
TimeZoneInfo phTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Taipei Standard Time");
var t = DateTime.Now;
DateTime phTime = TimeZoneInfo.ConvertTime(t, phTimeZone);
return phTime;
}
}
}

Related

JSON-RPC - Handling exceptions with TaskCompletionSource (SetException)

There is a JSON-RPC API, which I'm currently implementing. It can be tested here.
The problem is that if an incorrect DTO model is passed to SendAsync<TResponse>, JsonSerializer.Deserialize is going to throw a JsonException, which is not handled by my code. I know I've got to use SetException in some way, but I don't know how to do it, so here is the question. The exception message should be printed in the console as well.
public sealed class Client : IDisposable
{
...
private readonly ConcurrentDictionary<long, IResponseHandler> _handlers = new();
...
public Task StartAsync(CancellationToken cancellationToken)
{
_ = Task.Run(async () =>
{
await foreach (var message in _client.Start(cancellationToken))
{
using var response = JsonDocument.Parse(message);
try
{
var requestId = response.RootElement.GetProperty("id").GetInt32();
// TODO: Handle JsonException errors via `SetException`?
// TODO: Show error when incorrect input parameters are filled
if (_handlers.ContainsKey(requestId))
{
_handlers[requestId].SetResult(message);
_handlers.TryRemove(requestId, out _);
}
}
catch (KeyNotFoundException)
{
// My point is that a message should be processed only if it doesn't include `id`,
// because that means that the message is an actual web socket subscription.
_messageReceivedSubject.OnNext(message);
}
}
}, cancellationToken);
...
return Task.CompletedTask;
}
public Task<TResponse> SendAsync<TResponse>(string method, object #params)
{
var request = new JsonRpcRequest<object>
{
JsonRpc = "2.0",
Id = NextId(),
Method = method,
Params = #params
};
//var tcs = new TaskCompletionSource<TResponse>();
//_requestManager.Add(request.Id, request, tcs);
var handler = new ResponseHandlerBase<TResponse>();
_handlers[request.Id] = handler;
var message = JsonSerializer.Serialize(request);
_ = _client.SendAsync(message);
return handler.Task;
//return tcs.Task;
}
public async Task<AuthResponse?> AuthenticateAsync(string clientId, string clientSecret)
{
var #params = new Dictionary<string, string>
{
{"grant_type", "client_credentials"},
{"client_id", clientId},
{"client_secret", clientSecret}
};
var response = await SendAsync<SocketResponse<AuthResponse>>("public/auth", #params).ConfigureAwait(false);
return response.Result;
}
...
private interface IResponseHandler
{
void SetResult(string payload);
}
private class ResponseHandlerBase<TRes> : IResponseHandler
{
private readonly TaskCompletionSource<TRes> _tcs = new();
public Task<TRes> Task => _tcs.Task;
public void SetResult(string payload)
{
var result = JsonSerializer.Deserialize(payload, typeof(TRes));
_tcs.SetResult((TRes) result);
}
}
}
Coincidentally, I did something very similar while live-coding a TCP/IP chat application last week.
Since in this case you already have an IAsyncEnumerable<string> - and since you can get messages other than responses - I recommend also exposing that IAsyncEnumerable<string>:
public sealed class Client : IDisposable
{
public async IAsyncEnumerable<string> Start(CancellationToken cancellationToken)
{
await foreach (var message in _client.Start(cancellationToken))
{
// TODO: parse and handle responses for our requests
yield return message;
}
}
}
You can change this to be Rx-based if you want (_messageReceivedSubject.OnNext), but I figure if you already have IAsyncEnumerable<T>, then you may as well keep the same abstraction.
Then, you can parse and detect responses, passing along all other messages:
public sealed class Client : IDisposable
{
public async IAsyncEnumerable<string> Start(CancellationToken cancellationToken)
{
await foreach (var message in _client.Start(cancellationToken))
{
var (requestId, response) = TryParseResponse(message);
if (requestId != null)
{
// TODO: handle
}
else
{
yield return message;
}
}
(long? RequestId, JsonDocument? Response) TryParseResponse(string message)
{
try
{
var document = JsonDocument.Parse(message);
var requestId = response.RootElement.GetProperty("id").GetInt32();
return (document, requestId);
}
catch
{
return (null, null);
}
}
}
}
Then, you can define your collection of outstanding requests and handle messages that are for those requests:
public sealed class Client : IDisposable
{
private readonly ConcurrentDictionary<int, TaskCompletionSource<JsonDocument>> _requests = new();
public async IAsyncEnumerable<string> Start(CancellationToken cancellationToken)
{
await foreach (var message in _client.Start(cancellationToken))
{
var (requestId, response) = TryParseResponse(message);
if (requestId != null && _requests.TryRemove(requestId.Value, out var tcs))
{
tcs.TrySetResult(response);
}
else
{
yield return message;
}
}
(long? RequestId, JsonDocument? Response) TryParseResponse(string message)
{
try
{
var document = JsonDocument.Parse(message);
var requestId = response.RootElement.GetProperty("id").GetInt32();
return (document, requestId);
}
catch
{
return (null, null);
}
}
}
}
Note the usage of ConcurrentDictionary.TryRemove, which is safer than accessing the value and then removing it.
Now you can write your general SendAsync. As I note in my video, I prefer to split up the code that runs synchronously in SendAsync and the code that awaits the response:
public sealed class Client : IDisposable
{
...
public Task<TResponse> SendAsync<TResponse>(string method, object #params)
{
var request = new JsonRpcRequest<object>
{
JsonRpc = "2.0",
Id = NextId(),
Method = method,
Params = #params,
};
var tcs = new TaskCompletionSource<JsonDocument>(TaskCreationOptions.RunContinuationsAsynchronously);
_requests.TryAdd(request.Id, tcs);
return SendRequestAndWaitForResponseAsync();
async Task<TResponse> SendRequestAndWaitForResponseAsync()
{
var message = JsonSerializer.Serialize(request);
await _client.SendAsync(message);
var response = await tcs.Task;
return JsonSerializer.Deserialize(response, typeof(TResponse));
}
}
}
I've removed the "handler" concept completely, since it was just providing the type for JsonSerializer.Deserialize. Also, by using a local async method, I can use the async state machine to propagate exceptions naturally.
Then, your higher-level methods can be built on this:
public sealed class Client : IDisposable
{
...
public async Task<AuthResponse?> AuthenticateAsync(string clientId, string clientSecret)
{
var #params = new Dictionary<string, string>
{
{"grant_type", "client_credentials"},
{"client_id", clientId},
{"client_secret", clientSecret}
};
var response = await SendAsync<SocketResponse<AuthResponse>>("public/auth", #params);
return response.Result;
}
}
So the final code ends up being:
public sealed class Client : IDisposable
{
private readonly ConcurrentDictionary<int, TaskCompletionSource<JsonDocument>> _requests = new();
public async IAsyncEnumerable<string> Start(CancellationToken cancellationToken)
{
await foreach (var message in _client.Start(cancellationToken))
{
var (requestId, response) = TryParseResponse(message);
if (requestId != null && _requests.TryRemove(requestId.Value, out var tcs))
{
tcs.TrySetResult(response);
}
else
{
yield return message;
}
}
(long? RequestId, JsonDocument? Response) TryParseResponse(string message)
{
try
{
var document = JsonDocument.Parse(message);
var requestId = response.RootElement.GetProperty("id").GetInt32();
return (document, requestId);
}
catch
{
return (null, null);
}
}
}
public Task<TResponse> SendAsync<TResponse>(string method, object #params)
{
var request = new JsonRpcRequest<object>
{
JsonRpc = "2.0",
Id = NextId(),
Method = method,
Params = #params,
};
var tcs = new TaskCompletionSource<JsonDocument>(TaskCreationOptions.RunContinuationsAsynchronously);
_requests.TryAdd(request.Id, tcs);
return SendRequestAndWaitForResponseAsync();
async Task<TResponse> SendRequestAndWaitForResponseAsync()
{
var message = JsonSerializer.Serialize(request);
await _client.SendAsync(message);
var response = await tcs.Task;
return JsonSerializer.Deserialize(response, typeof(TResponse));
}
}
public async Task<AuthResponse?> AuthenticateAsync(string clientId, string clientSecret)
{
var #params = new Dictionary<string, string>
{
{"grant_type", "client_credentials"},
{"client_id", clientId},
{"client_secret", clientSecret}
};
var response = await SendAsync<SocketResponse<AuthResponse>>("public/auth", #params);
return response.Result;
}
}
You may also want to check out David Fowler's Project Bedrock, which may simplify this code quite a bit.

WaterfallDialog, fire the first step without the user sending any message. C#

I want the first step of the WaterfallDialog to prompt when the class is called. Right now to only do this is to use the OnMembersAddedAsync method which sends like a welcome message. Which is not I needed.
I want the StartConversation to automatically fire when the class is called.
public class HomeOfficeDialogV3 : ComponentDialog
{
private readonly StateAccessor _StateAccessor;
private readonly DirectlineApi _TokenReq;
private readonly IndicatorDelay _IndicatorDelay;
private UserDataExtractedDto _UserDataExtractedDto;
public HomeOfficeDialogV3(StateAccessor userState)
{
_StateAccessor = userState;
_TokenReq = new DirectlineApi();
_IndicatorDelay = new IndicatorDelay();
var waterfallSteps = new WaterfallStep[]
{
StartConversation,
};
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), waterfallSteps));
AddDialog(new TextPrompt(nameof(TextPrompt)));
AddDialog(new ChoicePrompt(nameof(ChoicePrompt)) { Style = ListStyle.SuggestedAction });
AddDialog(new ConfirmPrompt(nameof(ConfirmPrompt)));
AddDialog(new NumberPrompt<int>(nameof(NumberPrompt<int>)));
InitialDialogId = nameof(WaterfallDialog);
}
public HomeOfficeDialogV3(UserDataExtractedDto userDataExtractedDto)
{
_UserDataExtractedDto = userDataExtractedDto;
}
public async Task<DialogTurnResult> StartConversation(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
await _IndicatorDelay.ShowTypingIndicator(stepContext.Context, 1400);
return await stepContext.PromptAsync(nameof(ChoicePrompt),
new PromptOptions {
Prompt = MessageFactory.Text($"Did you know your office space can affect your well-being?"),
Choices = ChoiceFactory.ToChoices(new List<string> { "Hello Buddy 😊" })
}
);
}
}
public MainDialog(IConfiguration configuration, StateAccessor userState)
{
_StateAccessor = userState;
_TokenReq = new DirectlineApi();
_DialogsTurn = new DialogsTurn();
private DialogCurrentDayId _DialogCurrentDayId;
var waterfallSteps = new WaterfallStep[]
{
DialogsTurn
};
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), waterfallSteps));
AddDialog(new WelcomeDiloagV1(userState));
AddDialog(new ExerciseDialogV2(userState));
AddDialog(new HomeOfficeDialogV3(userState));
InitialDialogId = nameof(WaterfallDialog);
}
public MainDialog(DialogCurrentDayId dialogCurrentDayId)
{
_DialogCurrentDayId = dialogCurrentDayId;
}
private async Task<DialogTurnResult> DialogsTurn(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var dialogTurnID = Convert.ToInt32(UserDataExtractedDto.DialogTurnID);
var dialogEnum = Enum.GetValues(typeof(DialogEnumerator)).Cast<DialogEnumerator>();
var currentDialog = String.Empty;
foreach(DialogEnumerator dialog in dialogEnum)
{
if (dialogTurnID > (int)dialog)
{
dialogTurnID = dialogEnum.Count();
}
if ((int)dialog == dialogTurnID)
{
currentDialog = dialog.ToString();
}
}
return await stepContext.BeginDialogAsync(currentDialog, null, cancellationToken);
}
}
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<MainDialog>();
services.AddTransient<IBot, MainBot<MainDialog>>();
}
protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
foreach (var member in turnContext.Activity.MembersAdded)
{
if (member.Id != turnContext.Activity.Recipient.Id)
{
var token = turnContext.Activity.From.Name;
if (!String.IsNullOrEmpty(token))
{
var ConversationExistingId = await _StateAccessor.existingConversation.GetAsync(turnContext, () => new ExistingConversation(), cancellationToken);
if (_IsTest)
{
_UserDataFromReactWebChat.Token = _TokenTest;
_UserDataFromReactWebChat.UserId = _IdTest;
}
else
{
_UserDataFromReactWebChat.Token = turnContext.Activity.From.Name;
_UserDataFromReactWebChat.UserId = Int32.Parse(turnContext.Activity.From.Id);
}
var getDialogID = new DialogsTurn(_UserDataFromReactWebChat);
var initializeDialogsById = await getDialogID.GetNextDialog();
if (initializeDialogsById != null){
new MainDialog(initializeDialogsById);
}
await _IndicatorDelay.ShowTypingIndicator(turnContext, 5500);
var isGranted = String.Empty;
var userAllData = new UsersAllData(_UserDataFromReactWebChat);
var usersInfoFromWebApp = await userAllData.GetUsersInfoFromWebApp();
foreach (var i in usersInfoFromWebApp)
{
if(!String.IsNullOrEmpty(i.IsGranted))
{
isGranted = i.IsGranted.ToString();
}
new WelcomeDiloagV1(i);
new ExerciseDialogV2(i);
new HomeOfficeDialogV3(i);
//break;
}
if (isGranted == "true")
{
if (string.IsNullOrEmpty(ConversationExistingId.ConverstationId))
{
ConversationExistingId.ConverstationId = turnContext.Activity.Conversation.Id;
await _StateAccessor.existingConversation.SetAsync(turnContext, ConversationExistingId, cancellationToken);
await _StateAccessor.existingConversationState.SaveChangesAsync(turnContext);
}
else
{
var Conversation_id = ConversationExistingId.ConverstationId;
var TokenGenerated = _UserDataFromReactWebChat.Token;
}
}
else if (isGranted == "false")
{
await turnContext.SendActivityAsync(MessageFactory.Text("Unathorized requests."), cancellationToken);
}
}
else
{
await turnContext.SendActivityAsync(MessageFactory.Text($"Oops! Something went wrong."), cancellationToken);
}
}
}
}
I have multiple steps but I cut it out for this example.
If your bot is based on the document that you linked to then it will access the dialog stack from OnMessageActivityAsync using Dialog.RunAsync. You can do the same thing in OnMembersAddedAsync.

Receiving Unauthorized Access Error on RenewToken Periodically with Azure Service Bus Queue Listener

I am receiving a Microsoft.Azure.ServiceBus.ServiceBusException (message below with sensitive information removed) periodically in my queue receiver. The SAS key has send/listen access and the error seems inconsequential as processing continues as normal. However, the message is creating a signal to noise problem in my dashboards (receiving 10-70 errors per day). Any ideas on why this is happening? The listener is running in an Azure App Service, but I don't think that matters. I have adjusted my retry logic to use a RetryExponential with a 1 second to 1 minute backoff with 5 retries.
Request for guidance from SDK developers
Packages
Net Core 3.1
Microsoft.Azure.ServiceBus, Version=4.1.3.0, Culture=neutral, PublicKeyToken=7e34167dcc6d6d8c
Error Message
The link 'xxx;xxx:xxx:xxx:source(address:xxx):xxx' is force detached. Code: RenewToken. Details: Unauthorized access. 'Listen' claim(s) are required to perform this operation. Resource: 'sb://xxx.servicebus.windows.net/xxx'.. TrackingId:xxx, SystemTracker:xxx, Timestamp:2020-04-27T09:36:04 The link 'xxx;xxx:xxx:xxx:source(address:xxx):xxx' is force detached. Code: RenewToken. Details: Unauthorized access. 'Listen' claim(s) are required to perform this operation. Resource: 'sb://xxx.servicebus.windows.net/xxx'.. TrackingId:xxx, SystemTracker:xxx, Timestamp:2020-04-27T09:36:04
Source
internal delegate TClient ClientFactory<out TClient>(string connectionString, string entityPath,
RetryPolicy retryPolicy);
internal delegate Task OnMessageCallback<in TMessage>(TMessage message,
CancellationToken cancellationToken = default) where TMessage : ICorrelative;
internal sealed class ReceiverClientWrapper<TMessage> : IReceiverClientWrapper<TMessage>
where TMessage : ICorrelative
{
// ReSharper disable once StaticMemberInGenericType
private static readonly Regex TransientConnectionErrorRegex =
new Regex(
#"(The link '([a-f0-9-]+);([0-9]*:)*source\(address:([a-z0-9_]+)\):([a-z0-9_]+)' is force detached. Code: RenewToken. Details: Unauthorized access. 'Listen' claim\(s\) are required to perform this operation. Resource: 'sb:\/\/([a-z0-9-_.\/]+)'.. TrackingId:([a-z0-9_]+), SystemTracker:([a-z0-9]+), Timestamp:([0-9]{4}(-[0-9]{2}){2}T([0-9]{2}:){2}[0-9]{2}) )+",
RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.IgnoreCase);
private readonly IReceiverClient _receiverClient;
private readonly IMessageConverter<TMessage> _messageConverter;
private readonly ILogger _logger;
private readonly int _maximumConcurrency;
public ReceiverClientWrapper(IReceiverClient receiverClient, IMessageConverter<TMessage> messageConverter,
ILogger logger, int maximumConcurrency)
{
_receiverClient = receiverClient;
_messageConverter = messageConverter;
_logger = logger;
_maximumConcurrency = maximumConcurrency;
}
public Task SubscribeAsync(OnMessageCallback<TMessage> onMessageCallback,
OnFailureCallback onFailureCallback, CancellationToken cancellationToken = default)
{
var messageHandlerOptions = CreateMessageHandlerOptions(onFailureCallback, cancellationToken);
async Task Handler(Message message, CancellationToken token)
{
var convertedMessage = _messageConverter.Convert(message);
await onMessageCallback(convertedMessage, cancellationToken);
await _receiverClient.CompleteAsync(message.SystemProperties.LockToken);
}
_receiverClient.RegisterMessageHandler(Handler, messageHandlerOptions);
return Task.CompletedTask;
}
private MessageHandlerOptions CreateMessageHandlerOptions(OnFailureCallback onFailureCallback,
CancellationToken cancellationToken)
{
async Task HandleExceptionAsync(ExceptionReceivedEventArgs arguments)
{
var exception = arguments.Exception;
if (TransientConnectionErrorRegex.IsMatch(exception.Message))
{
_logger.LogWarning(exception, #"Transient connectivity error occurred");
return;
}
await onFailureCallback(exception, cancellationToken);
}
return new MessageHandlerOptions(HandleExceptionAsync)
{
AutoComplete = false,
MaxConcurrentCalls = _maximumConcurrency
};
}
public async ValueTask DisposeAsync()
{
await _receiverClient.CloseAsync();
}
}
internal sealed class SenderClientWrapper<TMessage> : ISenderClientWrapper<TMessage> where TMessage : ICorrelative
{
private readonly ISenderClient _senderClient;
private readonly IMessageConverter<TMessage> _messageConverter;
public SenderClientWrapper(ISenderClient senderClient, IMessageConverter<TMessage> messageConverter)
{
_senderClient = senderClient;
_messageConverter = messageConverter;
}
public Task SendAsync(TMessage message, CancellationToken cancellationToken = default)
{
var internalMessage = _messageConverter.Convert(message);
return _senderClient.SendAsync(internalMessage);
}
public Task SendAsync(IEnumerable<TMessage> messages, CancellationToken cancellationToken = default)
{
var internalMessages = messages
.Select(_messageConverter.Convert)
.ToImmutableArray();
return _senderClient.SendAsync(internalMessages);
}
public async ValueTask DisposeAsync()
{
await _senderClient.CloseAsync();
}
}
internal abstract class AbstractClientWrapperFactory
{
private const int MaximumRetryCount = 5;
private static readonly TimeSpan MinimumRetryBackOff = TimeSpan.FromSeconds(1);
private static readonly TimeSpan MaximumRetryBackOff = TimeSpan.FromMinutes(1);
protected AbstractClientWrapperFactory(IOptions<MessageBusConfiguration> options)
{
Options = options;
}
protected IOptions<MessageBusConfiguration> Options { get; }
protected static string GetEntityPath<TMessage>() where TMessage : class
{
var messageAttribute = typeof(TMessage).GetCustomAttribute<AbstractMessageAttribute>();
if (messageAttribute == null)
{
throw new ArgumentException($#"Message requires {nameof(AbstractMessageAttribute)}");
}
return messageAttribute.EntityName;
}
protected TClient CreateClientEntity<TMessage, TClient>(ClientFactory<TClient> clientFactory)
where TMessage : class
{
var entityPath = GetEntityPath<TMessage>();
var retryPolicy = CreateRetryPolicy();
return clientFactory(Options.Value.ConnectionString, entityPath, retryPolicy);
}
protected static IQueueClient QueueClientFactory(string connectionString, string entityPath,
RetryPolicy retryPolicy)
{
return new QueueClient(connectionString, entityPath, retryPolicy: retryPolicy);
}
private static RetryPolicy CreateRetryPolicy()
{
return new RetryExponential(MinimumRetryBackOff, MaximumRetryBackOff, MaximumRetryCount);
}
}
internal sealed class SenderClientWrapperFactory : AbstractClientWrapperFactory, ISenderClientWrapperFactory
{
private readonly IMessageConverterFactory _messageConverterFactory;
public SenderClientWrapperFactory(IMessageConverterFactory messageConverterFactory,
IOptions<MessageBusConfiguration> options) : base(options)
{
_messageConverterFactory = messageConverterFactory;
}
public ISenderClientWrapper<TEvent> CreateTopicClient<TEvent>() where TEvent : class, IEvent
{
return CreateWrapper<TEvent, ITopicClient>(TopicClientFactory);
}
public ISenderClientWrapper<TRequest> CreateQueueClient<TRequest>() where TRequest : class, IRequest
{
return CreateWrapper<TRequest, IQueueClient>(QueueClientFactory);
}
private ISenderClientWrapper<TMessage> CreateWrapper<TMessage, TClient>(ClientFactory<TClient> clientFactory)
where TMessage : class, ICorrelative
where TClient : ISenderClient
{
var clientEntity = CreateClientEntity<TMessage, TClient>(clientFactory);
var messageConverter = _messageConverterFactory.Create<TMessage>();
return new SenderClientWrapper<TMessage>(clientEntity, messageConverter);
}
private static ITopicClient TopicClientFactory(string connectionString, string entityPath,
RetryPolicy retryPolicy)
{
return new TopicClient(connectionString, entityPath, retryPolicy);
}
}
internal sealed class ReceiverClientWrapperFactory : AbstractClientWrapperFactory, IReceiverClientWrapperFactory
{
private readonly IMessageConverterFactory _messageConverterFactory;
private readonly ILogger<ReceiverClientWrapperFactory> _logger;
public ReceiverClientWrapperFactory(IOptions<MessageBusConfiguration> options,
IMessageConverterFactory messageConverterFactory,
ILogger<ReceiverClientWrapperFactory> logger) : base(options)
{
_messageConverterFactory = messageConverterFactory;
_logger = logger;
}
public IReceiverClientWrapper<TEvent> CreateTopicClient<TEvent>() where TEvent : class, IEvent
{
return CreateReceiverClientWrapper<TEvent, ISubscriptionClient>(SubscriptionClientFactory);
}
public IReceiverClientWrapper<TRequest> CreateQueueClient<TRequest>() where TRequest : class, IRequest
{
return CreateReceiverClientWrapper<TRequest, IQueueClient>(QueueClientFactory);
}
private IReceiverClientWrapper<TMessage> CreateReceiverClientWrapper<TMessage, TClient>(
ClientFactory<TClient> clientFactory)
where TMessage : class, ICorrelative
where TClient : IReceiverClient
{
var clientEntity = CreateClientEntity<TMessage, TClient>(clientFactory);
var messageConverter = _messageConverterFactory.Create<TMessage>();
return new ReceiverClientWrapper<TMessage>(clientEntity, messageConverter, _logger,
Options.Value.MaximumConcurrency);
}
private ISubscriptionClient SubscriptionClientFactory(string connectionString, string entityPath,
RetryPolicy retryPolicy)
{
return new SubscriptionClient(connectionString, entityPath, Options.Value.SubscriberName,
retryPolicy: retryPolicy);
}
}
internal sealed class RequestService<TRequest> : IRequestService<TRequest> where TRequest : class, IRequest
{
private readonly Lazy<ISenderClientWrapper<TRequest>> _senderClient;
private readonly Lazy<IReceiverClientWrapper<TRequest>> _receiverClient;
public RequestService(ISenderClientWrapperFactory senderClientWrapperFactory,
IReceiverClientWrapperFactory receiverClientWrapperFactory)
{
_senderClient =
new Lazy<ISenderClientWrapper<TRequest>>(senderClientWrapperFactory.CreateQueueClient<TRequest>,
LazyThreadSafetyMode.PublicationOnly);
_receiverClient
= new Lazy<IReceiverClientWrapper<TRequest>>(receiverClientWrapperFactory.CreateQueueClient<TRequest>,
LazyThreadSafetyMode.PublicationOnly);
}
public Task PublishRequestAsync(TRequest requestMessage, CancellationToken cancellationToken = default)
{
return _senderClient.Value.SendAsync(requestMessage, cancellationToken);
}
public Task PublishRequestAsync(IEnumerable<TRequest> requestMessages,
CancellationToken cancellationToken = default)
{
return _senderClient.Value.SendAsync(requestMessages, cancellationToken);
}
public Task SubscribeAsync(OnRequestCallback<TRequest> onRequestCallback, OnFailureCallback onFailureCallback,
CancellationToken cancellationToken = default)
{
return _receiverClient
.Value
.SubscribeAsync((message, token) => onRequestCallback(message, cancellationToken), onFailureCallback,
cancellationToken);
}
public async ValueTask DisposeAsync()
{
if (_senderClient.IsValueCreated)
{
await _senderClient.Value.DisposeAsync();
}
if (_receiverClient.IsValueCreated)
{
await _receiverClient.Value.DisposeAsync();
}
}
public Task ThrowIfNotReadyAsync(CancellationToken cancellationToken = default)
{
return _senderClient.Value.SendAsync(ImmutableArray<TRequest>.Empty, cancellationToken);
}
}
This was never resolved using the original Nuget package, but the new Azure.Messaging.ServiceBus package does not appear to have the issue. I have opted to move to that.

Custom data annotation attribute are static

I have class named AuthenticatToken which looks like this
public class TokenBasedAuthentication : ActionFilterAttribute, IAuthenticationFilter
{
ILocalData local = null;
string token = string.Empty;
bool istokenvalid = false;
public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
istokenvalid = false;
await Task.Factory.StartNew(() =>
{
var req = context.Request;
IEnumerable<string> headervalues = new List<string>();
bool hastoken = req.Headers.TryGetValues(Constants.TOKEN, out headervalues);
if (hastoken)
{
local = new LocalData();
token = headervalues.FirstOrDefault();
istokenvalid = local.ValidateToken(token);
}
if (!istokenvalid)
{
context.ErrorResult = new AuthenticationFailureResult("Invalid token when accessing service", req);
}
else
{
IPrincipal incomingprincipal = context.ActionContext.RequestContext.Principal;
context.Principal = incomingprincipal;
}
});
}
public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
{
return Task.FromResult(0);
}
}
the issue is when calling the above class as custom data annotation in controller the variables are not initialized it uses previous values for second request to controller.
[TokenBasedAuthentication]
public class SettingsController : ApiController, ISettings
{
}
so my question is are data annotation static ?

checking Internet connection with HttpClient

I am having difficulties to understand on how the bellow code could handle occasional internet connection loss. Ideally I would like to pause the app, once the connection is lost, and resume when it is up again. Is there any guideline on how to do it?
HttpClientHandler clientHandler = new HttpClientHandler();
clientHandler.UseDefaultCredentials = true;
HttpClient client = new HttpClient(clientHandler) { MaxResponseContentBufferSize = 1000000 };
HttpResponseMessage response = await client.GetAsync(Url, ct);
The following example is not a direct solution, but it is an example I built to show how to return "pre-canned" content to requests whilst offline and then return back online when connectivity is restored. If you can get what I'm doing here, building what you want should be fairly easy.
[Fact]
public async Task Getting_a_response_when_offline()
{
var offlineHandler = new OfflineHandler(new HttpClientHandler(), new Uri("http://oak:1001/status"));
offlineHandler.AddOfflineResponse(new Uri("http://oak:1001/ServerNotRunning"),
new HttpResponseMessage(HttpStatusCode.NonAuthoritativeInformation)
{
Content = new StringContent("Here's an old copy of the information while we are offline.")
});
var httpClient = new HttpClient(offlineHandler);
var retry = true;
while (retry)
{
var response = await httpClient.GetAsync(new Uri("http://oak:1001/ServerNotRunning"));
if (response.StatusCode == HttpStatusCode.OK) retry = false;
Thread.Sleep(10000);
}
}
public class OfflineHandler : DelegatingHandler
{
private readonly Uri _statusMonitorUri;
private readonly Dictionary<Uri, HttpResponseMessage> _offlineResponses = new Dictionary<Uri, HttpResponseMessage>();
private bool _isOffline = false;
private Timer _timer;
public OfflineHandler(HttpMessageHandler innerHandler, Uri statusMonitorUri)
{
_statusMonitorUri = statusMonitorUri;
InnerHandler = innerHandler;
}
public void AddOfflineResponse(Uri uri, HttpResponseMessage response)
{
_offlineResponses.Add(uri,response);
}
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (_isOffline == true) return OfflineResponse(request);
try
{
var response = await base.SendAsync(request, cancellationToken);
if (response.StatusCode == HttpStatusCode.ServiceUnavailable || response.StatusCode == HttpStatusCode.BadGateway)
{
MonitorOfflineState();
return OfflineResponse(request);
}
return response;
}
catch (WebException ex)
{
MonitorOfflineState();
return OfflineResponse(request);
}
}
private void MonitorOfflineState()
{
_isOffline = true;
_timer = new Timer( async state =>
{
var request = new HttpRequestMessage() {RequestUri = _statusMonitorUri};
try
{
var response = await base.SendAsync(request, new CancellationToken());
if (response.StatusCode == HttpStatusCode.OK)
{
_isOffline = false;
_timer.Dispose();
}
}
catch
{
}
}, null, new TimeSpan(0,0,0),new TimeSpan(0,1,0));
}
private HttpResponseMessage OfflineResponse(HttpRequestMessage request)
{
if (_offlineResponses.ContainsKey(request.RequestUri))
{
return _offlineResponses[request.RequestUri];
}
return new HttpResponseMessage(HttpStatusCode.ServiceUnavailable);
}
}
}

Categories

Resources