How to properly implement Xamarin NetworkServiceDiscovery via DependencyService? - c#

I've got a Xamarin Cross-Platform App and want to use Android's NetworkServiceDiscovery API.
I tried to implement it according to https://developer.android.com/training/connect-devices-wirelessly/nsd.html
Now, I'm not sure if I did everything right, for example: The android documentation wants you to create a RegistrationListener like this:
Android:
public void initializeRegistrationListener() {
mRegistrationListener = new NsdManager.RegistrationListener() {
#Override
public void onServiceRegistered(NsdServiceInfo NsdServiceInfo) {
// Save the service name. Android may have changed it in order to
// resolve a conflict, so update the name you initially requested
// with the name Android actually used.
mServiceName = NsdServiceInfo.getServiceName();
}
#Override
public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
// Registration failed! Put debugging code here to determine why.
}
#Override
public void onServiceUnregistered(NsdServiceInfo arg0) {
// Service has been unregistered. This only happens when you call
// NsdManager.unregisterService() and pass in this listener.
}
#Override
public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
// Unregistration failed. Put debugging code here to determine why.
}
};
}
And I rebuilt it like this in C#:
public class RegistrationListener : NsdManager.IRegistrationListener
{
public string serviceName;
public void Dispose()
{
throw new NotImplementedException();
}
public IntPtr Handle { get; }
public void OnRegistrationFailed(NsdServiceInfo serviceInfo, NsdFailure errorCode)
{
// Registration failed! Put debugging code here to determine why.
}
public void OnServiceRegistered(NsdServiceInfo serviceInfo)
{
// Save the service name. Android may have changed it in order to
// resolve a conflict, so update the name you initially requested
// with the name Android actually used.
serviceName = serviceInfo.ServiceName;
}
public void OnServiceUnregistered(NsdServiceInfo serviceInfo)
{
// Service has been unregistered. This only happens when you call
// NsdManager.unregisterService() and pass in this listener.
}
public void OnUnregistrationFailed(NsdServiceInfo serviceInfo, NsdFailure errorCode)
{
// Unregistration failed. Put debugging code here to determine why.
}
}
I implemented the ResolveListener and DiscoveryListener in the same way.
Then I made a Helper Class with the functions I want to call via DependencyServices:
public class NsdHelper
{
public static readonly string SERVICE_TYPE = "chatTest._tcp";
public DiscoveryListener discoveryListener;
public NsdManager nsdManager;
public NsdServiceInfo nsdServiceInfo;
public RegistrationListener registrationListener;
public ResolveListener resolveListener;
public string SERVICE_NAME { get; set; }
public void InitializeNsd()
{
resolveListener = new ResolveListener();
discoveryListener = new DiscoveryListener();
registrationListener = new RegistrationListener();
resolveListener.ServiceName = SERVICE_NAME;
resolveListener.ServiceInfo = nsdServiceInfo;
discoveryListener.resolveListener = resolveListener;
discoveryListener.nsdManager = nsdManager;
}
public void RegisterService(string sessionName)
{
SERVICE_NAME = sessionName;
// Create the NsdServiceInfo object, and populate it.
nsdServiceInfo = new NsdServiceInfo
{
ServiceName = sessionName,
ServiceType = SERVICE_TYPE,
Port = GenerateFreePort()
};
InitializeNsd();
// The name is subject to change based on conflicts
// with other services advertised on the same network.
nsdManager = (NsdManager) Application.Context.GetSystemService(Context.NsdService);
nsdManager.RegisterService(
nsdServiceInfo, NsdProtocol.DnsSd, registrationListener);
}
private int GenerateFreePort()
{
//setting the ServerSocket to 0 will generate the next free port
var serverSocket = new ServerSocket(0);
return serverSocket.LocalPort;
}
public void DiscoverServices()
{
nsdManager.DiscoverServices(
SERVICE_TYPE, NsdProtocol.DnsSd, discoveryListener);
}
public void StopDiscovery()
{
nsdManager.StopServiceDiscovery(discoveryListener);
}
public NsdServiceInfo GetChosenServiceInfo()
{
return nsdServiceInfo;
}
public void TearDown()
{
nsdManager.UnregisterService(registrationListener);
}
}
And now when I call RegisterService I get the following Error:
I don't know where exactly I've gone wrong! The errors I get while debugging Xamarin Apps also don't help much :(

Inherit your RegistrationListener subclass from Java.Lang.Object
Remove the Handle property and the Dispose methods as those are implemented in the Java.Lang.Object.
public class RegistrationListener : Java.Lang.Object, NsdManager.IRegistrationListener
{
~~~
}
Once you do that a ACW (Android Callable Wrapper) that will be generated to bind your C# implementation so it can be instanced from Java VM.

Related

Is there any solution to handle dataType for TestCaseSource ? [Nunit Framework]

Based on https://gigi.nullneuron.net/gigilabs/data-driven-tests-with-nunit/ website. I have try to create a simple testcase which prepare for read data in the future. But I have no idea how to handle Argument and use it properly
I have try to set as a object, but i think this might not be a correct solution
[TestCaseSource("GetDataString")]
public void TestMethod2(object configs)
{
}
Here is source code
namespace SAP
{
[TestFixture]
public class Scenario1
{
// This one Give System.ArgumentException
[TestCaseSource("GetDataString")]
public void TestMethod(List<Config> configs)
{
Console.WriteLine("Config " + configs);
}
// This one can handle an Exception
[TestCaseSource("GetDataString")]
public void TestMethod2(object configs)
{
}
public static List<Config> GetDataString()
{
var datas = new List<Config>();
datas.Add(new Config("Nick", "Coldson"));
return datas;
}
}
public class Config
{
public string NickName { get; set; }
public string Name { get; set; }
public Config(string nickname, string name)
{
NickName = nickname;
Name = name;
}
}
}
Here is error msg
System.ArgumentException : Object of type 'SAP.Config' cannot be
converted to type 'System.Collections.Generic.List`1[SAP.Config]'.
The testcasesource has slightly different definition pattern. Assuming you use nunit 3 it should be:
[TestCaseSource(typeof(MyTestData), nameof(GetDataString))]
public void TestMethod2(List<Config> configs)
{
...
}
public class MyTestData
{
public static IEnumerable GetDataString()
{
var datas = new List<Config>();
datas.Add(new Config("Nick", "Coldson"));
return new TestCaseData(datas);
}
}
For more info, check the documentation:
https://github.com/nunit/docs/wiki/TestCaseData
Your GetDataString returns a List<Config>.
Meaning, your test method with a [TestCaseSource("GetDataString")] will be executed as many times as many items the list has and your method must match the item type.
//// This one throws System.ArgumentException
//[TestCaseSource("GetDataString")]
//public void TestMethod(List<Config> configs)
//{
// Console.WriteLine("Config " + configs);
//}
// This one is ok
[TestCaseSource("GetDataString")]
public void TestMethod(Config config)
{
Console.WriteLine(config);
}
If you need to get List<Config> instances in your test, then your source must return some collection containing list items.

ASP.NET Web API possible deserialization issue while using flurl

I have a ASP.NET(C#, .NET 4.6.1) Web-Api-GET function which returns a complex object instance and is of generic type. Here is the return type definition (Note that the classes are much expansive in reality).
public class FileProcessInstance
{
public FileProcessInstance()
{ }
//ID that identifies file by primary key of log table
public int FileLogID;
//File name without path as received
public string OriginialFileName;
//Path with file name where file can be physically accessed
public string FileSharePath;
}
public class CommonStatusPayload<T> : CommonStatus
{
public CommonStatusPayload() : base(false)
{
Payload = default(T);
}
public CommonStatusPayload(T payload, bool status)
: base(status)
{
Payload = payload;
}
public virtual T Payload { get; private set; }
}
public class CommonStatus
{
public CommonStatus() : this(false)
{
}
public CommonStatus(bool status)
{
Status = status;
}
public bool Status { get; set; }
}
Now my web api looks like this:
[HttpGet]
public CommonStatusPayload<List<FileProcessInstance>> GetFilesForProcessing()
{
List<FileProcessInstance> lst = new List<FileProcessInstance>() { new FileProcessInstance() { FileLogID = 1, FileSharePath = #"\\d\s", OriginialFileName = "d.txt" } };
CommonStatusPayload<List<FileProcessInstance>> cs = new CommonStatusPayload<List<FileProcessInstance>>(lst, true);
return cs;
}
The issue is, a call to this api from C# code would receive null as payload, while Postman request does receive proper payload.
Now my client code looks like this:
static void Main(string[] args)
{
var lst = GetFilesForProcessing();
}
private static List<FileProcessInstance> GetFilesForProcessing()
{
List<FileProcessInstance> lst = new List<FileProcessInstance>();
try
{
Task<CommonStatusPayload<List<FileProcessInstance>>> task = GetFilesForProcessingFromAPI();
task.Wait();
if (task.Result.Payload != null)
lst.AddRange(task.Result.Payload);
}
catch (Exception ex)
{
}
return lst;
}
private static async Task<CommonStatusPayload<List<FileProcessInstance>>> GetFilesForProcessingFromAPI()
{
return await "http://localhost:10748/api/values/GetFilesForProcessing".ToString()
.GetAsync().ReceiveJson<CommonStatusPayload<List<FileProcessInstance>>>();
}
I have observed that the return payload works if it were to be a a) list by itslef b) a local instance of CommonStatusPayload<List<FileProcessInstance>>. This makes me believe that there is a possible deserialization issue, when the result is handed over to C# code from web-api. A fiddler check for the same request turns out to be just fine, just that C# client would not receive proper result.
Any guess as to what could be the underlying reason for payload being null?
I found the root cause of this issue. The private setter for Payload within CommonStatusPayload class is causing the deserialization to fail. Although for the intended behavior, i wanted the payload to be set only via constructor/method always to be associated with a relative status, at-least this change allows me to continue.
I did find some other questions here, related to JSON.NET with protected setters having same issues.

Ninject Factory Pattern and Bindings

I am trying to implement the Ninject.Extensions.Factory pattern and my program is telling me my bindings aren't right, but I can't figure out why. I keep getting an "Error activating IHashable. No matching bindings are available, and the type is not self-bindable" exception thrown. The relevant areas of my code are below:
public interface IHashable
{
FileInfo File { get; }
string ComputeHash();
}
public interface IHashableFactory
{
IHashable GetNew(FileInfo file);
}
public class MD5ChecksumProvider : IHashable
{
private FileInfo _file;
public FileInfo File
{
get { return _file; }
}
public MD5ChecksumProvider(FileInfo file)
{
if (file == null)
throw new ArgumentNullException("file");
_file = file;
}
public string ComputeHash()
{
// implementation
}
}
public class AppFileProvider : IAppFileProvider
{
private IHashableFactory _hashFactory;
public IHashableFactory HashProvider
{
get { return _hashFactory; }
}
public AppFileProvider(IHashableFactory hashProviderFactory)
{
_hashFactory = hashProviderFactory;
}
public string GetChecksum(FileInfo fileInfo)
{
var hasher = _hashFactory.GetNew(fileInfo);
return hasher.ComputeHash();
}
}
public class BindingProviders : NinjectModule
{
public override void Load()
{
Bind<IHashable>()
.To<MD5ChecksumProvider>();
}
}
public class BindingFactories : NinjectModule
{
public override void Load()
{
Bind<IHashableFactory>()
.ToFactory();
}
}
// my DI container
public sealed class Container : IDisposable
{
private bool _isDisposed;
private IKernel _kernel;
private BindingFactories _bindingFactories;
private BindingObjects _bindingObjects;
private BindingProviders _bindingProviders;
public Container()
{
_isDisposed = false;
_bindingFactories = new BindingFactories();
_bindingObjects = new BindingObjects();
_bindingProviders = new BindingProviders();
_kernel = new StandardKernel(_bindingObjects, _bindingProviders, _bindingFactories);
}
public T Get<T>()
{
return _kernel.Get<T>();
}
public void Dispose()
{
// nothing worth seeing
}
private void Dispose(bool disposing)
{
// nothing worth seeing
}
}
// the program (composition root)
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
using (var container = new Container())
{
var fileProvider = container.Get<IAppFileProvider>();
foreach (var file in files)
{
string hash = fileProvider.GetChecksum(storePath, file); // this line throws "Error activating IHashable. No matching bindings are available, and the type is not self-bindable.""
}
}
}
}
I feel like my bindings are setup correctly but I must be missing something obvious. Any ideas why I'm getting the exception from the above code?
This is caused by a feature of Ninject.Extensions.Factory.
It treats methods which start with Get differently from those which don't.
If you rename IHashableFactory.GetNew to Create or Make everything works fine.
The "Get" feature is described here:
The default instace provider of the extension has the convention that it tries to return an instance using a named binding whenever a method starts with “Get”. E.g. IFoo GetMySpecialFoo() is equal to
resolutionRoot.Get<IFoo>("MySpecialFoo");
Since i think this is not obvious to the user and the exception isn't helpful at all in this regard, i have filed an issue report here

Moving dynamic method call to class library causes a RuntimeBinderException in C#

I was reading an interesting article using of DataFlow + dynamic method invocation to make an Actor model in C#. Here is the completed example verbatim.
using System;
using System.Threading.Tasks.Dataflow;
namespace ConsoleApplication
{
public abstract class Message { }
public abstract class Actor
{
private readonly ActionBlock<Message> _action;
public Actor()
{
_action = new ActionBlock<Message>(message =>
{
dynamic self = this;
dynamic mess = message;
self.Handle(mess);
});
}
public void Send(Message message)
{
_action.Post(message);
}
}
class Program
{
public class Deposit : Message
{
public decimal Amount { get; set; }
}
public class QueryBalance : Message
{
public Actor Receiver { get; set; }
}
public class Balance : Message
{
public decimal Amount { get; set; }
}
public class AccountActor : Actor
{
private decimal _balance;
public void Handle(Deposit message)
{
_balance += message.Amount;
}
public void Handle(QueryBalance message)
{
message.Receiver.Send(new Balance { Amount = _balance });
}
}
public class OutputActor : Actor
{
public void Handle(Balance message)
{
Console.WriteLine("Balance is {0}", message.Amount);
}
}
static void Main(string[] args)
{
var account = new AccountActor();
var output = new OutputActor();
account.Send(new Deposit { Amount = 50 });
account.Send(new QueryBalance { Receiver = output });
Console.WriteLine("Done!");
Console.ReadLine();
}
}
}
This works as intended. Moving Actor & Message classes into a new class library and referencing properly causing an issue. When run it throws a RuntimeBinderException on the dynamic self.Handle(mess); within the Actor constructor saying Actors.Actor does not contain a definition for 'Handle'. Are there limitations to dynamic method calls I can't seem to find in the MSDN or syntax magic I missing to do this from a separate class library?
The original author got back to me.
Hi,
The problem is that you have declared your messages and actors inside
the internal NotWorkingProgram class.
class NotWorkingProgram // no access modifier! Default is 'internal' {
public class Deposit : Message
...
public class AccountActor : Actor
{
public void Handle(Deposit message)
...
} }
When you run the program the runtime tries to find a method named
'Handle' with a parameter of typ 'Deposit'. It can't find anything
because the AccountActor class is not visible from the Actors Project.
It is hidden inside the invisible NotWorkingProgram. If you make the
NotWorkingProgram class public (or move the Deposit and AccountActor
classes outside) it works!
Regards Johan
I'm leaving this here cause the RuntimeBinderException doesn't give much info, let alone any hint of class/method privacy being a possible root

mySQL error with plugin loaded into appDomain

I am struggling to get dynamic loading working with my plugin. I'm loading the plugin into an appdomain but I get the following exception when my plugin tries to execute any mySQL code:
MySql.Data.MySqlClient.MySqlException
was unhandled
Message=Access denied
for user ''#'localhost' (using
password: NO)
Here is the code on Plugins.cs:
public class Plugins {
private static List<PluginContainer> PluginList;
public static bool PluginsAreLoaded {
get {
if(PluginList==null) {
return false;
}
else {
return true;
}
}
}
public static void LoadAllPlugins(Form host) {
//add assemblies in OD folder to appDomain
Assembly[] list = AppDomain.CurrentDomain.GetAssemblies();
AppDomain appDomainAnesthesia AppDomain.CreateDomain("appDomainAnesthesia");
for(int i=0;i<ProgramC.Listt.Count;i++) {
string dllPathWithVersion = String.Empty;
if(!ProgramC.Listt[i].Enabled) {
continue;
}
if(ProgramC.Listt[i].PluginDllName=="") {
continue;
}
string dllPath=ODFileUtils.CombinePaths(Application.StartupPath,ProgramC.Listt[i].PluginDllName);
if(dllPath.Contains("[VersionMajMin]")) {
Version vers=new Version(Application.ProductVersion);
dllPathWithVersion=dllPath.Replace("[VersionMajMin]",vers.Major.ToString()+"."+vers.Minor.ToString());
dllPath=dllPath.Replace("[VersionMajMin]","");//now stripped clean
if(File.Exists(dllPathWithVersion)){
File.Copy(dllPathWithVersion,dllPath,true);
}
}
if(!File.Exists(dllPath)) {
continue;
}
//add our plugin to the appDomain
try {
string typeName = Path.GetFileNameWithoutExtension(dllPath) + ".Plugin";
PluginBase plugin = (PluginBase)appDomainAnesthesia.CreateInstanceAndUnwrap(Path.GetFileNameWithoutExtension(dllPathWithVersion), typeName);
appDomainAnesthesia.Load(Path.GetFileNameWithoutExtension(dllPathWithVersion));
plugin.Host=host;
}
catch(Exception ex) {
MessageBox.Show(ex.Message);
continue;//don't add it to plugin list.
}
PluginContainer container=new PluginContainer();
container.Plugin=plugin;
container.ProgramNum=ProgramC.Listt[i].ProgramNum;
PluginList.Add(container);
}
}
Here is PluginBase:
public abstract class PluginBase : MarshalByRefObject {
private Form host;
///<summary>This will be a refrence to the main FormOpenDental so that it can be used by the plugin if needed. It is set once on startup, so it's a good place to put startup code.</summary>
public virtual Form Host {
get {
return host;
}
set {
host=value;
}
}
///<summary>These types of hooks are designed to completely replace the existing functionality of specific methods. They always belong at the top of a method.</summary>
public virtual bool HookMethod(object sender,string methodName,params object[] parameters) {
return false;//by default, no hooks are implemented.
}
///<summary>These types of hooks allow adding extra code in at some point without disturbing the existing code.</summary>
public virtual bool HookAddCode(object sender,string hookName,params object[] parameters) {
return false;
}
public virtual void LaunchToolbarButton(long patNum) {
}
}
PluginContainer:
class PluginContainer {
public PluginBase Plugin;
public long ProgramNum;
and finally, Plugin:
public class Plugin : PluginBase {
private Form host;
public AnestheticData AnesthDataCur;
public AnestheticRecord AnestheticRecordCur;
public Patient PatCur;
public string SourceDirectory;
public string DestDirectory;
public System.ComponentModel.IContainer components;
public long patNum;
public static Procedure procCur;
public static List<Procedure> ProcList;
public static ODtextBox textNotes;
public override Form Host {
get {
return host;
}
set {
host=value;
ConvertAnesthDatabase.Begin();//if this crashes, it will bubble up and result in the plugin not loading. }
}
It bombs at the
plugin.Host=host;
line on Plugins.cs which comes from the ConverAnesthDatabase.Begin() method as there are multiple mySQL statements there.
I have access to the mySQL database, so that is not the issue.
How do I resolve this error?
Try not to "extend" your PluginBase and use an Interface instead:
public interface IPlugin
{
Form Host {get; set; }
bool HookMethod(object sender,string methodName,params object[] parameters);
bool HookAddCode(object sender,string hookName,params object[] parameters);
void LaunchToolbarButton(long patNum);
}
and then
public class Plugin : IPlugin
{
...
}

Categories