Purpose

The primary purpose of this blog is to explain cross-domain communications using events and attempts to answer questions.

How can I subscribe to an event across AppDomain?
How to raise an event across AppDomain?
How to perform cross AppDomain Communication?

Application Domain (AppDomain Class)

An application domain is an isolated environment where applications execute.

Internal Details

  1. An application domain is a partition in an operating system process where one or more applications reside.
  2. Objects in the same application domain communicate directly.
  3. Objects in different application domains communicate either by transporting copies of objects across application domain boundaries or by using a proxy to exchange messages.
  4. MarshalByRefObject is the base class for objects that communicate across application domain boundaries by exchanging messages using a proxy.
  5. Objects that do not inherit from MarshalByRefObject are implicitly MarshalByValue. When a remote application references a marshal by value object, it passes a copy of the object across application domain boundaries.
  6. MarshalByRefObject objects are accessed directly within the boundaries of the local application domain.
  7. The first time an application in a remote application domain accesses a MarshalByRefObject, it passes a proxy to the remote application. Subsequent calls on the proxy marshalls back to the object residing in the local application domain.
  8. Types must inherit from MarshalByRefObject when used across application domain boundaries, and it doesn't copy the state of the object because the members of the object are not usable outside the application domain where created.
  9. When you derive an object from MarshalByRefObject for use across application domain boundaries, you should not override any of its members, nor should you call its methods directly.
  10. The runtime recognises that classes derived from MarshalByRefObject marshalls across app domain boundaries.

Remarks

  1. AppDomain allows us to load DLL at runtime.
  2. For communication between 'AppDomain' boundary, the types should be Serializable.
  3. Derive from class MarshalByRefObject which enables access to objects across application domain boundaries in applications that support remoting.
  4. Full name of DLL assembly is used to load it into AppDomain. For now, we place it in the same folder as the main program.

Cross Domain Events

In this section, we discuss how to achieve sending and receiving events through Application Domain boundary. Here we are using the shared universal library with interfaces already known to us, and two separate publisher and subscriber DLLs loaded on runtime and fire events across domains.

For understanding we use four separate projects

  1. EventsCommon (Class Library Project)
    It defines standard interfaces for Publisher and Subscriber classes, and the Main Class uses it to create interface objects.

    namespace EventCommons
    {
        using System;
    
        /// <summary>
        /// Common Interface for Publisher
        /// </summary>
        public interface IEventCommonGenerator
        {
            /// <summary>
            /// Event using <see cref="Action{T}"/>
            /// </summary>
            event Action<string> NameGenerator;
    
            /// <summary>
            /// Fire Events
            /// </summary>
            /// <param name="input"></param>
            void FireEvent(string input);
        }
    
        /// <summary>
        /// Common Interface for Subscriber
        /// </summary>
        public interface IEventCommonCatcher
        {
            /// <summary>
            /// Print Events executed
            /// </summary>
            /// <returns></returns>
            string PrintEvents();
    
            /// <summary>
            /// Subscribe to Publisher's <see cref="IEventCommonGenerator.NameGenerator"/> event
            /// </summary>
            /// <param name="commonGenerator"></param>
            void Subscribe(IEventCommonGenerator commonGenerator);
        }
    }
    
  2. EventsPublisher (Class Library Project)
    It references EventCommon project and implements Publisher related Interface IEventCommonGenerator from EventCommon.

    namespace EventsPublisher
    {
        using EventCommons;
        using System;

        /// <summary>
        /// Implements <see cref="IEventCommonGenerator"/> from <see cref="EventCommons"/>
        /// </summary>
        [Serializable]
        public class EventsGenerators : IEventCommonGenerator
        {
            /// <summary>
            /// Fires Event
            /// </summary>
            /// <param name="input"></param>
            public void FireEvent(string input)
            {
                this.NameGenerator?.Invoke(input);
            }

            /// <summary>
            /// Event for Publisher
            /// </summary>
            public event Action<string> NameGenerator;
        }
    }
  1. EventsSubscriber (Class Library Project)
    It references EventCommon project and implements Subscriber related Interface IEventCommonCatcher from EventCommon.

    namespace EventsSubscriber
    {
        using System;
        using System.Collections.Generic;
        using EventCommons;

        /// <summary>
        /// Implements <see cref="IEventCommonCatcher"/> from <see cref="EventCommons"/>
        /// </summary>
        [Serializable]
        public class EventsCatcher : IEventCommonCatcher
        {
            /// <summary>
            /// Initializes object of <see cref="ReceivedValueList"/> and <see cref="EventsCatcher"/>
            /// </summary>
            public EventsCatcher()
            {
                this.ReceivedValueList = new List<string>();
            }

            /// <summary>
            /// Subscribes to the Publisher
            /// </summary>
            /// <param name="commonGenerator"></param>
            public void Subscribe(IEventCommonGenerator commonGenerator)
            {
                if (commonGenerator != null)
                {
                    commonGenerator.NameGenerator += this.CommonNameGenerator;
                }
            }

            /// <summary>
            /// Called when event fired from <see cref="IEventCommonGenerator"/> using <see cref="IEventCommonGenerator.FireEvent"/>
            /// </summary>
            /// <param name="input"></param>
            private void CommonNameGenerator(string input)
            {
                this.ReceivedValueList.Add(input);
            }

            /// <summary>
            /// Holds Events Values
            /// </summary>
            public List<string> ReceivedValueList { get; set; }

            /// <summary>
            /// Returns Comma Separated Events Value
            /// </summary>
            /// <returns></returns>
            public string PrintEvents()
            {
                return string.Join(",", this.ReceivedValueList);
            }
        }
    }

  1. CrossDomainEvents (Main Console Application)
    It loads EventsPublisher into Publisher AppDomain and EventsSubscriber into Subscriber AppDomain, Subscribes Events of Publisher AppDomain into Subscriber AppDomain and Fires the event.
using System;

namespace CrossDomainEvents
{
    using EventCommons;

    class Program
    {
        static void Main()
        {
            // Load Publisher DLL
            PublisherAppDomain.SetupDomain();
            PublisherAppDomain.CustomDomain.Load("EventsPublisher, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
            var newPublisherGenerator = PublisherAppDomain.Instance as IEventCommonGenerator;

            // Load Subscriber DLL
            SubscriberAppDomain.SetupDomain(newPublisherGenerator);
            SubscriberAppDomain.CustomDomain.Load("EventsSubscriber, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
            var newSubscriberCatcher = SubscriberAppDomain.Instance as IEventCommonCatcher;

            // Fire Event from Publisher and validate event on Subscriber
            if (newSubscriberCatcher != null && newPublisherGenerator != null)
            {
                // Subscribe Across Domains
                newSubscriberCatcher.Subscribe(newPublisherGenerator);

                // Fire Event
                newPublisherGenerator.FireEvent("First");

                // Validate Events
                Console.WriteLine(newSubscriberCatcher.PrintEvents());
            }

            Console.ReadLine();
        }
    }

    /// <summary>
    /// Creates Publisher AppDomain
    /// </summary>
    public class PublisherAppDomain : MarshalByRefObject
    {

        public static AppDomain CustomDomain;
        public static object Instance;

        public static void SetupDomain()
        {
            // Domain Name EventsGenerator
            CustomDomain = AppDomain.CreateDomain("EventsGenerator");
            // Loads EventsPublisher Assembly and create EventsPublisher.EventsGenerators
            Instance = Activator.CreateInstance(CustomDomain, "EventsPublisher, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "EventsPublisher.EventsGenerators").Unwrap();
        }
    }

    /// <summary>
    /// Creates Subscriber AppDomain
    /// </summary>
    public class SubscriberAppDomain : MarshalByRefObject
    {

        public static AppDomain CustomDomain;
        public static object Instance;

        public static void SetupDomain(IEventCommonGenerator eventCommonGenerator)
        {
            // Domain Name EventsCatcher
            CustomDomain = AppDomain.CreateDomain("EventsCatcher");
            // Loads EventsSubscriber Assembly and create EventsSubscriber.EventsCatcher
            Instance = Activator.CreateInstance(
                CustomDomain,
                "EventsSubscriber, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
                "EventsSubscriber.EventsCatcher").Unwrap();
        }
    }

}

Note:
We need to ensure that the EventsSubscriber.dll and EventsPublisher.dll are present in the same folder as CrossDomainEvents.exe. It can be done using XCOPY command in Publisher and Subscriber projects to paste DLL in CrossDomainEvents Project Output Directory.