How to...

Overview

io.Connect Desktop provides .NET libraries that enable you to access programmatically the available io.Connect features. There are three versions of the io.Connect .NET library for the following frameworks:

The libraries provide identical APIs and are initialized in the same way.

⚠️ Note that the io.Connect .NET library for .NET Standard is meant to provide a cross-platform support (e.g., for Android, Linux, Blazor) and therefore doesn't work with window handles. This means that you can use the library to control other windows registered in the io.Connect framework, but you can't use it to register your own window as an io.Connect Window.

To use the io.Connect Desktop features, you must reference the io.Connect .NET library (either as a standalone DLL file or as a NuGet package), and instantiate and initialize the Glue42 class (for .NET 5+ and .NET Framework) or the Glue42Base class (for .NET Standard) to gain access to the io.Connect functionalities.

Referencing

NuGet Package

The io.Connect .NET library is available as a NuGet package for .NET 5+, .NET Framework and .NET Standard, which you can include and configure as a standard NuGet package in your projects.

Standalone File

Another option is to reference the DLL library files delivered with the io.Connect Desktop installer and located in %LocalAppData%/interop.io/io.Connect Desktop/SDK/.NET/lib/.

Referencing the DLL library file for .NET 5+:

<Reference Include="GlueNetCore">
    <HintPath>..\lib\GlueNetCore.dll</HintPath>
</Reference>

Referencing the DLL library file for .NET Framework:

<Reference Include="ioConnectNET">
    <HintPath>..\lib\ioConnectNET.dll</HintPath>
</Reference>

Initialization

The .NET io.Connect library is initialized asynchronously with an optional InitializeOptions object in which you can specify various settings and io.Connect features you want your app to use.

⚠️ Note that after initializing the library, you may want to register the windows of your .NET app as io.Connect Windows in order to be able to use io.Connect functionalities. For more details on how to do that correctly and the pitfalls to avoid during window registration, see the Capabilities > Window Management > .NET section.

.NET 5+

The following example demonstrates initializing the io.Connect .NET library for .NET 5+ and specifying initialization options:

var options = new InitializeOptions()
{
    ApplicationName = "my-app",
    IncludedFeatures = GDFeatures.UseAppManager | GDFeatures.UseGlueWindows
};

// The initialization options aren't required.
// If skipped, the library will be initialized with the default options
// (default assembly name for `ApplicationName` and with all features included).
Glue42 io = await Glue42.InitializeGlue(options);

Logging

When initializing the io.Connect .NET library for .NET 5+, you can provide a logger factory implementation to be used as a logging mechanism instead of the built-in one:

var options = new InitializeOptions() { LoggerFactory = new Log4NetLoggerFactory() };

Glue42 io = await Glue42.InitializeGlue(options);

The following is an example implementation of a logger factory using the standard log4net library:

using log4net;

public class Log4NetLoggerFactory : IGlueLoggerFactory
{
    public IGlueLog GetLogger(string name)
    {
        return new Log4NetLog(LogManager.GetLogger(name));
    }

    public IGlueLog GetLogger(Type type)
    {
        return new Log4NetLog(LogManager.GetLogger(type));
    }

    public static Level GetLog4NetLevel(GlueLogLevel level)
    {
        return level switch
        {
            GlueLogLevel.All => Level.All,
            GlueLogLevel.Trace => Level.Trace,
            GlueLogLevel.Debug => Level.Debug,
            GlueLogLevel.Info => Level.Info,
            GlueLogLevel.Warn => Level.Warn,
            GlueLogLevel.Error => Level.Error,
            GlueLogLevel.Fatal => Level.Fatal,
            GlueLogLevel.Off => Level.Off,
            _ => throw new ArgumentOutOfRangeException(nameof(level), level, null)
        };
    }

    public class Log4NetLog : IGlueLog
    {
        private readonly ILog log_;

        public Log4NetLog(ILog log)
        {
            log_ = log;
        }

        public void Trace(Func<string> messageCtor, Exception e = null)
        {
            if (log_.Logger.IsEnabledFor(Level.Trace))
            {
                log_.Logger.Log(new LoggingEvent(new LoggingEventData
                {
                    Level = Level.Trace,
                    Message = messageCtor(),
                    LoggerName = log_.Logger.Name,
                    ThreadName = Thread.CurrentThread.Name,
                    TimeStampUtc = DateTime.UtcNow,
                    ExceptionString = e?.ToString()
                }));
            }
        }

        public void Info(string message, Exception e = null)
        {
            log_.Info(message, e);
        }

        public void Error(string message, Exception e = null)
        {
            log_.Error(message, e);
        }

        public void Debug(string message, Exception e = null)
        {
            log_.Debug(message, e);
        }

        public void Debug(Func<string> message, Exception e = null)
        {
            if (log_.IsDebugEnabled)
            {
                log_.Debug(message(), e);
            }
        }

        public bool IsEnabledFor(GlueLogLevel level)
        {
            return log_.Logger.IsEnabledFor(GetLog4NetLevel(level));
        }

        public void Warn(string message, Exception e = null)
        {
            log_.Warn(message, e);
        }

        public void Log(GlueLogLevel level, string message, Exception exception)
        {
            log_.Logger.Log(typeof(LogImpl), GetLog4NetLevel(level), message, exception);
        }
    }
}

.NET Framework

The following example demonstrates initializing the io.Connect .NET library for .NET Framework and specifying initialization options:

var initializeOptions = new InitializeOptions()
{
    ApplicationName = "my-app",
    IncludedFeatures = GDFeatures.UseAppManager | GDFeatures.UseGlueWindows
};

// The initialization options aren't required.
// If skipped, the library will be initialized with the default options
// (default assembly name for `ApplicationName` and with all features included).
Glue42 io = await Glue42.InitializeGlue(initializeOptions);

Logging

The io.Connect .NET library for .NET Framework can use your custom proxy logging facade implementation as a logging mechanism instead of the built-in one.

First, you must initialize your logging library and bind the proxy implementation to the built-in logging mechanism:

// Initializing your custom logging library.
log4net.Config.XmlConfigurator.Configure();

// Binding the proxy logging facade implementation to the built-in logging mechanism.
DotLoggingFacade.Instance = new ProxyLoggingFacade();

After that, initialize the io.Connect .NET library and provide initialization options that will instruct it to use your custom logging facade:

var options = new InitializeOptions() { LogLibrary = LogLibrary.UseCustomFacade };

Glue42 io = await Glue42.InitializeGlue(options);

The following is an example implementation of a proxy logging facade using the standard log4net library:

public class ProxyLoggingFacade : IDotLoggingFacade
{
    public IDotLog GetLogger(string loggerName)
    {
        return new DotLog(loggerName);
    }

    public List<string> GetLogFolders()
    {
        // ensure repo is initialized
        var logger = GetLogger(GetType().FullName);

        var fileAppenders = GetFileAppenders();

        var dirs = new HashSet<string>(fileAppenders.Select(fa => Path.GetDirectoryName(fa.File)));

        return dirs.ToList();
    }

    public List<string> GetLogFileNames()
    {
        // ensure repo is initialized
        var logger = GetLogger(GetType().FullName);

        var fileAppenders = GetFileAppenders();

        var files = new HashSet<string>(fileAppenders.Select(fa => fa.File));

        return files.ToList();
    }

    private static IEnumerable<FileAppender> GetFileAppenders()
    {
        var repository = LogManager.GetRepository();

        var fileAppenders =
            repository.GetAppenders().Where(iAppender => iAppender is FileAppender).Cast<FileAppender>();
        return fileAppenders;
    }

    private class DotLog : IDotLog
    {
        static DotLog()
        {
            DeclaringType = typeof(DotLog);
        }

        public DotLog(string loggerName)
        {
            Log4NetLogger = LogManager.GetLogger(loggerName);
        }

        internal DotLog(ILog log4NetLogger)
        {
            Log4NetLogger = log4NetLogger;
        }

        private static Type DeclaringType { get; }

        private ILog Log4NetLogger { get; }

        public bool IsEnabled(DotLogLevel logLevel)
        {
            return Log4NetLogger.Logger.IsEnabledFor(GetLevel(logLevel));
        }

        public void Log(DotLogLevel logLevel, object message)
        {
            Log4NetLogger.Logger.Log(DeclaringType, GetLevel(logLevel), message, null);
        }

        public void Log(
            DotLogLevel logLevel,
            object message,
            Exception exception)
        {
            Log4NetLogger.Logger.Log(DeclaringType, GetLevel(logLevel), message, exception);
        }

        public void FlushBuffers()
        {
            ILoggerRepository rep = LogManager.GetRepository();
            foreach (IAppender appender in rep.GetAppenders())
            {
                if (appender is BufferingAppenderSkeleton buffered)
                {
                    buffered.Flush();
                }
            }
        }

        public bool IsTraceEnabled => Log4NetLogger.Logger.IsEnabledFor(Level.Trace);

        public bool IsDebugEnabled => Log4NetLogger.IsDebugEnabled;

        public bool IsInfoEnabled => Log4NetLogger.IsInfoEnabled;

        public bool IsWarnEnabled => Log4NetLogger.IsWarnEnabled;

        public bool IsErrorEnabled => Log4NetLogger.IsErrorEnabled;

        public bool IsFatalEnabled => Log4NetLogger.IsFatalEnabled;

        public DotLogLevel SetLogLevel(DotLogLevel logLevel)
        {
            var logger = (Logger)Log4NetLogger.Logger;
            var oldLevel = logger.Level;
            logger.Level = GetLevel(logLevel);
            return GetLevel(oldLevel);
        }

        private static DotLogLevel GetLevel(Level logLevel)
        {
            if (logLevel == Level.All)
            {
                return DotLogLevel.All;
            }

            if (logLevel == Level.Trace)
            {
                return DotLogLevel.Trace;
            }

            if (logLevel == Level.Debug)
            {
                return DotLogLevel.Debug;
            }

            if (logLevel == Level.Info)
            {
                return DotLogLevel.Info;
            }

            if (logLevel == Level.Warn)
            {
                return DotLogLevel.Warn;
            }

            if (logLevel == Level.Error)
            {
                return DotLogLevel.Error;
            }

            if (logLevel == Level.Fatal)
            {
                return DotLogLevel.Fatal;
            }

            return DotLogLevel.Off;
        }

        private static Level GetLevel(DotLogLevel logLevel)
        {
            switch (logLevel)
            {
                case DotLogLevel.All:
                    return Level.All;
                case DotLogLevel.Trace:
                    return Level.Trace;
                case DotLogLevel.Debug:
                    return Level.Debug;
                case DotLogLevel.Info:
                    return Level.Info;
                case DotLogLevel.Warn:
                    return Level.Warn;
                case DotLogLevel.Error:
                    return Level.Error;
                case DotLogLevel.Fatal:
                    return Level.Fatal;
                case DotLogLevel.Off:
                default:
                    return Level.Off;
            }
        }
    }
}

.NET Standard

The following example demonstrates initializing the io.Connect .NET library for .NET Standard and specifying initialization options:

var initializeOptions = new InitializeOptions()
{
    ApplicationName = "my-app",
    IncludedFeatures = GDFeatures.UseAppManager | GDFeatures.UseGlueWindows
};

// The initialization options aren't required.
// If skipped, the library will be initialized with the default options
// (default assembly name for `ApplicationName` and with all features included).
Glue42Base io = await Glue42Base.InitializeGlue(initializeOptions);

Logging

The procedure for providing a custom logging mechanism for the io.Connect .NET library for .NET Standard is identical to the one for the io.Connect .NET library for .NET 5+ For more details, see the Initialization > .NET 5+ > Logging section.

App Definition

To add your .NET app to the io.Connect launcher, you must create a JSON file with app definition. Place this file in the %LocalAppData%/interop.io/io.Connect Desktop/UserData/<ENV>-<REG>/apps folder, where <ENV>-<REG> represents the environment and region of io.Connect Desktop (e.g., DEMO-INTEROP.IO).

⚠️ Note that you can also launch manually your interop-enabled .NET app and it will automatically register itself and its child windows in the io.Connect Desktop in-memory store. In this case, your app will be available in the io.Connect environment until the next restart of io.Connect Desktop. This is useful if you have a multi window interop-enabled app with dynamic interop-enabled child windows (e.g., different user configurations, software updates, etc.) - when your main window starts its child windows, they will be registered automatically.

The following is an example definition of a .NET app:

{
    "title": "My .NET App",
    "type": "exe",
    "name": "my-net-app",
    "details": {
        "path": "%GDDIR%/../Demos/MyNETApp/",
        "command": "MyNETApp.exe",
        "parameters": " --mode=1"
    }
}

The "name", "type" and "path" properties are required and "type" must be set to "exe". The "path" property points to the app working directory.

The value of the "title" property will be used as a name for the app in the io.Connect launcher and as a window title.

For more details, see the Developers > Configuration > Application section.

See the .NET examples on GitHub which demonstrate various io.Connect Desktop features.

ClickOnce

io.Connect Desktop offers support for ClickOnce apps. Below you can see how to initialize the io.Connect .NET library in your ClickOnce app and how to register a ClickOnce app in io.Connect Desktop.

Initialization

In a ClickOnce app, the library is initialized the same way as in other .NET apps:

var initializeOptions = new InitializeOptions()
{
    ApplicationName = "ClientProfileDemo",
    IncludedFeatures = GDFeatures.UseAppManager | GDFeatures.UseGlueWindows
};

// The initialization options aren't required.
// If skipped, the library will be initialized with the default options
// (default assembly name for `ApplicationName` and with all features included).
Glue42 io = await Glue42.InitializeGlue(initializeOptions);

Registering ClickOnce Apps

To add your ClickOnce app to the io.Connect launcher, you must create a JSON file with app definition.

The following is an example definition of a ClickOnce app:

{
    "title": "My ClickOnce App",
    "type": "clickonce",
    "name": "my-clickonce-app",
    "details": {
        "url": "https://example.com/my-clickonce-app.application",
        "width": 1000,
        "height": 400,
        "appParameters": [
            {
                "name": "p1",
                "value": "customParameter"
            }
        ]
    }
}

The "name", "type" and "url" properties are required and "type" must be set to "clickonce". The "url" property points to the physical location where the ClickOnce app is deployed and from where it will be installed on the user machine.

The "appParameters" property is an array of objects defining custom parameters that your app can access at runtime through io.GDStartingContext.ApplicationConfig.Details.AppParameters. Each object sets the "name" and the "value" of a custom parameter.

For more details, see the Developers > Configuration > Application section.

Silverlight

io.Connect Desktop offers a Silverlight library as a version of the io.Connect .NET library. The Silverlight library offers the same features and functionalities as the io.Connect .NET library, it's only tailored to meet the specifications of the Microsoft Silverlight framework.

io.Connect .NET Concepts

Once the io.Connect .NET library has been initialized, your app has access to all io.Connect functionalities. For more detailed information on the different io.Connect capabilities and APIs, see: