Friday, August 7, 2009

ASPX Web Method: Same Class, Different Domains

When you try to pass an object as a parameter to a Web Service. For simplicity sake, call this object, object A. At the same time you also have object A referenced from a local binary.

As you try to do the following: WebServiceX.RunWebMethod(A); it bombed. Well... as it turns A and A are different since they come from a different domain. The first A is a proxy object, created when you reference the webservice in Visual Studio, the other A is the actual object whose binary exists locally. C# being strongly typed throws an exception.

The only way to get around this restriction is to make deep copy of object A via. reflection, or... XML serialization and deserialization. In this example I'll cover the first method, via. reflection.

So now after implementing the following code snippet, you want to run the webservice this way: WebServiceX.RunWebMethod((AnotherDomain.A) A.CopyTo(AnotherDomain.A))


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace ReflectionHelpers
{
public static class DeepCopyUtils
{


        /// <summary>
        /// We can't use generic with copy to but this wrapper allows it to wrap the logic around in a way that'll allow for type casting.
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <typeparam name="TTarget"></typeparam>
        /// <param name="Source"></param>
        /// <param name="Target"></param>
        /// <returns></returns>
        public static TTarget CopyTo<TSource, TTarget>(this TSource Source, TTarget Target)
        {
            return (TTarget) Source.CopyTo_Base(Target);
        }

        /// <summary>
        /// Make a deep copy of source to target type of object.
        /// NOTE: Unfortunately the 2nd argument cannot be generic as that would cause problems when creating linq anonymous type.
        /// </summary>
        /// <param name="Source">The source</param>
        /// <param name="Target">The target object.</param>
        /// <returns>An object of type Target.</returns>
        private static Object CopyTo_Base(this Object Source, Object Target)
        {
            //The requisite null case scenerio. 
            if (Source == null)
                return null; //If the source is null, then target will have a null value also, simple.
            else if (Target == null && Source != null)
                Target = Source; //If the target is null, but source is not null, then we want to  return the shallow copy of the target, by letting this function falls through.


            Type BType = Target.GetType();
            Type AType = Source.GetType();
            //Members or properties.
            MemberInfo[] BProperties = ((MemberInfo[])BType.GetProperties()).Union((MemberInfo[])BType.GetFields()).ToArray();
            MemberInfo[] AProperties = ((MemberInfo[])AType.GetProperties()).Union((MemberInfo[])AType.GetFields()).ToArray();
            
            //Creates a new target object.
            Object Result;
            if (Target.GetType().GetConstructor(new Type[] { }) != null)
                Result = Target.GetType().GetConstructor(new Type[] { }).Invoke(new Object[0]);            //The requisite null case scenerio. 
            else //No default constructor, it may be a semi-primitive eg. string or array, so we can do nothing more
                return Source.Clone();
            

            //Find all similar property names between Source and Target, put them to a list, along with the content of the Source.
            var x = from b in BProperties
                    join a in AProperties 
                    on new{
                        Name = b.Name,
                        TypeName = b.GetMemberType().Name
                    }
                    equals
                    new {
                        Name = a.Name,
                        TypeName = a.GetMemberType().Name
                    }
                    select new
                    {                        
                        //The name of the property.
                        ID = a.Name,
                        //3 cases for content: 1) Array, 2) Struct Type and 3) Value Type.
                        Content = a.GetType().IsArray ? ((System.Array)a.GetValue(Source)).CopyTo((System.Array)b.GetValue(Target)) :
                                    !a.GetType().IsValueType ? a.GetValue(Source).CopyTo(b.GetValue(Target)) :
                                    a.GetValue(Source)
                                  
                    };


            Dictionary<string, object> MergeResult = new Dictionary<string, object>();

            foreach (var y in x)
            {
                MergeResult[y.ID] = y.Content;
            }

            //Copy if the same name exists in the dictionary as in the result.
            foreach (var z in ((MemberInfo[]) Result.GetType().GetProperties()).Union((MemberInfo[]) Result.GetType().GetFields()))
            {
                if (MergeResult.Keys.Contains(z.Name))
                {
                    if(z is PropertyInfo)
                        ((PropertyInfo) z).SetValue(Result, MergeResult[z.Name], null);
                    else if (z is FieldInfo)
                        ((FieldInfo)z).SetValue(Result, MergeResult[z.Name]);
                }
            }

            return Result;
        }
/// <summary> /// Retrieves the value of a member, if the member is of type property or field. /// NOTE: Only works for field and properties, all else returns null. /// </summary> /// <param name="Member"></param> /// <returns></returns> public static object GetValue(this MemberInfo Member, object ObjectInstance) { if (Member.MemberType == MemberTypes.Property) return ((PropertyInfo)Member).GetValue(ObjectInstance, new object[] { }); else if (Member.MemberType == MemberTypes.Field) return ((FieldInfo)Member).GetValue(ObjectInstance); return null; } /// <summary> /// Return the property type of a field, properties or method (return type). /// eg. int a = 0; will return "int". /// </summary> /// <param name="Member">The name of the member to look for.</param> /// <returns>The type of the field/property or method.</returns> public static Type GetMemberType(this MemberInfo Member) { if (Member.MemberType == MemberTypes.Property) return ((PropertyInfo)Member).PropertyType; else if (Member.MemberType == MemberTypes.Field) return ((FieldInfo)Member).FieldType; else if (Member.MemberType == MemberTypes.Method) return ((MethodInfo)Member).ReturnType; return null; } /// <summary> /// Shallow copy one array to the next. /// </summary> /// <param name="Source"></param> /// <param name="Target"></param> /// <returns></returns> public static System.Array CopyTo(this System.Array Source, System.Array Target) { //The requisite null case scenerio. if (Source == null) return null; //If the source is null, then target will have a null value also, simple. else if (Target == null && Source != null) Target = Source; //If the target is null, but source is not null, then we want to return the shallow copy of the target, by letting this function falls through. LinkedList<object> CopyResult = new LinkedList<object>(); for (int i = 0; i < Min(Source.GetLength(0), Target.GetLength(0)); i++) { CopyResult.AddLast(Source.GetValue(i).CopyTo(Target.GetValue(i))); } return (System.Array)CopyResult.ToArray(); } public static int Min(int a, int b) { if (a <= b) return a; else return b; } } }

No comments:

Post a Comment