I`ve made a simple Blazor server hub with [Authorize] attribute. Authorisation process is described in specific AuthorizationRequirement and is considered successful only if incoming HTTP request has specified headers.
[Authorize(Policy = EnterpriseServiceBusHub.EsbHubAuthorizationPolicy)]
public class EnterpriseServiceBusHub : Hub
{
public const string EsbHubPath = "/esb-hub";
public const string EsbHubMethod = "InvokeIntegration";
public const string EsbHubAuthorizationPolicy = "EsbHubAuthorizationPolicy";
public const string EsbIntegrationID = "ESB-Integration-ID";
public const string EsbIntegrationName = "ESB-Integration-Channel";
private readonly IEntepriseServiceBus _entepriseServiceBus;
public EnterpriseServiceBusHub(IEntepriseServiceBus entepriseServiceBus)
{
_entepriseServiceBus = entepriseServiceBus;
}
[HubMethodName(EsbHubMethod)]
public async Task InvokeIntegration(string integrationChannel, string encryptedMessage)
{
await Clients.Others.SendAsync(integrationChannel, encryptedMessage);
}
}
public class EsbAuthorizationRequirement : AuthorizationHandler<EsbAuthorizationRequirement>, IAuthorizationRequirement
{
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, EsbAuthorizationRequirement requirement)
{
try
{
await EsbAuthorizationChecker.CheckAuthorizationAsync();
context.Succeed(requirement);
}
catch (Exception ex)
{
context.Fail(new AuthorizationFailureReason(this, ex.Message));
}
}
}
static class EsbAuthorizationChecker
{
public static async Task<bool> CheckAuthorizationAsync()
{
var httpContextAccessor = ServicesHolder.GetService<IHttpContextAccessor>();
var requestHeaders = httpContextAccessor.HttpContext.Request.Headers;
if (!requestHeaders.TryGetValue(EnterpriseServiceBusHub.EsbIntegrationID, out var integrationID) ||
!requestHeaders.TryGetValue(EnterpriseServiceBusHub.EsbIntegrationName, out var integrationUrl))
{
var message = $"Headers {EnterpriseServiceBusHub.EsbIntegrationID} or {EnterpriseServiceBusHub.EsbIntegrationName} were not provided";
throw new Exception(message);
}
return await Task.FromResult(true);
}
}
The problem is that even specifying those headers in HubConnectionBuilder, attempt to start hub connection throws 400 (Bad request) exception.
_hubConnection = new HubConnectionBuilder().WithUrl($#"{Url}/{EnterpriseServiceBusHub.EsbHubPath}",
(HttpConnectionOptions options) =>
{
options.Headers.Add(EnterpriseServiceBusHub.EsbIntegrationID, "Value-1");
options.Headers.Add(EnterpriseServiceBusHub.EsbIntegrationName, "Value-2");
}).Build();
await _hubConnection.StartAsync(); // throws 400 Bad request
The specified authorisation policy is included in Program.cs
builder.Services.AddAuthorization(options =>
{
options.AddPolicy(EnterpriseServiceBusHub.EsbHubAuthorizationPolicy, policy =>
{
policy.Requirements.Add(new EsbAuthorizationRequirement());
});
});
builder.Services.AddSignalR(config =>
{
config.EnableDetailedErrors = true;
config.ClientTimeoutInterval = null;
});
What can be the reason for that? Am I specifying headers wrong? Thank you.
I am making a notification microservice for users. It is impossible to use the SignalR built into MassTransit due to the built-in microservice architecture.
After accepting messages from the queue RabbitMq, it is called StartAsync:
public class MassTransitManager : IMassTransitManager
{
public MassTransitManager(Func<string, Task<string>> processDelegateAsync)
{
ProcessDelegateAsync = processDelegateAsync;
}
public Func<string, Task<string>> ProcessDelegateAsync { get; set; }
public async Task<string> StartAsync(string requestString)
{
string response;
try
{
response = await ProcessMessageAsync(requestString);
}
catch (Exception exception)
{
response = exception.ToString();
}
return response;
}
private async Task<string> ProcessMessageAsync(string requestString)
{
if (ProcessDelegateAsync == null)
throw new InvalidOperationException($"Not found delegate {nameof(ProcessDelegateAsync)}.");
var result = await ProcessDelegateAsync(requestString);
return result;
}
}
MassTransitManager is in Class Library. Delegate created in Startup:
services.AddSingleton<IMassTransitManager, MassTransitManager>(provider =>
{
return new MassTransitManager(async serializedMessage =>
{
var hubContext = provider.GetRequiredService<IHubContext<NoticeHub>>();
await hubContext.Clients.All.SendAsync("SendResponse", "Hi all.");
return "Hi man";
});
});
But no client receives this message. What can be the ways to solve this problem?
Here I am trying to remove ConnectionId from connected connectedId list UserHandler.ConnectedUser in OnDisconnectedAsync method of my hub.
But the problem is that when ever user gets logout from application or close that window the OnDisconnectedAsync method on hub does not get hit.
Any help with my code will be grate. Thank you.
Below is my hub
public class ChatHub : Hub
{
public List<string> SendingUserList = new List<string>();
public async Task SendMessage(string to, string message)
{
var senderConnectionId = Context.ConnectionId;
SendingUserList.Add(to);
SendingUserList.Add(senderConnectionId);
foreach (var connectionId in SendingUserList)
{
await Clients.Client(connectionId).SendAsync("ReceiveMessage", message);
}
}
public override Task OnConnectedAsync()
{
Clients.All.SendAsync("ReciveUser", UserHandler.UserName, Context.ConnectionId);
return base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception e)
{
UserHandler.ConnectedUser.Remove(Context.ConnectionId));
await base.OnDisconnectedAsync(e);
}
}
public static class UserHandler
{
public static HashSet<string> ConnectedUser = new HashSet<string>();
public static string UserName { get; set; }
}
Below is my remaining code
protected override async Task OnInitializedAsync()
{
UserHandler.UserName = httpContextAccessor.HttpContext.User.Identity.Name;
hubConnection = new HubConnectionBuilder()
.WithUrl(NavigationManager.ToAbsoluteUri("/chathub"))
.Build();
hubConnection.ServerTimeout = TimeSpan.FromMinutes(60);
hubConnection.On<string>("ReceiveMessage", BroadcastMessage);
hubConnection.On<string,string>("ReciveUser", RefreshUserList);
await hubConnection.StartAsync();
StateHasChanged();
}
private void RefreshUserList(string connectedUserId, string connectionId )
{
UserHandler.ConnectedUser.Add(connectionId);
connectedUserList = UserHandler.ConnectedUser;
StateHasChanged();
}
private void BroadcastMessage(string message)
{
var encodedMsg = $"{message}";
messages.Add(encodedMsg);
StateHasChanged();
}
public async ValueTask DisposeAsync()
{
await hubConnection.DisposeAsync();
}
You'll need to implement the IAsyncDisposable interface in the Hub's consumer (Razor Component), like this:
#implements IAsyncDisposable
Add these methods:
public void Dispose()
{
hubConnection.DisposeAsync();
}
public async ValueTask DisposeAsync()
{
await hubConnection.DisposeAsync();
}
Now, when you close the client's page the OnDisconnectedAsync method will be called.
In my App, there is an Async Function ProcessOffer(). When I called it in Constructor as ProcessOffer, it works but synchronously. I want to call this Function in constructor asynchronously.
The ProcessOffer() is a function implemented in CredentialViewModel, but I want that, It should be triggered asynchronously everywhere on the App (IndexViewModel e.t.c).
If I'm on the IndexPage, and Web Application sends a request to Mobile Application, ProcessOffer(), should be triggered.. actually what ProcessOffer does is, that it asks the user to enter a PIN, if it's correct, it sends back a response to the Web Application.
I've tried answers from other Posts, but they returned the Error Autofac.Core.DependencyResolutionException: 'An exception was thrown while activating App.Name, when I sends a request to Mobile App from Web Application.
The Solutions I tried.
1- https://stackoverflow.com/a/64012442/14139029
2- Task.Run(() => ProcessOffer()).Wait();
3- ProcessOffer().GetAwatier().GetResult();
CredentialViewModel.cs
namespace Osma.Mobile.App.ViewModels.Credentials
{
public class CredentialViewModel : ABaseViewModel
{
private readonly CredentialRecord _credential;
private readonly ICredentialService _credentialService;
private readonly IAgentProvider _agentContextProvider;
private readonly IConnectionService _connectionService;
private readonly IMessageService _messageService;
private readonly IPoolConfigurator _poolConfigurator;
[Obsolete]
public CredentialViewModel(
IUserDialogs userDialogs,
INavigationService navigationService,
ICredentialService credentialService,
IAgentProvider agentContextProvider,
IConnectionService connectionService,
IMessageService messageService,
IPoolConfigurator poolConfigurator,
CredentialRecord credential
) : base(
nameof(CredentialViewModel),
userDialogs,
navigationService
)
{
_credential = credential;
_credentialService = credentialService;
_agentContextProvider = agentContextProvider;
_connectionService = connectionService;
_messageService = messageService;
_poolConfigurator = poolConfigurator;
_credentialState = _credential.State.ToString();
if (_credentialState == "Offered")
{
ProcessOffer();
}
}
[Obsolete]
public async Task ProcessOffer()
{
foreach (var item in _credential.CredentialAttributesValues)
{
await SecureStorage.SetAsync(item.Name.ToString(), item.Value.ToString());
}
var RegisteredPIN = await SecureStorage.GetAsync("RegisteredPIN");
string PIN = await App.Current.MainPage.DisplayPromptAsync("Enter PIN", null, "Ok", "Cancel", null, 6, Keyboard.Numeric);
if (PIN == RegisteredPIN)
{
try
{
//await _poolConfigurator.ConfigurePoolsAsync();
var agentContext = await _agentContextProvider.GetContextAsync();
var credentialRecord = await _credentialService.GetAsync(agentContext, _credential.Id);
var connectionId = credentialRecord.ConnectionId;
var connectionRecord = await _connectionService.GetAsync(agentContext, connectionId);
(var request, _) = await _credentialService.CreateRequestAsync(agentContext, _credential.Id);
await _messageService.SendAsync(agentContext.Wallet, request, connectionRecord);
await DialogService.AlertAsync("Request has been sent to the issuer.", "Success", "Ok");
}
catch (Exception e)
{
await DialogService.AlertAsync(e.Message, "Error", "Ok");
}
}
else if (PIN != RegisteredPIN && PIN != null)
{
DialogService.Alert("Provided PIN is not correct");
}
}
#region Bindable Command
[Obsolete]
public ICommand ProcessOfferCommand => new Command(async () => await ProcessOffer());
public ICommand NavigateBackCommand => new Command(async () =>
{
await NavigationService.PopModalAsync();
});
#endregion
#region Bindable Properties
private string _credentialState;
public string CredentialState
{
get => _credentialState;
set => this.RaiseAndSetIfChanged(ref _credentialState, value);
}
#endregion
}
}
IndexViewModel.cs
namespace Osma.Mobile.App.ViewModels.Index
{
public class IndexViewModel : ABaseViewModel
{
private readonly IConnectionService _connectionService;
private readonly IMessageService _messageService;
private readonly IAgentProvider _agentContextProvider;
private readonly IEventAggregator _eventAggregator;
private readonly ILifetimeScope _scope;
public IndexViewModel(
IUserDialogs userDialogs,
INavigationService navigationService,
IConnectionService connectionService,
IMessageService messageService,
IAgentProvider agentContextProvider,
IEventAggregator eventAggregator,
ILifetimeScope scope
) : base(
"Index",
userDialogs,
navigationService
)
{
_connectionService = connectionService;
_messageService = messageService;
_agentContextProvider = agentContextProvider;
_eventAggregator = eventAggregator;
_scope = scope;
}
public override async Task InitializeAsync(object navigationData)
{
await base.InitializeAsync(navigationData);
}
public class Post
{
public string Success { get; set; }
public string firstname { get; set; }
}
[Obsolete]
public async Task ScanVerification(object sender, EventArgs e)
{
// Code
}
public async Task SettingsPage(SettingsViewModel settings) => await NavigationService.NavigateToAsync(settings, null, NavigationType.Modal);
#region Bindable Command
public ICommand SettingsPageCommand => new Command<SettingsViewModel>(async (settings) =>
{
await SettingsPage(settings);
});
[Obsolete]
public ICommand ScanVerificationCommand => new Command(async () => await ScanVerification(default, default));
#endregion
}
}
App.xml.cs
[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
namespace Osma.Mobile.App
{
public partial class App : Application
{
public new static App Current => Application.Current as App;
public static IContainer Container { get; set; }
// Timer to check new messages in the configured mediator agent every 10sec
private readonly Timer timer;
private static IHost Host { get; set; }
public App()
{
InitializeComponent();
timer = new Timer
{
Enabled = false,
AutoReset = true,
Interval = TimeSpan.FromSeconds(10).TotalMilliseconds
};
timer.Elapsed += Timer_Elapsed;
}
public App(IHost host) : this() => Host = host;
public static IHostBuilder BuildHost(Assembly platformSpecific = null) =>
XamarinHost.CreateDefaultBuilder<App>()
.ConfigureServices((_, services) =>
{
services.AddAriesFramework(builder => builder.RegisterEdgeAgent(
options: options =>
{
options.AgentName = "Mobile Holder";
options.EndpointUri = "http://11.222.333.44:5000";
options.WalletConfiguration.StorageConfiguration =
new WalletConfiguration.WalletStorageConfiguration
{
Path = Path.Combine(
path1: FileSystem.AppDataDirectory,
path2: ".indy_client",
path3: "wallets")
};
options.WalletConfiguration.Id = "MobileWallet";
options.WalletCredentials.Key = "SecretWalletKey";
options.RevocationRegistryDirectory = Path.Combine(
path1: FileSystem.AppDataDirectory,
path2: ".indy_client",
path3: "tails");
// Available network configurations (see PoolConfigurator.cs):
options.PoolName = "sovrin-test";
},
delayProvisioning: true));
services.AddSingleton<IPoolConfigurator, PoolConfigurator>();
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterAssemblyModules(typeof(CoreModule).Assembly);
if (platformSpecific != null)
{
containerBuilder.RegisterAssemblyModules(platformSpecific);
}
containerBuilder.Populate(services);
Container = containerBuilder.Build();
});
protected override async void OnStart()
{
await Host.StartAsync();
// View models and pages mappings
var _navigationService = Container.Resolve<INavigationService>();
_navigationService.AddPageViewModelBinding<MainViewModel, MainPage>();
_navigationService.AddPageViewModelBinding<RegisterViewModel, RegisterPage>();
_navigationService.AddPageViewModelBinding<IndexViewModel, IndexPage>();
_navigationService.AddPageViewModelBinding<SettingsViewModel, SettingsPage>();
_navigationService.AddPageViewModelBinding<CredentialsViewModel, CredentialsPage>();
_navigationService.AddPageViewModelBinding<CredentialViewModel, CredentialPage>();
if (Preferences.Get(AppConstant.LocalWalletProvisioned, false))
{
await _navigationService.NavigateToAsync<MainViewModel>();
}
else
{
await _navigationService.NavigateToAsync<ProviderViewModel>();
}
timer.Enabled = true;
}
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
// Check for new messages with the mediator agent if successfully provisioned
if (Preferences.Get(AppConstant.LocalWalletProvisioned, false))
{
Device.BeginInvokeOnMainThread(async () =>
{
try
{
var context = await Container.Resolve<IAgentProvider>().GetContextAsync();
await Container.Resolve<IEdgeClientService>().FetchInboxAsync(context);
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
});
}
}
protected override void OnSleep() =>
// Stop timer when application goes to background
timer.Enabled = false;
protected override void OnResume() =>
// Resume timer when application comes in foreground
timer.Enabled = true;
}
}
Look, you have public override async Task InitializeAsync(object navigationData) method in the IndexViewModel class. I suppose the framework invoke it to initialize the view model. So, thus CredentialViewModel inherits same ABaseViewModel anyway, why wouldn't you just override InitializeAsync in your CredentialViewModel and call ProcessOffer from it?
public CredentialViewModel(
IUserDialogs userDialogs,
INavigationService navigationService,
ICredentialService credentialService,
IAgentProvider agentContextProvider,
IConnectionService connectionService,
IMessageService messageService,
IPoolConfigurator poolConfigurator,
CredentialRecord credential
) : base(
nameof(CredentialViewModel),
userDialogs,
navigationService
)
{
_credential = credential;
_credentialService = credentialService;
_agentContextProvider = agentContextProvider;
_connectionService = connectionService;
_messageService = messageService;
_poolConfigurator = poolConfigurator;
_credentialState = _credential.State.ToString();
}
public override async Task InitializeAsync(object navigationData)
{
if (_credentialState != "Offered") return;
await ProcessOffer();
}
Anyway, you have to avoid calling asynchronous operations in constructors.
So in my View Models, if i need to initialize asynchronously I usually pass OnAppearing() to the view model instead of the constructor. Someone can tell me if this is unwise but it solved my needs.
Code Behind View:
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class CredentialView : ContentPage
{
public CredentialView()
{
InitializeComponent();
BindingContext = new CredentialViewModel();
}
protected override void OnAppearing()
{
base.OnAppearing();
((CredentialViewModel)BindingContext).OnAppearing();
}
}
View Model:
public class CredentialViewModel : INotifyPropertyChanged
{
public CredentialViewModel ()
{
}
public async void OnAppearing()
{
await ProcessOffer();
}
}
Also just FYI, the biggest gotcha i've experienced with asynchronous code on xamarin is to use BeginInvokeOnMainThread. It's my understanding that touched UI should be executed on main thread! (probably why they call it the UI thread. haha)
Example:
Device.BeginInvokeOnMainThread(() =>
{
observableCollection.Clear();
});
As you've already asked before, kicking off async code in the constructor of a class can be a can of worms. You should instead consider doing either:
Starting ProcessOffer in a lifecycle method instead. For instance when the View is showing call ProcessOfferCommand.
Using fire and forget to not block the constructor: Task.Run(ProcessOffer) you should probably avoid this though.
Use something like NotifyTask Stephen Cleary describes here: https://learn.microsoft.com/en-us/archive/msdn-magazine/2014/march/async-programming-patterns-for-asynchronous-mvvm-applications-data-binding you can find the complete code for it here: https://github.com/StephenCleary/Mvvm.Async/blob/master/src/Nito.Mvvm.Async/NotifyTask.cs
Use a CreateAsync pattern with a private constructor. However, this doesn't work well with Dependency Injection usually. Doing IO intensive work during resolution isn't really the correct place to do it.
In my opinion using either 1. or 3. would be the best solutions, perhaps leaning towards a combination of 1. using NotifyTask that can notify you when it is done loading.
For testing purposes, I am using this directly inside of a razor block in a .cshtml page.
#functions{
public class Inline
{
public HttpResponseBase r { get; set; }
public int Id { get; set; }
public List<System.Threading.Tasks.Task> tasks = new List<System.Threading.Tasks.Task>();
public void Writer(HttpResponseBase response)
{
this.r = response;
tasks.Add(System.Threading.Tasks.Task.Factory.StartNew(
() =>
{
while (true)
{
r.Write("<span>Hello</span>");
System.Threading.Thread.Sleep(1000);
}
}
));
}
}
}
#{
var inL = new Inline();
inL.Writer(Response);
}
I had expected it to write a span with the text "Hello" once every second. It will write "Hello" once sometimes, but not every time or even most times. Why isn't this task long running?
The reason you are seeing different result is because the task is running asynchronously and if the response object is completed before your task gets a chance to write on it, the taks will throw exception and it will terminate the only way you can do this is if you add Task.WaitAll() at the end of the Writer() method.
This will work but the page will not stop loading content.
this.r = response;
tasks.Add(System.Threading.Tasks.Task.Factory.StartNew(
() =>
{
while (true)
{
r.Write("<span>Hello</span>");
r.Flush(); // this will send each write to the browser
System.Threading.Thread.Sleep(1000);
}
}
));
//this will make sure that the response will stay open
System.Threading.Tasks.Task.WaitAll(tasks.ToArray());
Here is another option this one uses a custom ActionResult , it first process the controller (the default result) after that is done it starts the task.
public class CustomActionResult:ViewResult
{
public override void ExecuteResult(ControllerContext context)
{
base.ExecuteResult(context);
var t = Task.Factory.StartNew(() =>
{
while (true)
{
Thread.Sleep(1000);
context.HttpContext.Response.Write("<h1>hello</h1>");
context.HttpContext.Response.Flush();
}
});
Task.WaitAll(t);
}
}
In your controller
public class HomeController : Controller
{
public ActionResult Index()
{
return new CustomActionResult();
}
}