net.pipe service host in WPF app - wpf

The contract:
[ServiceContract]
public interface IDaemonService {
[OperationContract]
void SendNotification(DaemonNotification notification);
}
The service:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class DaemonService : IDaemonService {
public DaemonService() {
}
public void SendNotification(DaemonNotification notification) {
App.NotificationWindow.Notify(notification);
}
}
In WPF app I do the following:
using (host = new ServiceHost(typeof (DaemonService), new[] {new Uri("net.pipe://localhost")})) {
host.AddServiceEndpoint(typeof (IDaemonService), new NetNamedPipeBinding(), "AkmDaemon");
host.Open();
}
This WPF app launches another app like this:
Task.Factory.StartNew(() => {
var tpm = new Process { StartInfo = { FileName = "TPM" } };
tpm.Start();
}
});
The app named TPM starts properly. Then I do attach to process in the debugging menu of Visual Studio and I see the client says that nobody is listening at the endpoint.
Here is the client:
[Export(typeof(DaemonClient))]
public class DaemonClient : IHandle<DaemonNotification> {
private readonly ChannelFactory<IDaemonService> channelFactory;
private readonly IDaemonService daemonServiceChannel;
public DaemonClient(IEventAggregator eventAggregator) {
EventAggregator = eventAggregator;
EventAggregator.Subscribe(this);
channelFactory = new ChannelFactory<IDaemonService>(new NetNamedPipeBinding(),
new EndpointAddress("net.pipe://localhost/AkmDaemon"));
daemonServiceChannel = channelFactory.CreateChannel();
}
public IEventAggregator EventAggregator { get; private set; }
public void Handle(DaemonNotification message) {
daemonServiceChannel.SendNotification(message); //Here I see that the endpoint //is not found
}
public void Close() {
channelFactory.Close();
}
}
EndpointNotFoundException There was no endpoint listening at "net.pipe://localhost/AkmDaemon"... blablabla

You are creating your ServiceHost in a using statement, so it is disposed right after the Open call. The Dispose call closes the ServiceHost.
using (host = new ServiceHost(...))
{
host.AddServiceEndpoint(...);
host.Open();
}
// ServiceHost.Dispose() called here
Just drop the using block.

Related

Navigation between Blazor Winforms pages

After a successfull experience with Blazor web server and Maui hybrid with Blazor, I'm now taking my first steps integrating my Blazor components in Winforms.
But I found no tutorials with samples of how to navigate between different pages/windows/forms.
Html links do not seem to work, and neither do NavigationManager:
Default page, app successfully loads it:
#inject NavigationManager NavigationManager
<div>
<button #onclick="OnClick">Go to page 2</button>
<a href="#" #onclick="#OnClick" #onclick:preventDefault>go to page 2</a>
<a href="page2" >go to page 2</a>
</div>
#code {
private void OnClick()
{
NavigationManager.NavigateTo("Page2");
}
}
Second page, no way to open it from previous one:
#page "/Page2"
<div>Wellcome to Page2</div>
Should I better navigate between Forms?
Update 1
I finally got it working having the component trigger an event on a form state class which instantiates a new form and shows it.
I guess I should better declare a singleton service with global appstate class in program.cs and hold all form instantiation from there, but I can't find the right way to declare it on a Blazor Winforms project.
My last version as follows, but appState is null when initializing Form1:
The singleton that communicates between Blazor compolnents and Winforms forms:
public class AppState
{
public event EventHandler<EventArgs>? GotoForm;
public void NavigateNext()
{
GotoForm?.Invoke(this,new EventArgs());
}
}
The component which feeds content to each form:
#inject AppState appState
<h3>Welcome to Form #FormNumber</h3>
<button #onclick="appState.NavigateNext">Goto page #AltFormNumber()</button>
#code {
[Parameter] public int FormNumber { get; set; }
int? AltFormNumber()=>FormNumber == null ? null : 3-FormNumber;
}
The default form:
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebView.WindowsForms;
using Microsoft.Extensions.DependencyInjection;
public partial class Form1 : Form
{
[Inject] AppState appState { get; }
public Form1()
{
InitializeComponent();
var services = new ServiceCollection();
services.AddWindowsFormsBlazorWebView();
//services.AddSingleton<FormState>();
blazorWebView1.HostPage = "wwwroot\\index.html";
blazorWebView1.Services = services.BuildServiceProvider();
//var appState = blazorWebView1.Services.GetRequiredService<AppState>();
appState.GotoForm += NavigateNext;
blazorWebView1.RootComponents.Add<Pages.FormComponent>("#app",
new Dictionary<string, object?> { { "FormNumber", 1 } });
}
private void NavigateNext(object? sender, EventArgs args)
{
var frm = new Form2();
frm.Show();
}
}
The alternative form:
public partial class Form2 : Form
{
[Inject] AppState appState { get; }
public Form2()
{
InitializeComponent();
var services = new ServiceCollection();
services.AddWindowsFormsBlazorWebView();
//services.AddSingleton<FormState>();
blazorWebView1.HostPage = "wwwroot\\index.html";
blazorWebView1.Services = services.BuildServiceProvider();
//var appState = blazorWebView1.Services.GetRequiredService<AppState>();
appState.GotoForm += NavigateNext;
blazorWebView1.RootComponents.Add<Pages.FormComponent>("#app",
new Dictionary<string, object?> { { "FormNumber", 2 } });
}
private void NavigateNext(object? sender, EventArgs args)
{
var frm = new Form1();
frm.Show();
}
}
The Program.cs:
internal static class Program
{
[STAThread]
static void Main()
{
ApplicationConfiguration.Initialize();
var host = CreateHostBuilder().Build();
ServiceProvider = host.Services;
Application.Run(new Form1());
}
public static IServiceProvider ServiceProvider { get; private set; }
static IHostBuilder CreateHostBuilder()
{
return Host.CreateDefaultBuilder()
.ConfigureServices((context, services) => {
services.AddSingleton<AppState>();
});
}
}

Problem loading view with MEF and ExportAttribute

I have a WPF app and I'm trying to use MEF to load viewmodels and view.
I can't successfully load Views.
The code:
public interface IContent
{
void OnNavigatedFrom( );
void OnNavigatedTo( );
}
public interface IContentMetadata
{
string ViewUri { get; }
}
[MetadataAttribute]
public class ExtensionMetadataAttribute : ExportAttribute
{
public string ViewUri { get; private set; }
public ExtensionMetadataAttribute(string uri) : base(typeof(IContentMetadata))
{
this.ViewUri = uri;
}
}
class ViewContentLoader
{
[ImportMany]
public IEnumerable<ExportFactory<IContent, IContentMetadata>> ViewExports
{
get;
set;
}
public object GetView(string uri)
{
// Get the factory for the View.
var viewMapping = ViewExports.FirstOrDefault(o =>
o.Metadata.ViewUri == uri);
if (viewMapping == null)
throw new InvalidOperationException(
String.Format("Unable to navigate to: {0}. " +
"Could not locate the View.",
uri));
var viewFactory = viewMapping.CreateExport();
var view = viewFactory.Value;
return viewFactory;
}
}
I supposed to use this code like this:
1)Decorate a User control
[Export(typeof(IContent))]
[ExtensionMetadata("CustomPause")]
[PartCreationPolicy(System.ComponentModel.Composition.CreationPolicy.NonShared)]
public partial class CustomPause : Page , IContent, IPartImportsSatisfiedNotification
{
public CustomPause()
{
InitializeComponent();
}
}
2) Compose the parts:
var cv = new CompositionContainer(aggregateCatalog);
var mef = new ViewContentLoader();
cv.ComposeParts(mef);
3) Load the view at runtime given a URI, for example:
private void CustomPause_Click(object sender, RoutedEventArgs e)
{
var vc = GlobalContainer.Instance.GetMefContainer() as ViewContentLoader;
MainWindow.MainFrame.Content = vc.GetView ("CustomPause");
}
Problem is this line in the GetView method fails:
var viewMapping = ViewExports.FirstOrDefault(o =>
o.Metadata.ViewUri == uri);
The query fails and so viewMapping is null but composition seems ok and I can see that ViewExports contains an object of type:
{System.ComponentModel.Composition.ExportFactory<EyesGuard.MEF.IContent, EyesGuard.MEF.IContentMetadata>[0]
I don't know where I'm wrong. Do you have a clue?
Gianpaolo
I had forgot this
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
in the MetadataAttribute

Windows Service can not open Windows Form

I am trying to create a Windows Service using TopShelf and within this service i want to launch a Windows Form.After i created the service and i debugged it calling ShowDialog the form does not show up:
Service
class SimpleServ {
private Task task;
private const string PATH = #"D:/out.txt";
private Logger logger;
private CancellationTokenSource src = new CancellationTokenSource();
public SimpleServ() {
logger = new Logger();
}
public void Start() {
logger.Log("Started");
this.task = Task.Run(async () => {
var fm = new Fm(logger);
while (true) {
fm.ShowDialog();
logger.Log("Just closed the dialog");
await Task.Delay(3000);
}
});
}
public void Stop() {
logger.Log("Stopped service");
}
}
Form
public partial class Fm : Form {
private Logger log;
public Fm(Logger log) {
this.log = log;
this.log.Log("From Form constructor");
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e) {
this.log.Log("Button clicked");
this.Close();
}
}
Main
class Program {
static void Main(string[] args) {
var exitCode = HostFactory.Run(x => {
x.Service<SimpleServ>(s => {
s.ConstructUsing(h => new SimpleServ());
s.WhenStarted(h => h.Start());
s.WhenStopped(h => h.Stop());
});
x.RunAsLocalSystem();
x.SetServiceName("SimpleService");
x.SetDisplayName("Simple Service");
x.SetDescription("Simple serv description");
});
int exitCodeValue = (int)Convert.ChangeType(exitCode, exitCode.GetTypeCode());
Environment.ExitCode = exitCodeValue;
}
}
I have attached myself to the service and after it reaches the line of ShowDialog nothing happens.
Update
I have also added a Logger to log all important events and so far , it seems the form opens but i can not see it:
Logger
public class Logger {
private string path;
public Logger(string logPath=Constants.PATH) {
this.path = logPath;
}
private object #lock = new object();
public void Log(string message) {
string formattedMessage = "Date:" + DateTime.Now.ToString() + "\tMessage:" + message;
File.AppendAllLines(this.path, new string[] { formattedMessage });
}
}
The output of the file is :
Date:6/12/2019 11:19:13 AM Message:Started
Date:6/12/2019 11:19:13 AM Message:From Form constructor
In a world where Session 0 Isolation -- an important security measure to prevent Shatter attacks -- is the law of the land, you should think very carefully about any design relying on service interaction.
A best practice is to restructure your solution to have:
A service that runs in the background, independently of the
user
A conventional GUI application that interacts with the service and
can be run by any user

Update ObservableCollection from wcf service callback function

I'm currently working on chat client-server application based on wcf service, which wich operate in duplex mode. I have two WPF applications: Server, where service itself is hosted and Client.
Here is service contract:
[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(ISendToClient))]
interface IChatService
{
[OperationContract]
ChatUser ClientConnect(ChatUser userName);
[OperationContract]
void RemoveUser(ChatUser user);
[OperationContract(IsOneWay = true)]
void sendNewMessage(ChatMessage message);
}
interface ISendToClient
{
[OperationContract]
void newUserConnected(ChatUser user);
[OperationContract(IsOneWay = true)]
void deliverNewMessage(ChatMessage message);
}
and its implementation:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple, UseSynchronizationContext = false)]
public class ChatService : IChatService, INotifyPropertyChanged
{
public ChatService()
{
conectedUsers = new ObservableCollection<ChatUser>();
}
ISendToClient callback;
private ObservableCollection<ChatUser> _conectedUsers;
public ObservableCollection<ChatUser> conectedUsers
{
get { return _conectedUsers; }
set
{
if (_conectedUsers == value) return;
_conectedUsers = value;
OnPropertyChanged();
}
}
public ChatUser ClientConnect(ChatUser user)
{
//do smth
}
public List<ChatMessage> GetNewMessages(ChatUser user)
{
//do smth
}
public void sendNewMessage(ChatMessage newMessage)
{
callback = OperationContext.Current.GetCallbackChannel<ISendToClient>();
callback.deliverNewMessage(newMessage);
}
#region PropertyChangedMembers
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName]String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
View model on clinet side include two additional methods
public void newUserConnected(ChatUser user)
{
Users.Add(user);
}
public void deliverNewMessage(ChatMessage message)
{
messHistory.Add(message);//adding message to ObservaleCollection
//this Collection is binded to listbox, but not displays
}
Server use this methods to inform client about new messages or new users logged in.
Service is correctly configured, client can connect to server and send messages, server sends them to all users via calling deliverNewMessage() method on client side, this messages successfully added to ObservaleCollection, which is binded to listBox:
<ListBox Grid.Row="0" Margin="3" Name="lst"
ItemsSource="{Binding messHistory}" />
BUT! No messages displays in listBox. I tried to do like this:
Application.Current.Dispatcher.Invoke(new Action(() =>
{
messHistory.Add(message);
}));
with no effect.
I guess, one little detail is missed, but have no clue.
I'd appreciate any ideas how to fix this issue.

Populate my ListView with item source from class

I have this Singleton that hold my ObservableCollection<MyData> as a memeber:
public sealed class Singleton
{
private static volatile Singleton instance;
private static object syncRoot = new Object();
public ObservableCollection<MyData> Files { get; private set; }
private Singleton()
{
Files = new ObservableCollection<MyData>();
}
public static Singleton Instance
{
get
{
if (instance == null)
{
lock (syncRoot)
{
if (instance == null)
instance = new Singleton();
}
}
return instance;
}
}
}
Declaration from main form class:
ObservableCollection<MyData> Files;
And here after the constructor:
Files= Singleton.Instance.Files;
XAML:
<ListView ItemsSource="{Binding Files}" />
Now when the user choose files i want to check each file:
private static void Check(IEnumerable<string> files)
{
CancellationTokenSource tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
Task task = Task.Factory.StartNew(() =>
{
try
{
Parallel.ForEach(files,
new ParallelOptions
{
MaxDegreeOfParallelism = 1
},
file =>
{
ProcessFile(file);
});
}
catch (Exception)
{ }
}, tokenSource.Token,
TaskCreationOptions.None,
TaskScheduler.Default).ContinueWith
(t =>
{
}
, TaskScheduler.FromCurrentSynchronizationContext()
);
}
And:
private static void ProcessFile(string file)
{
// Lets assume that i want to add this file into my `ListView`
MyData data = new .....
Singleton.Instance.Files.Add(data);
}
So after this point when i am add files into my list nothing happenning.
Using your code above i was able to reproduce the issue you describe.
The problem is that WPF cannot bind to fields, see this question for more details. All you need to do is to change the ObservableCollection<MyData> in the code behind of your main form to a property instead of a field.
public partial class MainWindow : Window
{
public ObservableCollection<MyData> Files { get; private set; }

Resources