C# Windows Form application.
I have an Hub and a class. Both should reference each other.
This is because:
from the hub I need to call the class' methods
from the class I need to retrieve my Hub
Right now I'm able to do the first point using Autofac:
using Autofac;
using Autofac.Integration.SignalR;
using Microsoft.AspNet.SignalR;
using Microsoft.Owin.Cors;
using Microsoft.Owin.Hosting;
using Owin;
using MyProject.Classes;
using System;
using System.Reflection;
using System.Windows.Forms;
namespace MyProject
{
static class Program
{
static IDisposable webApp;
[STAThread]
static void Main()
{
string url = "http://localhost:8080";
webApp = WebApp.Start(url);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Engine());
}
}
class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseCors(CorsOptions.AllowAll);
var builder = new ContainerBuilder();
var config = new HubConfiguration();
builder.RegisterHubs(Assembly.GetExecutingAssembly()).PropertiesAutowired();
builder.RegisterType<Erp>().PropertiesAutowired().InstancePerLifetimeScope();
var container = builder.Build();
config.Resolver = new AutofacDependencyResolver(container);
app.UseAutofacMiddleware(container);
app.MapSignalR(config);
}
}
}
here the Hub:
using Microsoft.AspNet.SignalR;
using MyProject.Classes;
using System;
using System.Threading.Tasks;
namespace MyProject.Hubs
{
public class LiveHub : Hub
{
private readonly Erp _erp;
public LiveHub(Erp erp)
{
_erp = erp;
}
public override Task OnConnected()
{
_erp.someMethod();
return base.OnConnected();
}
}
}
and here Erp.cs:
public class Erp
{
public Erp()
{
}
public void SomeMethod()
{
// do something
}
public void SomeOtherMethod()
{
// usually I do:
var hub = GlobalHost.ConnectionManager.GetHubContext<LiveHub>();
hub.Clients.All.foo();
}
}
but here I read:
A common error in OWIN integration is use of the GlobalHost. In OWIN you create the configuration from scratch. You should not reference GlobalHost anywhere when using the OWIN integration. Microsoft has documentation about this and other IoC integration concerns here.
If I cannot use the "old" method, how should I retrieve my Hub?
I tried to add another DI in Erp for LiveHub but it doesn't work. In my form I create an instance of Erp:
public partial class Engine : Form
{
private Erp _erp = new Erp();
public Engine()
{
InitializeComponent();
}
}
if I add that DI the declaration will be impossible because I need to define the LiveHub in constructor, that requires itself the Erp parameter...
What am I not seeing?
You can decouple the Hub from the object (Erp in your case) by emitting events.
namespace MyProject.Hubs
{
public class LiveHub : Hub
{
public event Action SomethingHappened;
public override Task OnConnected()
{
SomethingHappened?.Invoke();
return base.OnConnected();
}
}
}
Now you can connect the Erp without the Hub having to know it. You will have to subscribe to the event somewhere else in your code. But the circular reference is broken.
To decouple Engine from Form you could do something like this:
public partial class EngineForm : Form
{
public EngineForm()
{
InitializeComponent();
}
}
public class Engine
{
public Engine(EngineForm form, Erp erp)
{
this.form = form;
this.erp = erp;
}
// Here is where you'll write some code to coordinate
// communication between the Erp and the EngineForm.
//
// The main advantage is that you can inject the Erp
// and have it preconfigured.
}
Related
I have two projects: Engine, Client. During the execution of the Engine1.cs class I would like to open a windows form from the ClientAction class by passing it objects from the Engine.cs. Client references the Engine project.
namespace Engine {
public Class Engine1 {
public Engine1() {
}
//what I would do if I could reference the Client project
ClientAction.OpenForm(obj1, obj2);
}
}
using Engine;
namespace Client {
public Class ClientAction {
public ClientAction() { }
public OpenForm(object obj1, object obj2) {
Form1.Open(obj1, obj2){
...
}
}
}
}
You can do this using reflection and class System.Activator ("mscorlib.dll" assembly). Define your classes as below:
In project Engine
using System;
using System.Reflection;
namespace Engine
{
public class Engine1
{
public Engine1()
{
var clientAction = Activator.CreateInstance(
Type.GetType("Client.ClientAction, Client"), new object[] { });
MethodInfo methodInfo = clientAction.GetType().GetMethod("OpenForm");
var arg1 = new object();
var arg2 = new object();
methodInfo.Invoke(clientAction, new object[] { arg1, arg2 });
}
}
}
In project Client,
class ClientAction
namespace Client
{
public class ClientAction
{
public ClientAction() { }
public void OpenForm(object obj1, object obj2)
{
new Form1()
{
Text = "OpenForm(object obj1, object obj2)"
}.Show();
}
}
}
Now in project Client, you can test it in class Program like this:
using System;
using System.Windows.Forms;
namespace Client
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var engine1 = new Engine.Engine1();
Application.Run(new Form1());
}
}
}
Another option is using delegates. You must create a static event in project Engine and in project Client subscribe a handler to this event which will invoke required method.
In project Engine
the delegate:
namespace Engine
{
public class OpenFormEventArgs
{
public object Obj1 { get; set; }
public object Obj2 { get; set; }
}
public delegate void OpenFormEventHandler(object sender, OpenFormEventArgs e);
}
class Engine1
namespace Engine
{
public class Engine1
{
public static event OpenFormEventHandler OpenForm;
public Engine1()
{
var obj1 = new object();
var obj2 = new object();
OpenFormEventArgs e = new OpenFormEventArgs() { Obj1 = obj1, Obj2 = obj2 };
OpenForm?.Invoke(this, e);
}
}
}
In project Client,
class ClientAction
namespace Client
{
public class ClientAction
{
public ClientAction()
{
Engine.Engine1.OpenForm += Engine1_OpenForm;
}
private void Engine1_OpenForm(object sender, Engine.OpenFormEventArgs e)
{
OpenForm(e.Obj1, e.Obj2);
}
public void OpenForm(object obj1, object obj2)
{
new Form1()
{
Text = "OpenForm(object obj1, object obj2)"
}.Show();
}
}
}
Now in project Client, you can test it in class Program like this:
using System;
using System.Windows.Forms;
namespace Client
{
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var clientAction = new ClientAction();
var engine1 = new Engine.Engine1();
Application.Run(new Form1());
}
}
}
You can define an interface in the Engine project, with methods to call various actions. When client creates the engine, make the engine require an instance of that interface. The client project implementing the interface will then handle the events i.e. create forms.
Another approach is expose C# events from your Engine1 class. Subscribe to these events in Client, create your forms.
In both approaches, events raised by Engine class will be handled in the referencing Client project.
Interfaces are better when the projects are tightly coupled, or when you want to return values to the engine (C# events support multiple subscribers, and subscribing is optional).
Create interfaces for the types you want to pass to the client. Locate these interfaces in the client project.
This way, you only need one reference from Engine to Client. The implementations of the interfaces are linving in the Engine project.
You can call the client from Engine and pass the objects needed by the client.
This result in multiple improvements. You can implement different engines. The client doesn't need to know, how the engine works. And it's generally a cleaner and more flexible design.
Note: The classes and interfaces should all be in there own file.
Engine
using Client;
namespace Engine
{
public class Engine1
{
public Engine1()
{
var myObject = new MyObject
{
SomeProperty = null
};
ClientAction.OpenForm(myObject);
}
}
public class MyObject : IMyObject
{
public object SomeProperty { get; set; }
public void DoSomething()
{
// do something
}
}
}
Client
namespace Client
{
public class ClientAction
{
public ClientAction() { }
public OpenForm(IMyObject myObject)
{
myObject.DoSomething();
Form1.Open(myObject.SomeProperty);
}
}
public interface IMyObject
{
object SomeProperty { get; set; }
void DoSomething();
}
}
No, this is not possible and it suggests poor design choices on the developer's part.
Code that is shared between two projects/applications or even libraries should be in their own, separate project. This is usually called Common.
In essence you'd have the following structure:
...\MySolution\
Project1\
...
Project2\
...
Common\
CommonClass.cs
Within this structure, Project1 & Project2 can reference Common and use the same code base.
Another thing you should look out for, are potential circular dependencies, which can occur if (for example) a class in Project1 relies on a class in Common, and a class in Common relies on a class in Project1.
I am actually trying to archive global variables in Xamarin where any page can consume it. After a lot of research, looks like the best way to archive such thing is using the Singleton design pattern. I am facing difficulty to implement this. take a look...
global.cs
using System;
namespace xamarin_forms
{
sealed class Global
{
public string test { get; set; }
private static Global _instance = null;
private Global()
{
}
static internal Global Instance()
{
if (_instance == null)
{
_instance = new Global();
}
return _instance;
}
}
}
App.xaml.cs
using Xamarin.Forms;
namespace xamarin_forms
{
public partial class App : Application
{
Global global = Global.Instance();
public App()
{
InitializeComponent();
MainPage = new PageWelcome();
global.test = "123";
}
protected override void OnStart()
{
}
protected override void OnSleep()
{
// Handle when your app sleeps
}
protected override void OnResume()
{
// Handle when your app resumes
}
}
}
Ok, so far, I just created my singleton class with a simple test property. I set this to 123 when I initialize my application.
Now, on another page, welcome page...I'd like to read the value that I set previously on the initialization...
PageWelcome.xaml.cs
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace xamarin_forms
{
public partial class PageWelcome : ContentPage
{
public PageWelcome()
{
InitializeComponent();
Global global = Global.Instance();
DisplayAlert("Alert", global.test, "OK");
}
}
}
Actually this is not working. It's returns me a null. So, how to use this correctly ? Thanks !
In your App's constructor, you first create an instance of PageWelcome. This instance reads the test property of your Global singleton and displays its contents in an alert. At this point, no value has been assigned to that property as far as I can see.
It is only after the PageWelcome constructor finishes that you actually assign a value to the test property of your singleton. Change your App constructor to
public App()
{
InitializeComponent();
global.test = "123";
MainPage = new PageWelcome();
}
and it should work as expected.
You don't need a Singleton.
Just create a static class with your variables static and you would be able to use them on any Page, like you want global variables.
// 1. Create static class Global with string _Test
public static class Global
{
public static void Init()
{
// your init class
}
private static string _Test { get; set; }
public static string Test
{
get => return _Test;
set => _Test = value;
}
}
// 2. Init Global in your App.cs
public App()
{
Global.Init();
}
// 3. Then use them on any page
public PageWelcome()
{
Global.Test = "123";
}
I've implemented a very small plugin system based on C# with MEF. The problem is, none of my plugins are instanced. In the Aggregate-Catalog I can see my plugin listed. But, after I'll compose these parts, there isn't my plugin in the plugin list, what I'm doing wrong?
Here's a snippet of my code:
Plugin-Loader:
[ImportMany(typeof(IFetchService))]
private IFetchService[] _pluginList;
private AggregateCatalog _pluginCatalog;
private const string pluginPathKey = "PluginPath";
...
public PluginManager(ApplicationContext context)
{
var dirCatalog = new DirectoryCatalog(ConfigurationManager.AppSettings[pluginPathKey]);
//Here's my plugin listed...
_pluginCatalog = new AggregateCatalog(dirCatalog);
var compositionContainer = new CompositionContainer(_pluginCatalog);
compositionContainer.ComposeParts(this);
}
...
And here, the plugin itself:
[Export(typeof(IFetchService))]
public class MySamplePlugin : IFetchService
{
public MySamplePlugin()
{
Console.WriteLine("Plugin entered");
}
...
}
Tested working sample.
Compile class library with code inside PluginNameSpace namespace and place it to the 'Test' folder which will be inside console app exe folder.
using System;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.IO;
using System.Reflection;
using ConsoleApplication;
namespace ConsoleApplication
{
public interface IFetchService
{
void Write();
}
class PluginManager
{
[ImportMany(typeof(IFetchService))]
public IFetchService[] PluginList;
public PluginManager()
{
var dirCatalog = new DirectoryCatalog(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + "\\Test");
var pluginCatalog = new AggregateCatalog(dirCatalog);
var compositionContainer = new CompositionContainer(pluginCatalog);
compositionContainer.ComposeParts(this);
}
}
class Program
{
static void Main(string[] args)
{
var pluginManager = new PluginManager();
foreach (var fetchService in pluginManager.PluginList)
{
fetchService.Write();
}
Console.ReadKey();
}
}
}
// Separate class library
namespace PluginNameSpace
{
[Export(typeof(IFetchService))]
public class MySamplePlugin : IFetchService
{
public void Write()
{
Console.WriteLine("Plugin entered");
}
}
}
I am looking to get platform specific location details using Xamarin's dependency injection but running into issues. More than likely from doing it wrong.
Here is my current setup:
nuMaps/Interfaces/ILocationService.cs
using Xamarin.Forms.Maps;
namespace nuMaps
{
public interface ILocationService
{
void initLocationService();
Position getCurrentPosition();
}
}
nuMaps/nuMaps.Droid/Interfaces/LocationService.cs
using System;
using nuMaps;
using nuMaps.Droid;
using Xamarin.Forms.Maps;
using Android.App;
using Android.Gms.Common;
using Android.Gms.Common.Apis;
using Android.Gms.Location;
using Android.Locations;
using Android.Widget;
[assembly: Xamarin.Forms.Dependency (typeof (LocationService))]
namespace nuMaps.Droid
{
public class LocationService : Java.Lang.Object, ILocationService, IGoogleApiClientConnectionCallbacks, IGoogleApiClientOnConnectionFailedListener, Android.Gms.Location.ILocationListener
{
readonly IGoogleApiClient _googleApiClient;
readonly LocationRequest _locRequest;
Position _currentPosition;
Location _currentLocation;
public LocationService()
{
Console.WriteLine ("LocationService constructor");
_currentPosition = new Position (0, 0);
_googleApiClient = new GoogleApiClientBuilder (Xamarin.Forms.Forms.Context)
.AddApi (LocationServices.Api)
.AddConnectionCallbacks (this)
.AddOnConnectionFailedListener (this)
.Build ();
_locRequest = new LocationRequest ();
}
#region ILocationService implementation
public void initLocationService()
{
_googleApiClient.Connect ();
}
public Position getCurrentPosition ()
{
if (_googleApiClient.IsConnected) {
_currentLocation = LocationServices.FusedLocationApi.GetLastLocation (_googleApiClient);
_currentPosition = new Position (_currentLocation.Latitude, _currentLocation.Longitude);
}
_googleApiClient.Disconnect ();
return new Position (_currentLocation.Latitude, _currentLocation.Longitude);
}
#endregion
public void OnLocationChanged(Location l)
{
Console.WriteLine ("OnLocationChanged");
_currentLocation = l;
}
public void OnConnectionFailed (ConnectionResult result)
{
Console.WriteLine ("ConnectionFailed");
}
#region IGoogleApiClientConnectionCallbacks implementation
public void OnConnected (Android.OS.Bundle connectionHint)
{
Console.WriteLine ("OnConnected");
if (!_googleApiClient.IsConnected)
LocationServices.FusedLocationApi.RequestLocationUpdates (_googleApiClient, _locRequest, this);
_currentLocation = LocationServices.FusedLocationApi.GetLastLocation (_googleApiClient);
}
public void OnConnectionSuspended (int cause)
{
Console.WriteLine ("OnConnectionSuspended");
}
#endregion
}
}
Usage in nuMaps/Views/MapPage.xaml.cs
using Xamarin.Forms;
using Xamarin.Forms.Maps;
using System;
using System.Diagnostics;
namespace nuMaps
{
public partial class MapPage : ContentPage
{
public MapPage ()
{
InitializeComponent ();
Position p = DependencyService.Get<ILocationService>().getCurrentPosition();
MyMap.MoveToRegion (
MapSpan.FromCenterAndRadius (
p, Distance.FromMiles (1)
)
);
}
}
}
nuMaps/Views/Loginpage.xaml.cs
using System;
using Xamarin.Forms;
using nuMaps;
namespace nuMaps
{
public partial class LoginPage : ContentPage
{
public LoginPage ()
{
InitializeComponent ();
/*
* Init platform specific location service.
* TODO: This *shouldn't* need to happen, but we don't get location information until
* the OnConnected callback happens which is too slow to put in getCurrentLocation method.
*/
DependencyService.Get<ILocationService>().initLocationService();
}
}
}
I believe the problem is in your implementation of ILocationService.
I would remove implementing activity (why do you want to use OnCreate?) and take a look at http://developer.xamarin.com/guides/android/platform_features/maps_and_location/location/.
I'd recommend on Android getting the GPS location via the google play apis, which will require implementing ILocationListener, IGoogleApiClientConnectionCallbacks, and IGoogleApiClientOnConnectionFailedListener. Hope that helps!
Edit for comments:
If the LocationService in the question is up to date, I don't see that you're implementing IGoogleApiClientConnectionCallbacks or ILocationListener. It may be that because the mappage is using gps, GetLastKnownLocation works, because a location has recently been obtained.
GPS location requesting is an async operation - one of the methods with IGoogleApiClientConnectionCallbacks is OnConnected, where you should call something like:
LocationServices.FusedLocationApi.RequestLocationUpdates(googleApiClient, locationRequest, this);
This will actively request location updates, and then fire OnLocationChanged(Location location) when an location update is returned. This is all async, so you'll likely need to expose these events in your LocationService and subscribe to them.
You could give TinyIoC a try instead of the Xamarin Dependency Service. It works well for platform-specific implementations, like location services.
Maybe the Xamarin Dependency Service can be used for these types of things, but I've only used it for custom Renderers so far. For more service-based stuff, I use TinyIoC.
I want one class to be both an object, which provides information about the backend, and a class, which a backend informs when servers goes down (e.g. ZooKeeper or WCF).
The problem is when I bind the same class to two different interfaces in in singleton scope, Ninject creates either two instances or throws an error, depending on how I do it.
The following example must print the same Guid and all interfaces must be bound.
Example:
Program.cs
using System;
using Ninject;
using Ninject.Modules;
namespace ConsoleApplication1
{
static class Program
{
static void Main(string[] args)
{
IKernel kernel = new StandardKernel();
kernel.Load(new INinjectModule[] { new Bindings() });
Console.WriteLine("First interface");
var i1 = kernel.Get<IState>();
i1.Inform();
Console.WriteLine("Second interface");
var i2 = kernel.Get<IListener>();
i2.Send();
Console.ReadKey();
}
}
}
IListener.cs
namespace ConsoleApplication1
{
public interface IListener
{
void Send();
}
}
IState.cs
namespace ConsoleApplication1
{
public interface IState
{
void Inform();
}
}
StateClass.cs
using System;
namespace ConsoleApplication1
{
public class StateClass : IState, IListener
{
private readonly String _seed;
public StateClass()
{
_seed = Guid.NewGuid().ToString();
}
public void Send()
{
Console.WriteLine(_seed);
}
public void Inform()
{
Console.WriteLine(_seed);
}
}
}
Bindings.cs - Version 1 In this example everything works, if the code is commented. The problem is I dont know in advance if a class impelments IState interface it will also IListener interface:
using Ninject.Modules;
using Ninject.Extensions.Conventions;
namespace ConsoleApplication1
{
class Bindings : NinjectModule
{
public override void Load()
{
Kernel.Bind(x => x
.FromAssemblyContaining<IState>()
.SelectAllClasses()
.InheritedFrom<IState>()
.BindAllInterfaces()
.Configure(y => y.InSingletonScope()));
//uncomment the following binding to see an exception
//problem is we dont know this in advance
//Kernel.Bind(x => x
// .FromAssemblyContaining<IListener>()
// .SelectAllClasses()
// .InheritedFrom<IListener>()
// .BindAllInterfaces()
// .Configure(y => y.InSingletonScope()));
}
}
}
Bindings.cs - Version 2 - no exception, but Application prints different Guids:
using Ninject.Modules;
using Ninject.Extensions.Conventions;
namespace ConsoleApplication1
{
class Bindings : NinjectModule
{
public override void Load()
{
Kernel.Bind<IListener>().To<StateClass>().InSingletonScope();
Kernel.Bind<IState>().To<StateClass>().InSingletonScope();
}
}
}
So I think in your module you're going to have to tell Ninject that both interfaces are using the same object. if you don't, Ninject will always assume that each interface has its own singleton.
class Bindings : NinjectModule
{
public override void Load()
{
Kernel.Bind<StateClass>().ToSelf().InSingletonScope();
Kernel.Bind<IListener>().ToMethod(ctx => ctx.Kernel.Get<StateClass>());
Kernel.Bind<IState>().ToMethod(ctx => ctx.Kernel.Get<StateClass>());
}
}