Question

We have an MVC UI that will generically draw itself based on data types it gets from the WebAPI. On App start of the ui I call into my WebApi to pull the "LeadTypes" down in a list of Assemblies List using binary formatter to serialize and deserialize. Problem is, when the references are removed from the UI, the deserializer blows up saying it cannot find InstallmentLoan, version 1.0.0.0 or one of its dependencies. Well there are no dependencies other than system, these are just basic models with custom data annotations, etc. The goal is to not have any of our types referenced on the UI.

Error "Could not load file or assembly 'LeadGenFramework.Entity.LeadType.InstallmentLoan, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified."

There has to be a clever way to use and reference in memory assemblies without having the physical file. What I don't understand is why is it looking for a file if I have it in memory?

Any pointers would be great!

Here is the FusionLog:

=== Pre-bind state information === LOG: DisplayName = LeadGenFramework.Entity.LeadType.InstallmentLoan, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null (Fully-specified) LOG: Appbase = file:///E:/MainTrunk2/LeadGenFramework-Copy/trunk/LeadGenFramwork.Web.Api.RestClient.Test/bin/Debug LOG: Initial PrivatePath = NULL

Calling assembly : (Unknown)

LOG: This bind starts in default load context. LOG: Using application configuration file: C:\Users\charbaugh\AppData\Local\Temp\tmp8271.tmp LOG: Using host configuration file: LOG: Using machine configuration file from C:\Windows\Microsoft.NET\Framework\v4.0.30319\config\machine.config. LOG: Policy not being applied to reference at this time (private, custom, partial, or location-based assembly bind). LOG: Attempting download of new URL file:///E:/MainTrunk2/LeadGenFramework-Copy/trunk/LeadGenFramwork.Web.Api.RestClient.Test/bin/Debug/LeadGenFramework.Entity.LeadType.InstallmentLoan.DLL. LOG: Attempting download of new URL file:///E:/MainTrunk2/LeadGenFramework-Copy/trunk/LeadGenFramwork.Web.Api.RestClient.Test/bin/Debug/LeadGenFramework.Entity.LeadType.InstallmentLoan/LeadGenFramework.Entity.LeadType.InstallmentLoan.DLL. LOG: Attempting download of new URL file:///E:/MainTrunk2/LeadGenFramework-Copy/trunk/LeadGenFramwork.Web.Api.RestClient.Test/bin/Debug/LeadGenFramework.Entity.LeadType.InstallmentLoan.EXE. LOG: Attempting download of new URL file:///E:/MainTrunk2/LeadGenFramework-Copy/trunk/LeadGenFramwork.Web.Api.RestClient.Test/bin/Debug/LeadGenFramework.Entity.LeadType.InstallmentLoan/LeadGenFramework.Entity.LeadType.InstallmentLoan.EXE.

Was it helpful?

Solution 2

Ok, I figured this out. I was using reflections to get the assembly and then serializing the Assembly type. Assembly only provides property information for the physical file assembly and it does not contain an entire graph of the file. I hope this helps someone, lesson learned!

So I just used this on the controller and then the revers on the client.

    public MemoryStream Get()
    {
        List<MemoryStream> leadTypeAssemblyStreams = new List<MemoryStream>();

        foreach (string dllAssembly in _leadTypeNamespaces)
        {
            using (MemoryStream ms = new MemoryStream())
            {
                string filename = dllAssembly;

                if (!filename.Contains(".dll"))
                    filename = filename + ".dll";

                using (FileStream file = new FileStream(AssemblyDirectory + "\\" + filename, FileMode.Open, FileAccess.Read))
                {
                    byte[] bytes = new byte[file.Length];
                    file.Read(bytes, 0, (int)file.Length);
                    ms.Write(bytes, 0, (int)file.Length);
                    file.Close();

                    leadTypeAssemblyStreams.Add(ms);
                    ms.Close();
                }
            }    
        }

        return new MemoryStream(SerializeObj(leadTypeAssemblyStreams));
    }

    internal static string AssemblyDirectory
    {
        get
        {
            string codeBase = Assembly.GetExecutingAssembly().CodeBase;
            UriBuilder uri = new UriBuilder(codeBase);
            string path = Uri.UnescapeDataString(uri.Path);
            return Path.GetDirectoryName(path);
        }
    }


    internal static byte[] SerializeObj(object obj)
    {
        using (MemoryStream stream = new MemoryStream())
        {
            BinaryFormatter formatter = new BinaryFormatter();

            formatter.Serialize(stream, obj);

            byte[] bytes = stream.ToArray();
            stream.Close();

            return bytes;
        }
    }

Now, I can just call the GetDeserializedFileStreamPaths(stream) and it will return a physical path to the files that were streamed from the WebApi. Here is my WebApiClient class the UI will reference.

public class LFGWebApiClientHelpers
{
    public List<Type> GetLeadTypes(Stream stream)
    {
        List<string> assembliesFilePath = GetDeserializedFileStreamPaths(stream);
        List<Assembly> assemblies = new List<Assembly>();

        foreach (string filePath in assembliesFilePath)
        {
            assemblies.Add(Assembly.LoadFile(filePath));
        }

        List<Type> leadTypes = new List<Type>();

        foreach (Assembly asm in assemblies)
        {
            Type leadType = asm.GetTypes().Where(x => x.GetCustomAttributes(typeof(WebApiExportedTypeAttribute), false).Length > 0).FirstOrDefault(x => x.Name.Contains("Lead"));
            leadTypes.Add(leadType);
        }

        return leadTypes;
    }

    private static List<string> GetDeserializedFileStreamPaths(Stream stream)
    {
        try
        {
            List<string> assembliesPath = new List<string>();
            IFormatter formatter = new BinaryFormatter();
            List<MemoryStream> leadTypeAssemblyStreams = formatter.Deserialize(stream) as List<MemoryStream>;

            int fileNumber = 1;

            string leadTypeAsseblyDirectory = AssemblyDirectory.Replace("bin", "DownloadedLeadTypeAssemblies");

            if (!Directory.Exists(leadTypeAsseblyDirectory))
                Directory.CreateDirectory(leadTypeAsseblyDirectory);
            try
            {
                if (leadTypeAssemblyStreams == null)
                    throw new Exception("Lead type assembly stream cannot be null.");

                    foreach (MemoryStream leadTypeAssemblyStream in leadTypeAssemblyStreams)
                    {
                        string localFileLocation = leadTypeAsseblyDirectory +
                                                   string.Format("\\LeadType{0}.dll", fileNumber);

                        //add path for return type
                        assembliesPath.Add(localFileLocation);
                        using (
                            FileStream file = new FileStream(localFileLocation, FileMode.Create, FileAccess.Write))
                        {
                            leadTypeAssemblyStream.WriteTo(file);
                            leadTypeAssemblyStream.Close();
                        }

                        fileNumber++;
                    }
            }
            catch (Exception ex)
            {
                throw new LGFException().SetMessage(ex);
            }

            stream.Close();

            return assembliesPath;
        }
        catch (Exception ex)
        {
            throw new LGFException().SetMessage(ex, "There was a problems during the deserializtion of LeadTypes.");
        }
    }

    private static string AssemblyDirectory
    {
        get
        {
            string codeBase = Assembly.GetExecutingAssembly().CodeBase;
            UriBuilder uri = new UriBuilder(codeBase);
            string path = Uri.UnescapeDataString(uri.Path);
            return Path.GetDirectoryName(path);
        }
    }
}

OTHER TIPS

You will have to get the assemblies from somewhere, for that you will need to stream the assembly content and call assembly.load(byte[] rawAssembly).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top