Callback method on the SignalR Wpf client side - wpf

I have 2 .NET solutions. One is asp.net mvc web application which is signalr server. The other is SignalR WPF client that consumes the collection of custom class objects from the web signalr server. I run the signalr MVC web server with IIS. The signalr WPF client application is run in another VS instance.
The SignalR web server has a Hub method (named "MessageLogHub", please see the below codes) that uses SqlDependency to broadcast collection of custom class objects to all signalr clients if there is a change (e.g. manually inserting data into the SQL Server database table) in a target database table. The collection of custom class objects are all of the records of a SQL Server database server.
From the signalr WPF client application side, I let the client register the SignalR server method as follows:
Proxy.On<List<MessageLog>>("BroadcastMessage", OnBroadcastMessage);
And the call back method named "OnBroadcastMessage" looks like:
private void OnBroadcastMessage(List<MessageLog> logs)
{
// problem: this callback method is hits once there is a put from signalr server. but the "logs" parameter's value is never changed !!!
Dispatcher.Invoke(() =>
{
textblockTime.Text = DateTime.Now.ToLongTimeString() + " - logs.Count=" + logs.Count();
datagridLogs.ItemsSource = null;
datagridLogs.ItemsSource = logs;
});
}
So when there is a change of database, the SignalR web server will calls the method "BroadcastMessage" on all connected clients (please see the below Signalr Hub server codes) and the callback method (on the SignalR WPF client codes) "OnBroadcastMessage" will be fired automatically on clients (WPF or web app clients). In VS, I debugs the call back method named "OnBroadcastMessage" and I see it is hit when there is a change in the database that triggers the SignalR server codes to broadcast to all connected clients.
The problem is in the signalr Wpf client callback method of "OnBroadcastMessage" (please see the above code of callback method of "OnBroadcastMessage"), the value of the "logs" parameter is not changed anytime the debug break-point is hit in that callback method. If a row is inserted in the database table, then the returned value of "logs" parameter is supposed to be changed and the new "logs" parameter is put to the signalr client side. Please help me.
I have another signalr asp.net MVC web project that is signalr client and it is embedded in the SignalR MVC web server solution. It works fine with the signalr server and I see the changes in real time way with that signalR web client. But the signalr WPF client application is not working as mentioned above.
Here are the codes (the xaml code behind file) for signalr WPF client application:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using MessageWatchWpfClient.Models;
using Microsoft.AspNet.SignalR.Client;
namespace MessageWatchWpfClient
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private string hostUrl = "http://localhost/MessageWatch/signalr";
public IHubProxy Proxy { get; set; }
public HubConnection Connection { get; set; }
public MainWindow()
{
InitializeComponent();
}
private async void Window_Loaded(object sender, RoutedEventArgs e)
{
try
{
Connection = new HubConnection(hostUrl);
Proxy = Connection.CreateHubProxy("MessageLogHub");
// 1) Register so that Server automatically calls Client when database is changed
Proxy.On<List<MessageLog>>("BroadcastMessage", OnBroadcastMessage);
await Connection.Start();
// 2) Client calls Server on the Wpf application loading and the following is run one time only
await Proxy.Invoke<List<MessageLog>>("GetAllMessagesLog")
.ContinueWith((t) =>
{
Dispatcher.Invoke(() =>
{
textblockTime.Text = DateTime.Now.ToLongTimeString() + " - logs.Count=" + t.Result.Count();
datagridLogs.ItemsSource = t.Result;
});
}
);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void OnBroadcastMessage(List<MessageLog> logs)
{
// problem: this callback method is hits once there is a put from signalr server. but the "logs" parameter's value is never changed !!!
Dispatcher.Invoke(() =>
{
textblockTime.Text = DateTime.Now.ToLongTimeString() + " - logs.Count=" + logs.Count();
datagridLogs.ItemsSource = null;
datagridLogs.ItemsSource = logs;
});
}
}
}
The following codes are for SignalR Hub class on the server side application that is Asp.net MVC web and is hosted on IIS:
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Globalization;
using MessageWatch.Models;
using Microsoft.AspNet.SignalR;
using SD = SignalR.Dal;
using MessageWatch.Utils;
namespace MessageWatch.Hubs
{
public class MessageLogHub : Hub
{
NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();
public IEnumerable<MessageLog> GetAllMessagesLog()
{
var messageList = new List<MessageLog>();
try
{
var conStr = ConfigurationManager.ConnectionStrings["SignalRDatabase"].ToString();
var connection = new SqlConnection(conStr);
const string query = "SELECT Message,EventID,LL.Name as LogLevelID,OC.Name as OperationCodeID,ML.ServerName,ML.ComponentName,ML.SubComponentName FROM [dbo].[MessageLog] ML inner join [dbo].[LogLevel] LL on ML.LogLevelID = LL.ID inner join [dbo].[OperationCode] OC on ML.OperationCodeID = OC.ID order by ML.ID desc";
SqlDependency.Start(conStr); // to avoid error "When using SqlDependency without providing an options value, SqlDependency.Start() must be called prior to execution of a command added to the SqlDependency instance."
var command = new SqlCommand(query, connection);
var dependency = new SqlDependency(command);
//If Something will change in database and it will call dependency_OnChange method.
dependency.OnChange += dependency_OnChange;
connection.Open();
var da = new SqlDataAdapter(command);
var dt = new DataTable();
da.Fill(dt);
for (var i = 0; i < dt.Rows.Count; i++)
{
var ml = new MessageLog
{
Name = dt.Rows[i]["Message"].ToString(),
EventID = Convert.ToInt32(dt.Rows[i]["EventID"].ToString()),
LogLevelName = dt.Rows[i]["LogLevelID"].ToString(),
OperationCodeName = dt.Rows[i]["OperationCodeID"].ToString(),
ServerName = dt.Rows[i]["ServerName"].ToString(),
ComponentName = dt.Rows[i]["ComponentName"].ToString(),
SubComponentName = dt.Rows[i]["SubComponentName"].ToString()
};
messageList.Add(ml);
}
}
catch (Exception ex)
{
_logger.Error(CultureInfo.CurrentCulture, "{0}", ex.Message);
}
return messageList;
}
private void dependency_OnChange(object sender, SqlNotificationEventArgs e)
{
if (e.Type == SqlNotificationType.Change)
{
SendNotifications();
}
}
private void SendNotifications()
{
var messageList = GetAllMessagesLog();
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<MessageLogHub>();
context.Clients.All.broadcastMessage(messageList);//Will update all the clients with message log.
}
public void CreateData()
{
try
{
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<MessageLogHub>();
var dbContext = new SD.SignalREntities();
var repository = new SD.GenericRepository(dbContext);
var ml = new DataRandomer().Create();
repository.Add(ml);
dbContext.SaveChanges();
context.Clients.All.createData();
}
catch (Exception ex)
{
_logger.Error(CultureInfo.CurrentCulture, "{0}", ex.Message);
}
}
}
}

Related

How connect to an existing Azure SQL database with ASP.NET CORE MVC using Entity Framework Core with migrations?

I have an app using Azure App Services with SQL server and SQL database that are connect to my web app on asp MVC. I've used Distributed Sql Server Cache as a table on my database and so far everything is working well and connected to each other.
Now I want to do two things:
Add entity framework to my app (I already have the database and
connection string)
Run migration – after I've published my app (If I've added for a
example new line or new
table, now I have new version)
I'm not sure how to do those things , I've looked up on many guides and couldn't find an answer. I found a post similar to mine – but using azure functions - here
. I would appreciate it if someone can help me with the steps that I need to follow (like they did in that post) to get entity framework and the migration.
Here is my code:
Program.cs-
using Microsoft.Extensions.Azure;
using Azure.Identity;
var builder = WebApplication.CreateBuilder(args);
if(!builder.Environment.IsDevelopment())
builder.Configuration.AddAzureKeyVault(new Uri(Environment.GetEnvironmentVariable("VaultUri")), new DefaultAzureCredential());
builder.Services.AddControllersWithViews();
builder.Services.AddAzureClients(clientBuilder =>
{
clientBuilder.AddBlobServiceClient(builder.Configuration["storage:blob"], preferMsi: true);
clientBuilder.AddQueueServiceClient(builder.Configuration["storage:queue"], preferMsi: true);
});
builder.Services.AddDistributedSqlServerCache(options =>
{
options.ConnectionString = builder.Configuration.GetConnectionString("db");
options.SchemaName = "dbo";
options.TableName = "_Cache";
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
Home Controller:
namespace WebAppAzure.Controllers
{
public class HomeController : Controller
{
private readonly BlobServiceClient storage;
private readonly ILogger<HomeController> logger;
private readonly IDistributedCache cache;
public HomeController(BlobServiceClient storage, ILogger<HomeController> logger,
IDistributedCache cache)
{
this.storage = storage;
this.logger = logger;
this.cache = cache;
}
public IActionResult Index()
{
var containerClient = storage.GetBlobContainerClient("public");
var blob = containerClient.GetBlobClient("image.jpeg");
var model = blob.Uri.ToString();
return View(model: model);
}
public IActionResult Privacy()
{
var stringModel = DateTime.Now.ToString();
cache.SetString("name", stringModel);
return View(model: $"SET: {stringModel}");
}
public IActionResult About()
{
var stringModel = cache.GetString("name");
return View(model: $"GET: {stringModel}");
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
}
Add entity framework to my app (I already have the database and connection string)
Use below code for add Entity framework and upload to azure app service and run migration command to migrate database.
DBcontext file in project.
using Microsoft.EntityFrameworkCore;
using WebApplication_72783922.Entity;
namespace WebApplication_72783922
{
public class DbConnectionEntity : DbContext
{
public DbConnectionEntity()
{
}
//string connectionString = Environment.GetEnvironmentVariable("ConnectionStrings:dbcon").ToString();
public DbConnectionEntity(DbContextOptions<DbConnectionEntity> options)
: base(options)
{
}
public virtual DbSet<Users> users { get; set; }
public virtual DbSet<department> Departments { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer("Server=xxxx;Initial Catalog=database;Persist Security Info=False;User ID=adminserver72783922;Password=xxxx;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;");
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
}
Program.cs File code.
using Microsoft.Extensions.Azure;
using Azure.Identity;
using Microsoft.Extensions.Configuration;
var builder = WebApplication.CreateBuilder(args);
if (!builder.Environment.IsDevelopment())
// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddDistributedSqlServerCache(options =>
{
options.ConnectionString = "Server=xxxx;Initial Catalog=database;Persist Security Info=False;User ID=adminserver72783922;Password=xxxx;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;";
options.SchemaName = "dbo";
options.TableName = "_Cache";
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
Run migration – after I’ve published my app
Enable Migration using this command on Package Manager Console enable-migrations
Then add-migration InitialCreate
Then create migrationadd-migration test-v1
update database update-database -verbose

PostgreSQL and Blazor .Net Core 3.1

Are there any resources on how to use the current version of Blazor (3.1) and PostgreSQL?
I've tried writing the simplest code, just to see whether it connects to the database but I get this error message: System.Net.Dns:GetHostByName is not supported on this platform
a button click would activate this code:
async void connection()
{
var connString = "Host=Server1;Username=postgres;Password=pass;Database=BlazorData";
try
{
await using var conn = new NpgsqlConnection(connString);
await conn.OpenAsync();
errcheck = "success";
}
catch (Exception ex)
{
errcheck = ex.Message;
}
}
I explain how I use it with entity framework. It might help you.
in startup.cs, ConfigureServices method have this
services.AddEntityFrameworkNpgsql().AddDbContext<ApplicationDbContext>(options =>
options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection")));
You need two packages to be installed through nuget
Npgsql.EntityFrameworkCore.PostgreSQL
Npgsql.EntityFrameworkCore.PostgreSQL.Design
in appsetting.json make sure you have setup connection string correctly, below one is mine. Host can be localhost if database is in the same machine as the database
"DefaultConnection": "Host=192.168.16.240;Port=5432;Username=postgres;Password=mypassword;Database=mydatabase;"
That's basically it.
then define a application db context with your tables
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions options) : base(options)
{
}
public DbSet<Room> Rooms { get; set; }
public DbSet<Meal> Meals { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
in package mangaer console
add-migration initial
update-database
you should see the tables created in PgAdmin.
and use your dbsets like usual c# lists. and remember to inject applicationdbcontext in the controllers you need it.
The error says you need to resolve the hostname by yourself. Either pass an IP address or use Dns.GetHostEntry
using System.Linq;
using System.Net;
using System.Net.Sockets;
...
async void connection()
{
var host = Dns.GetHostEntry("Server1");
var firstIpV4Address = host.AddressList.First(a => a.AddressFamily == AddressFamily.InterNetwork);
var connString = $"Host={firstIpV4Address};Username=postgres;Password=pass;Database=BlazorData";
try
{
await using var conn = new NpgsqlConnection(connString);
await conn.OpenAsync();
errcheck = "success";
}
catch (Exception ex)
{
errcheck = ex.Message;
}
}

Get WinForm "Cross-thread operation not valid" error for WCF host application ONLY while running in Visual Studio

I have a WCF service that I am self-hosting in a WinForm application. In the application I have a ListBox control to display diagnostic information. I pass a reference to the ListBox control to the WCF service. In that service there is a callback that uses it.
For some reason if I run the application outside of Visual Studio, I am able to use the ListBox to display information in the main hosting application, all methods in the WCF service, and the single callback in the WCF service. But, running in Visual Studio, the same application fails when trying to write info while in the callback.
Am I doing something that I fundamentally cannot do and getting away with it OR is there something incorrect going on while running in Visual Studio?
Here is part of my WinForm host
public WCFServiceHostGUI()
{
InitializeComponent();
// OutputBox is a ListBox control reference. Pass to WCF service to display diagnostics
WCFCallbacks.UsbBrokerService.initialize(OutputBox);
startService();
}
private void startService()
{
usbBrokerServiceHost = new ServiceHost(typeof(WCFCallbacks.UsbBrokerService));
ServiceDescription serviceDesciption = usbBrokerServiceHost.Description;
foreach (ServiceEndpoint endpoint in serviceDesciption.Endpoints)
{
OutputBox.Items.Add("Endpoint - address: " + endpoint.Address);
OutputBox.Items.Add(" - binding name: " + endpoint.Binding.Name);
OutputBox.Items.Add(" - contract name: " + endpoint.Contract.Name);
}
usbBrokerServiceHost.Open();
/*
ChannelFactory<WCFCallbacks.IUsbBroker> channelFactory = new ChannelFactory<WCFCallbacks.IUsbBroker>(BINDING, ADDRESS);
WCFCallbacks.IUsbBroker clientProxy = channelFactory.CreateChannel();
clientProxy.initialize(OutputBox);
*/
}
In the constructor a reference to the ListBox is passed to the WCF service. One can see the usual creation of the ServiceHost in the startService() method.
Here is the WCF service that gets the ListBox reference
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class UsbBrokerService : IUsbBroker
{
private static readonly List<IUsbBrokerCallback> subscribers = new List<IUsbBrokerCallback>();
private static ListBox OutputBox = null;
public UsbBrokerService()
{
Console.WriteLine("Service started");
UsbDevicePluginIndicator.DeviceNotify += new UsbDevicePluginIndicator.DeviceNotifyDelegate(UsbDevicePluginIndicator_DeviceNotify);
UsbDevicePluginIndicator.Start();
}
public static void initialize(ListBox outputBox)
{
OutputBox = outputBox;
}
public void AddMessage(string message)
{
// Use the following to see which endpoint is accessed
OperationContext oc = OperationContext.Current;
if (oc != null)
{
Console.WriteLine("A request was made on endpoint " + oc.Channel.LocalAddress.ToString());
if (OutputBox != null)
{
OutputBox.Items.Add("A request was made on endpoint " + oc.Channel.LocalAddress.ToString());
}
}
}
public bool RegisterDevices(UsbDevice[] usbDevices)
{
try
{
IUsbBrokerCallback callback = OperationContext.Current.GetCallbackChannel<IUsbBrokerCallback>();
if (!subscribers.Contains(callback))
{
subscribers.Add(callback);
}
return true;
}
catch
{
return false;
}
}
public bool UnRegisterDevices(UsbDevice[] usbDevices)
{
try
{
IUsbBrokerCallback callback = OperationContext.Current.GetCallbackChannel<IUsbBrokerCallback>();
if (subscribers.Contains(callback))
{
subscribers.Remove(callback);
}
return true;
}
catch
{
return false;
}
}
private void UsbDevicePluginIndicator_DeviceNotify(System.Windows.Forms.Message msg)
{
BroadcastHeader lBroadcastHeader;
Console.WriteLine("Wcf WM_DEVICECHANGE signaled");
if (OutputBox != null)
{
OutputBox.Items.Add("Wcf WM_DEVICECHANGE signaled");
}
}
}
In the callback routine UsbDevicePluginIndicator_DeviceNotify the attempt to write to OutputBox fails with "Cross-thread operation not valid: Control 'OutputBox' accessed from a thread other than the thread it was created on." but ONLY while running in Visual Studio.
So am I doing something fundamentally wrong?

Ria Service And OOB : check if service is reachable, MEF not importing my views

Hi currently I am trying to check if the Ria Service is available for our OOB application.
public static void IsServiceReachable(Action onServiceAvailable, Action onServiceUnavailable)
{
try {
DomainContext context = new DomainContext();
InvokeOperation<bool> invokeOperation = context.IsAlive();
invokeOperation.Completed += (s, arg) => onServiceAvailable();
}
catch (Exception) {
onServiceUnavailable();
}
}
When the exception happen my App hangs, and is now just a white screen. Am I doing it correctly?
I am also using MEF in the app, I am lazy importing my views, sadly when Ria Service is not reachable, MEF doesnt import my views :( I am calling CompositionInitializer.SatisfyImports(this).
[ImportMany(AllowRecomposition = true)]
public Lazy<BaseUserControl, IViewMetadata>[] Views { get; set; }
Have you tried checking if an error has occured in the OnServiceAvailable callback:
void OnServiceAvailable(object sender, EventArgs e)
{
InvokeOperation op = sender as InvokeOperation;
if (op.HasError) {
Exception exception = op.Error;
...
} else {
...
}
}
You should probably rename OnServiceAvailable something like OnOperationComplete.
You have to handle the errors in the callback - including the 'ServiceNotAvailable' error. Remember this is an asyncronous call - the client does does not wait for the server response before it continues.

Determining error in the channel using Silverlight proxy client for WCF service

I'm creating Silverlight proxy client for WCF service using async pattern:
public class ProductService : ClientBase<IProductService> {
public event EventHandler<DataEventArgs<Product>> GetProductCompleted;
public void GetProductAsync(string productName) {
IAsyncResult asyncResult = Channel.BeginGetProduct(productName, GetProductCallback, null);
}
private void GetProductCallback(IAsyncResult asyncResult) {
Product product = Channel.EndGetProduct(asyncResult);
if (GetProductCompleted != null)
GetProductCompleted(this, new DataEventArgs<Product>(product));
}
}
How can I get know if an error occurred in the channel during performing request to the service?
The EndGetProduct ought to throw the error when called, so place a try..catch around it.

Resources