Overcoming lack of AppDomain class in PCL library with Xamarin

Hi Community,

If you usually wear two hats (architect and  developer) like I do, you might find this blog post useful. My current engagement requires me to have the precision of a surgeon, because anything misplaced might break the functionality of this Xamarin Android application. My customer’s requirement was “simple” being able to extend existing functionality and stabilize the solution as it stands now. I do not know who architected or built it in the first place, but that is irrelevant at this moment in time. The solution has one Xamarin Android (Xamarin Forms) and a common library that is PCL, and even when the application has a service locator and dependency injection the way it was originally built does not allow me to pass certain types to the PCL side of things. Let’s remember, PCL does not support all the features in .NET because it is agnostic of the platform, so it can run and behave the same on iOS, Android, Windows Phone, etc. Since I had to consume some functionality in .NET project (Xamarin) in PCL it was very likely that functionality was not present.  For those scenarios, I rely on the AppDomain to store code that must be available to the overall solution, in the AppDomain (in .NET) I can easily store an object or just a IntPtr and use Marshal class to consume it from somewhere else, this was not the case however. In PCL land, AppDomain is not serializable that’s why I had to come up with this approach.

PCL does not support AppDomain nor has a Marshal class, so from that standpoint I was limited. In order to workaround it this is what I had to do.

1-. In the Xamarin Android code that has access to AppDomain, we store the object that is dependent by PCL project. We implement and expose our required object as a singleton.

///////////////////////////////////////////////

// Code that stores information in AppDomain //

////////////////////////////////////////////// 


// Due to lack of AppDomain in PCL, pinning ITransactionalDataService instance to then retrieve it from PCl library

if (AppDomain.CurrentDomain.GetData(Constants.TransactionalDataService) == null) {

    var tranDb = TinyIoC.TinyIoCContainer.Current.Resolve<ITransactionalDataService>();

    AppDomain.CurrentDomain.SetData(Constants.TransactionalDataService, tranDb);

    IOC.AppDomainWrapper.PinObjectInAppDomain(AppDomain.CurrentDomain.GetData(Constants.TransactionalDataService));

}



///////////////////////////

//AppDomainWrapper class //

//////////////////////////


using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;


namespace MyNamespace {

    /// <summary>

    /// 

    /// </summary>

    public class AppDomainWrapper {

        /// <summary>

        /// The _ application domain pinned object

        /// </summary>

        private static object _appDomainPinnedObject;


        /// <summary>

        /// The _lock object

        /// </summary>

        private static readonly object _lockObj = new object();



        /// <summary>

        /// Pins the object in application domain.

        /// </summary>

        /// <param name="objToPin">The object to pin.</param>

        public static void PinObjectInAppDomain(object objToPin) {

            lock (_lockObj) {

                if (_appDomainPinnedObject == null)

                    _appDomainPinnedObject = objToPin;

            }

        }


        /// <summary>

        /// Gets the pinned object from application domain.

        /// </summary>

        /// <typeparam name="T"></typeparam>

        /// <returns></returns>

        public static T GetPinnedObjectFromAppDomain<T>() where T : class {

            var retval = default(T);


            if (_appDomainPinnedObject != null)

                retval = _appDomainPinnedObject as T;


            return retval;

        }


    }

}

 

2-. In the PCL project we then retrieve for use

private static void SafeExecuteWrapper(MetroLog.ILogger logger, string message, bool storeInDb = true, Exception ex = null, LoggerMethod method = LoggerMethod.Info, params object[] ps) {

    if (!string.IsNullOrEmpty(message) && logger != null) {

        try {

            if (storeInDb) {

                var formattedMsg = message;

                var appDomain = AppDomainWrapper.GetPinnedObjectFromAppDomain<ITransactionalDataService>();


                if (ps != null)

                    formattedMsg = string.Format(message, ps);


                appDomain.Insert(new Log() { Message = formattedMsg, SerializedException = ex?.SerializeAsString() });

            } else {

                if (method == LoggerMethod.Info)

                    logger.Info(message, ex);

                else if (method == LoggerMethod.Error)

                    logger.Error(message, ex);

            }

        } catch {

            // Safe to swallow exception    

        }

    }

}

 

Now, the object that was required in PCL library can be retrieved from AppDomain. I would also like to mention that we should not store too much stuff in AppDomain. In this case, I had a requirement and was the only option available. If code would have been written better, none of all this would make sense.

 

Regards,

 

Angel

Leave a Reply

Your email address will not be published. Required fields are marked *