Calling a Client Method on a Windows Service - asp.net-mvc

I have a SignalR client in a Windows Service that successfully calls a Server method in an MVC app. First the Server Code:
public class AlphaHub : Hub
{
public void Hello(string message)
{
// We got the string from the Windows Service
// using SignalR. Now need to send to the clients
Clients.All.addNewMessageToPage(message);
// Send message to Windows Service
}
and
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
app.MapSignalR("/signalr", new HubConfiguration());
}
}
The Windows Service client is:
protected override async void OnStart(string[] args)
{
eventLog1.WriteEntry("In OnStart");
try
{
var hubConnection = new HubConnection("http://localhost.com/signalr", useDefaultUrl: false);
IHubProxy alphaProxy = hubConnection.CreateHubProxy("AlphaHub");
await hubConnection.Start();
await alphaProxy.Invoke("Hello", "Message from Service");
}
catch (Exception ex)
{
eventLog1.WriteEntry(ex.Message);
}
}
It sends a message to the MVC Server. Now I want to call the other way from server to client. The Client Programming Guide has the following code examples which will NOT work as this is not a desktop.
WinRT Client code for method called from server without parameters (see WPF and Silverlight examples later in this topic)
var hubConnection = new HubConnection("http://www.contoso.com/");
IHubProxy stockTickerHubProxy = hubConnection.CreateHubProxy("StockTickerHub");
stockTickerHub.On("notify", () =>
// Context is a reference to SynchronizationContext.Current
Context.Post(delegate
{
textBox.Text += "Notified!\n";
}, null)
);
await hubConnection.Start();
How can I call a method on the client?

The .NET client side code seems fine. You can simply get rid of Context.Post since your client is running inside of a Windows Service and doesn't need a SyncContext:
protected override async void OnStart(string[] args)
{
eventLog1.WriteEntry("In OnStart");
try
{
var hubConnection = new HubConnection("http://localhost.com/signalr", useDefaultUrl: false);
IHubProxy alphaProxy = hubConnection.CreateHubProxy("AlphaHub");
stockTickerHub.On("Notify", () => eventLog1.WriteEntry("Notified!"));
await hubConnection.Start();
await alphaProxy.Invoke("Hello", "Message from Service");
}
catch (Exception ex)
{
eventLog1.WriteEntry(ex.Message);
}
}
You can invoke the "Notify" callback from inside your AlphaHub on the server like so:
public class AlphaHub : Hub
{
public void Hello(string message)
{
// We got the string from the Windows Service
// using SignalR. Now need to send to the clients
Clients.All.addNewMessageToPage(message);
// Send message to the Windows Service
Clients.All.Notify();
}
Any client will be able to listen to these notifications since we are using Clients.All. If you want to avoid this, you need some way to authenticate your Windows Service and get its ConnectionId. Once you have that, you can send to the Windows Service specifically like so:
Clients.Client(serviceConnectionId).Notify();

Hope this helps.
Windows Service with self hosted SignalR
public partial class MyWindowsService : ServiceBase
{
IDisposable SignalR { get; set; }
public class SignalRStartup
{
public static IAppBuilder App = null;
public void Configuration(IAppBuilder app)
{
app.Map("/signalr", map =>
{
map.UseCors(CorsOptions.AllowAll);
var hubConfiguration = new HubConfiguration()
{
// EnableDetailedErrors = true
};
map.RunSignalR(hubConfiguration);
});
}
}
public MyWindowsService()
{
InitializeComponent();
}
protected override void OnStart(string[] args) { Start(); }
protected override void OnStop() { Stop(); }
public void Start()
{
SignalR = WebApp.Start<SignalRStartup>("http://localhost:8085/signalr");
CallToMvcJavascript();
}
public new void Stop()
{
SignalR.Dispose();
}
private void CallToMvcJavascript(){
GlobalHost.ConnectionManager.GetHubContext<MyHub>().Clients.All.addNotice(// object/data to send//);
}
}
The Hub in the Windows Service
public class MyHub : Hub
{
public void Send()
{
Clients.All.confirmSend("The service received the client message");
}
}
The Javascript
$.connection.hub.logging = true;
$.connection.hub.url = "http://localhost:8085/signalr";
var notices = $.connection.myHub;
notices.client.addNotice = function(notice) {
console.log(notice);
};
notices.client.confirmSend = function(msg) {
alert(msg);
};
$.connection.hub.start().done(function() {
$('#myTestBtn').on('click', function() {
notices.server.send();
});
});

Related

Reply back to exactly same client who connected and called with SignalR

I am having a two tier application, with one being Windows Form application and other being Web Application with MVC.
Desktop Application has a SignalR Hub area which manages all client connected to it from Web App.
Hub Class
public delegate void ClientConnectionEventHandler(string clientId);
public delegate void ClientNameChangedEventHandler(string clientId, string newName);
public delegate void ClientInitializeEventHandler(string clientId);
public class StockTickerHub:Hub
{
static ConcurrentDictionary<string, string> _users = new ConcurrentDictionary<string, string>();
public static event ClientConnectionEventHandler ClientConnected;
public static event ClientConnectionEventHandler ClientDisconnected;
public static event ClientNameChangedEventHandler ClientNameChanged;
public static event ClientInitializeEventHandler ClientInitialized;
//Called when a client is connected
public override Task OnConnected()
{
_users.TryAdd(Context.ConnectionId, Context.ConnectionId);
ClientConnected?.Invoke(Context.ConnectionId);
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled)
{
string username;
_users.TryRemove(Context.ConnectionId, out username);
ClientDisconnected?.Invoke(Context.ConnectionId);
return base.OnDisconnected(stopCalled);
}
public void SetUserName(string userName)
{
_users[Context.ConnectionId] = userName;
ClientNameChanged?.Invoke(Context.ConnectionId, userName);
}
public void InitializeGrid()
{
ClientInitialized?.Invoke(Context.ConnectionId);
}
}
When web client connects to Desktop App, it's being added and connected.
Inherited Hub Class
public class ClientGateway
{
private BindingList<ClientItem> _clients = new BindingList<ClientItem>();
frmMasterTicker frm;
public ClientGateway()
{
//Register to hub events
StockTickerHub.ClientConnected += StockTickerHub_ClientConnected;
StockTickerHub.ClientNameChanged += StockTickerHub_ClientNameChanged;
StockTickerHub.ClientDisconnected += StockTickerHub_ClientDisconnected;
StockTickerHub.ClientInitialized += StockTickerHub_ClientInitialized;
}
private void StockTickerHub_ClientInitialized(string clientId)
{
InitializeGrid(clientId);
}
private void StockTickerHub_ClientDisconnected(string clientId)
{
var client = _clients.FirstOrDefault(x => x.Id == clientId);
if (client != null)
{
_clients.Remove(client);
}
}
private void StockTickerHub_ClientNameChanged(string clientId, string newName)
{
//Update client's name if it's available
var client = _clients.FirstOrDefault(x => x.Id == clientId);
if (client != null)
{
client.Name = newName;
SetOperationLogMessage.AddLogMessage(this.ToString(), "", $"Client name changed. Id:{clientId}, Name:{newName}");
SendTestMessage();
}
}
private void StockTickerHub_ClientConnected(string clientId)
{
//Add client to the list
_clients.Add(new ClientItem() { Id = clientId, Name = clientId });
SetOperationLogMessage.AddLogMessage("ClientGateway", "StockTickerHub_ClientConnected", $"Client connected:{clientId}");
}
public void SendTestMessage()
{
var hubContext = GlobalHost.ConnectionManager.GetHubContext<StockTickerHub>();
hubContext.Clients.All.addMessage("Ticker Server", "Hello handshake from server.");
}
public void InitializeGrid(string connectionid)
{
if (_clients.Count > 0)
{
frm = (frmMasterTicker)Helper.GetOpenForm("frmMasterTicker");
//string msg = "Hello from server at " + DateTime.Now.ToString();
string msg = JsonConvert.SerializeObject(frm.GetInitializeDataFromGrid());
var hubContext = GlobalHost.ConnectionManager.GetHubContext<StockTickerHub>();
hubContext.Clients.Client(connectionid).initializeGrid(msg);
}
}
public void SendTickerData(object lstStock)
{
if (_clients.Count > 0)
{
//string msg = "Hello from server at " + DateTime.Now.ToString();
string msg = JsonConvert.SerializeObject(lstStock);
var hubContext = GlobalHost.ConnectionManager.GetHubContext<StockTickerHub>();
hubContext.Clients.Client("").getTickerData(msg);
}
}
}
Web Page Code
//Connect to SignalR server and get the proxy
function connect() {
$.connection.hub.url = url;
simpleHubProxy = $.connection.stockTickerHub;
if (simpleHubProxy) {
$.connection.hub.start().done(function () {
writeToLog("Connected...");
simpleHubProxy.server.setUserName("RMSAdmin");
RequestGridInitialData();
})
.fail(function () {
alert("Can't connect");
})
;
simpleHubProxy.client.addMessage = function (name, message) {
writeToLog(name + ":" + message);
}
simpleHubProxy.client.initializeGrid = function (message) {
dtSource = JSON.parse(message);
$("#grid").data("kendoGrid").dataSource.data(dtSource);
}
simpleHubProxy.client.getTickerData = function (message) {
writeToLog(message);
}
$.connection.hub.disconnected(function () {
writeToLog("Server disconnected.");
});
$.connection.hub.reconnecting(function () {
writeToLog("Server reconnecting...");
});
$.connection.hub.reconnected(function () {
writeToLog("Server reconnected...");
});
$.connection.hub.error(function (error) {
console.log('SignalR error: ' + error)
});
}
}
connect();
I can get the connection id from Hub.
Now I have a Windows Form. In which, I want to send data to exactly the same client who connected recently. I have a list of all clients connection with id. But within that, who connected recently and to whom I need to send data from Form, I am unable to do the progress with.
Following is a code try inside a Windows Form, which works, but it sends data to all connected client.
hubContext.Clients.All.getTickerData(JsonConvert.SerializeObject(tmpStock));
I want to send data only to that client who connects recently. How should I do that?
In the piece of code where you are sending the windows form data, you can call:
hubContext.Clients.Caller.getTickerData(JsonConvert.SerializeObject(tmpStock));
The .Caller will send the message to the client that invoked the method.
You can read more about it, in the oficial microsoft documentation, here: https://learn.microsoft.com/en-us/aspnet/signalr/overview/guide-to-the-api/hubs-api-guide-server#selectingclients
Edit: Since you don't have access to the .Caller method outside the hub you need to save the ClientId inside the hub and pass it the the outside class... SO there you will be able to call the caller client by id:
hubContext.Clients.Client(clientId).getTickerData(JsonConvert.SerializeObject(tmpStock));

websocket handhsake exception: 200

I am using springboot to setup my websocket endpoint and angularJs to connect to my endpoint. which i have done before and it worked fine that time. but now when i do it in new project. its giving me hand shake error. below is mycode:
Websocket config
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/chatService").setAllowedOrigins("*").addInterceptors(new HttpSessionHandshakeInterceptor());
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/app");
registry.enableSimpleBroker("/chat");
}
}
angularJs code:
var socket = new WebSocket('ws://192.168.225.133:9191/chatService');
// console.log(socket);
stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
if(frame.command=="CONNECTED"){
//alert(frame);
//showLoader(false);
stompClient.subscribe('http://localhost:9191/chat/messages', function(response) {
var data = JSON.parse(response.body);
chat.gif=false;
console.log("data_json...");
if(data.to==$scope.admin.id)
$scope.getMessagesById(data.from,1);
else
$scope.getMessagesById(data.to,1);
console.log(data);
$scope.myImgs=null;
});
}
else{
var r = confirm("Could not connect! Retry?");
if (r == true) {
connect();
}
}
});
}
in my project there was a client forward controller. disabling that fixed the issue.

Background Thread that uses ApplicaitonDBContext

I am trying to wire up a background thread that will update the database once an hour from Active Directory. I am not sure how to pass the current
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => false;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
// Add framework services.
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("Connection")));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1).AddSessionStateTempDataProvider();
services.AddSession();
services.AddHttpContextAccessor();
services.AddSingleton(Configuration);
services.AddScoped<IAppDbRepository, AppDbRepository>();
services.AddScoped<IActiveDirectoryUtility, ActiveDirectoryUtility>();
services.AddScoped<IActiveDirectoryManager, ActiveDirectoryManager>();
services.AddHostedService<LdapManager>();
services.AddScoped<ILdapManager, LdapManager>();
}
In the LdapManager class I would like to call the UpdateUsers method every hour:
public class LdapManager : ILdapManager, IHostedService
{
private IConfiguration _configuration = null;
private Logging _logger;
private List<string> ldapConnectorForDirectoryEntries = new List<string>();
public LdapManager(IConfiguration configuration)
{
_configuration = configuration;
UpdateUsers();
SyncActiveDirectoryUsers();
}
public void SyncActiveDirectoryUsers()
{
try
{
using (var waitHandle = new AutoResetEvent(false))
{
ThreadPool.RegisterWaitForSingleObject(waitHandle, (state, timeout) => { UpdateUsers(); }, null, TimeSpan.FromHours(1), false);
}
}
catch
{
throw;
}
}
}
The UpdateUsers() method should be able to call the applicationDBContext.SaveChanges() method.
How can I ensure that the LDAP manger class can use the Application DB context?
You probably want class LdapManager : BackgroundService, ILdapManager
BackgroundService is .NET Core 2.1, there is a code sample available for core 2.0
Inject IServiceScopeFactory and override Task ExecuteAsync( ), run a while loop there.
while(!stoppingToken.IsCancellationRequested)
{
using (var scope = _serviceScopeFactory.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
...; // do your stuff
}
await Task.Delay(myConfig.BackgroundDelay, stoppingToken);
}
And here is a good read about this on MSDN, including the code sample for 2.0
For accessing ApplicationDbContext from HostedService.
DbHostedService
public class DbHostedService : IHostedService
{
private readonly ILogger _logger;
public DbHostedService(IServiceProvider services,
ILogger<DbHostedService> logger)
{
Services = services;
_logger = logger;
}
public IServiceProvider Services { get; }
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Consume Scoped Service Hosted Service is starting.");
DoWork();
return Task.CompletedTask;
}
private void DoWork()
{
_logger.LogInformation("Consume Scoped Service Hosted Service is working.");
using (var scope = Services.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
var user = context.Users.LastOrDefault();
_logger.LogInformation(user?.UserName);
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Consume Scoped Service Hosted Service is stopping.");
return Task.CompletedTask;
}
}
Register DbHostedService
services.AddHostedService<DbHostedService>();

Implement dependency injection in background services in Xamarin Forms using Prism

I am making use of Prism in my xamarin forms project.I was able to use dependency injection(constructor injection) in my View Model without any problems.I am also making use of background services to push long running tasks in the background.How do I inject dependency in my Background services?When I try to pass the interface object as a paramater to the constructor(SyncingBackgroundingCode) ,the object(SqliteService) is null.I have registered and resolved the objects in the dependency injection container.
How to handle this case?Can anybody provide an example or link to implement this scenario?
This is the piece of code where im trying to implement dependency injection.
This is in Droid :-
public class AndroidSyncBackgroundService : Service
{
CancellationTokenSource _cts;
public override IBinder OnBind (Intent intent)
{
return null;
}
public override StartCommandResult OnStartCommand (Intent intent, StartCommandFlags flags, int startId)
{
_cts = new CancellationTokenSource ();
Task.Run (() => {
try {
//INVOKE THE SHARED CODE
var oBackground = new SyncingBackgroundingCode();
oBackground.RunBackgroundingCode(_cts.Token).Wait();
}
catch (OperationCanceledException)
{
}
finally {
if (_cts.IsCancellationRequested)
{
var message = new CancelledTask();
Device.BeginInvokeOnMainThread (
() => MessagingCenter.Send(message, "CancelledTask")
);
}
}
}, _cts.Token);
return StartCommandResult.Sticky;
}
public override void OnDestroy ()
{
if (_cts != null) {
_cts.Token.ThrowIfCancellationRequested ();
_cts.Cancel ();
}
base.OnDestroy ();
}
}
This is in PCL:-
public class SyncingBackgroundingCode
{
public SQLiteConnection _sqlconnection;
SqliteCalls oSQLite = new SqliteCalls();
ISqliteService _SqliteService;
public SyncingBackgroundingCode(ISqliteService SqliteService)
{
//object is null
}
public async Task RunBackgroundingCode(CancellationToken token)
{
DependencyService.Get<ISQLite>().GetConnection();
await Task.Run (async () => {
token.ThrowIfCancellationRequested();
if (App.oSqliteCallsMainLH != null)
{
App.bRunningBackgroundTask = true;
oSQLite = App.oSqliteCallsMainLH;
await Task.Run(async () =>
{
await Task.Delay(1);
oSQLite.ftnSaveOnlineModeXMLFormat("Offline", 0);
oSQLite.SyncEmployeeTableData();
oSQLite.SaveOfflineAppCommentData();
oSQLite.SaveOfflineAdditionToFlowData();
await Task.Delay(500);
var msgStopSyncBackgroundingTask = new StopSyncBackgroundingTask();
MessagingCenter.Send(msgStopSyncBackgroundingTask, "StopSyncBackgroundingTask");
});
}
}, token);
}
}
Unfortunately Xamarin and Xamarin Forms don't give frameworks like Prism anywhere to tie into to handle IoC scenarios. There are a couple of ways you can handle this though.
First the Container is a public property on the PrismApplication in your background service you could do something like:
public class FooBackgroundService
{
private App _app => (App)Xamarin.Forms.Application.Current;
private void DoFoo()
{
var sqlite = _app.Container.Resolve<ISQLite>();
}
}
Another slightly more involved way would be to use the ServiceLocator pattern. You might have something like the following:
public static class Locator
{
private static Func<Type, object> _resolver;
public static T ResolveService<T>() =>
(T)_resolver?.Invoke(typeof(T));
public static void SetResolver(Func<Type, object> resolver) =>
_resolver = resolver;
}
In your app you would then simply set the resolver. Prism actually does something similar to this with the ViewModel locator, which then allows it to inject the correct instance of the NavigationService.
public class App : PrismApplication
{
protected override void OnInitialized()
{
SetServiceLocator();
NavigationService.NavigateAsync("MainPage");
}
protected override void RegisterTypes()
{
// RegisterTypes
}
private void SetServiceLocator()
{
Locator.SetResolver(type => Container.Resolve(type, true));
}
}
Finally your service would simply reference the Service Locator like:
public class BarBackgroundService
{
public void DoBar()
{
var sqlite = Locator.ResolveService<ISQLite>();
// do foo
}
}

Calling service/repository methods in ASP.Net Core middleware

ASP.Net Core noob here...I am using an ASP.Net Core WebAPI core project using DNX451 with EF 6.
I have a requirement to implement API Key auth in our service. To do this I have created middleware that gets information from the request and proceeds with authentication. It is SUPPOSED to go to the database, get the key to match, and then return and do the validation.
Here is the middleware implemented to look at the context and get the APIKey
AuthenticationHandler
public class AuthorizationHandler
{
private readonly RequestDelegate _next;
private IAuthenticationService _authenticationService;
public AuthorizationHandler(RequestDelegate next, IAuthenticationService authService)
{
_authenticationService = authService;
_next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
var apiKey = context.Request.Headers["Key"];
var location = context.Request.Headers["Host"];
var locationKey = _authenticationService.GetApiKey(location);
if (apiKey == locationKey)
await _next(context);
context.Response.StatusCode = 403;
context.Response.Headers.Add("WWW-Authenticate",
new[] { "Basic" });
}
catch (Exception ex)
{
context.Response.StatusCode = 500;
context.Response.Headers.Add("WWW-Authenticate",
new[] { "Basic" });
}
}
}
Here is the startup class with context and middleware registration
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
builder.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfiguration Configuration { get; set; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped(k => new DbContext(Configuration["Data:Context:ConnectionString"]));
// Add framework services.
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseIISPlatformHandler();
app.UseStaticFiles();
app.RegisterAuthorizationHeader();
app.RegisterAuthorization();
app.UseMvc();
}
// Entry point for the application.
public static void Main(string[] args) => WebApplication.Run<Startup>(args);
}
Here is Auth service
public interface IAuthenticationService
{
string GetApiKey(string location);
}
public class AuthenticationService: IAuthenticationService
{
private IApiKeyRepository _apiKeyRepository;
public AuthenticationService(IApiKeyRepository repo)
{
_apiKeyRepository= repo;
}
public string GetApiKey(string location)
{
return _apiKeyRepository.GetApiKeyByLocation(location);
}
}
The repo
public interface IApiRepository
{
string GetApiKeyByLocation(string location);
}
public class ApiRepository: IApiRepository
{
private DbContext _context;
public ApiRepository(DbContext context)
{
_context = context;
}
public string GetApiKeyByLocation(string location)
{
var apiRow = _context.ApiKeyStore.FirstOrDefault(a => a.Location == location);
return apiRow == null ? string.Empty : apiRow.APIKey;
}
}
When attempting this I get the following error:
The context cannot be used while the model is being created. This
exception may be thrown if the context is used inside the
OnModelCreating method or if the same context instance is accessed by
multiple threads concurrently. Note that instance members of DbContext
and related classes are not guaranteed to be thread safe.
Now, when I debug this every break point is hit twice. I believe I understand WHY this issue is occurring but have no idea how to fix it.
Can someone give me an idea, please? Any better solution ideas?
To use scoped dependencies in a middleware (which is necessarily a singleton by definition), the best approach is to flow it as a parameter of InvokeAsync instead of flowing it via the constructor:
public async Task Invoke(HttpContext context, IAuthenticationService authenticationService)
{
try
{
var apiKey = context.Request.Headers["Key"];
var location = context.Request.Headers["Host"];
var locationKey = authenticationService.GetApiKey(location);
if (apiKey == locationKey)
await _next(context);
context.Response.StatusCode = 403;
context.Response.Headers.Add("WWW-Authenticate",
new[] { "Basic" });
}
catch (Exception ex)
{
context.Response.StatusCode = 500;
context.Response.Headers.Add("WWW-Authenticate",
new[] { "Basic" });
}
}

Resources