Assembly loaded by byte array gives problems on ascx compilation (referencedAssemblies cannot contain null)

StackOverflow https://stackoverflow.com/questions/17239618

Question

I have a problem that is a bit complicated to explain (otherwise I wouldn't have problems with it :) ), but I'll give it a try.

I have an asp.net webapplication and I'm trying to load user controls at runtime from an other project without the original website having a reference to the project/dll containing the usercontrol.

When I know the path of all the needed files (dll etc .. I can make it work) especially for the assembly.loadfile(Path). But now I'm using a virtualpathProvider wich can only provide me with a stream. so I can't use assembly.loadfile(path) anymore, but I have to use Assembly.load(byte[]) . The problem is, when switching to assembly.load(byte[]) i can't compile the ascx files anymore. It gives me this exception:

The 'ReferencedAssemblies' property cannot contain null or empty strings. Parameter name: options

The only difference between the loaded assemblies is that the property Location gives an error. I assume that the location of the assemblies is passed to the builder.

Is there anybody who could provide any idea's on how to solve this? (Other than: write the assembly to a temp folder on the disk)

I was thinking on my own buildprovider or something in this direction, but I'm afraid that I might run in to the same issues.

This seems like the same issue eventually: Supply Assembly to CompilerParameters ReferencedAssemblies from memory and not disk?

Now for a better understanding, and possible helping other people out: the full stacktrace and full source:

full stacktrace

at Microsoft.VisualBasic.VBCodeGenerator.CmdArgsFromParameters(CompilerParameters options)
at Microsoft.VisualBasic.VBCodeGenerator.FromFileBatch(CompilerParameters options, String[] fileNames)
at System.CodeDom.Compiler.CodeCompiler.System.CodeDom.Compiler.ICodeCompiler.CompileAssemblyFromFileBatch(CompilerParameters options, String[] fileNames)
at System.Web.Compilation.AssemblyBuilder.Compile()
at System.Web.Compilation.BuildProvidersCompiler.PerformBuild()
at System.Web.Compilation.BuildManager.CompileWebFile(VirtualPath virtualPath)
at System.Web.Compilation.BuildManager.GetVPathBuildResultInternal(VirtualPath virtualPath, Boolean noBuild, Boolean allowCrossApp, Boolean allowBuildInPrecompile, Boolean throwIfNotFound, Boolean ensureIsUpToDate)
at System.Web.Compilation.BuildManager.GetVPathBuildResultWithNoAssert(HttpContext context, VirtualPath virtualPath, Boolean noBuild, Boolean allowCrossApp, Boolean allowBuildInPrecompile, Boolean throwIfNotFound, Boolean ensureIsUpToDate)
at System.Web.Compilation.BuildManager.GetVirtualPathObjectFactory(VirtualPath virtualPath, HttpContext context, Boolean allowCrossApp, Boolean throwIfNotFound)
at System.Web.Compilation.BuildManager.GetCompiledType(VirtualPath virtualPath)
at WebApplication1._Default.CompileAddControl(String path) in E:\Google Drive\Lab\DLLLoading\WebApplication1\Default.aspx.vb:line 29

I have 2 webapplications, a normal one, and one with only ascx(usercontrol) files in it.

Webapplication2:

This contains the usercontrol and nothing else.

ShowTime.ascx

<%@ Control 
    Language="vb" 
    AutoEventWireup="false" 
    CodeBehind="Showtime.ascx.vb" 
    Inherits="WebApplication2.Showtime,WebApplication2"
%>
<asp:Label ID="lblTime" runat="server"></asp:Label>

Here I added the assembly name to the Inherits value => otherwise the application won't understand that is has to look in a different assembly for the code.

ShowTime.ascx.vb

Public Class Showtime
    Inherits System.Web.UI.UserControl
    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        lblTime.Text = "Hello world from external :" & Now.ToShortDateString & " - " & Now.ToShortTimeString
    End Sub
End Class

That's all that there is needed for this Webapplication.

WebApplication1:

Here is where the magic happens

Global.asax

Imports System.Reflection
Imports System.Web.Hosting
Imports System.IO

Public Class Global_asax
    Inherits System.Web.HttpApplication

    Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
        AddHandler AppDomain.CurrentDomain.AssemblyResolve, AddressOf Global_asax.ResolveEventHandler
    End Sub

    Public Shared Function ResolveEventHandler(sender As Object, args As System.ResolveEventArgs) As Assembly
        Try
        If args.Name.StartsWith("WebApplication2") Then
            Dim _ret As Assembly

            ''START OF NOT WORKING CODE
            ''To make this path work,  I added the other application with ascx's as a virtual directory (~/apps/HelloWorld/) in IIS..  (make sure to NOT have a web.config in webapplication2)
            Dim _vf As VirtualFile = HostingEnvironment.VirtualPathProvider.GetFile("~/apps/HelloWorld/bin/WebApplication2.dll")
            'copy to memorystream so i can use the very handy toArray function (speed of coding)
            Using _ms As New MemoryStream, _s As Stream = _vf.Open()
                _s.CopyTo(_ms)
                _ret = Assembly.Load(_ms.ToArray)
            End Using
            ''END OF NOT WORKING CODE

            ''START OF WORKING CODE
            'Dim _ret As Assembly = Assembly.LoadFile(HostingEnvironment.MapPath("~/apps/HelloWorld/bin/WebApplication2.dll"))
            ''START OF WORKING CODE
            Return _ret
        End If
        Return Nothing
    Catch ex As Exception
        'this is a test project so debug.writeline will do.. 
        Debug.WriteLine(ex.ToString)
        Return Nothing
    End Try
End Function

Default.aspx

This one has this control:

Default.aspx.vb

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
    Try
        CompileAddControl("~/apps/HelloWorld/Showtime.ascx")
    Catch ex As Exception
        Debug.WriteLine(ex.ToString)
    End Try
End Sub

Private Shared m_dict As New Dictionary(Of String, Type)

Public Sub CompileAddControl(path As String)
    Try
        Dim _t As Type = Nothing
        If Not m_dict.TryGetValue(path, _t) Then
            _t = BuildManager.GetCompiledType(path)
            m_dict.Add(path, _t)
        End If

        Dim _o As Control = DirectCast(BuildManager.CreateInstanceFromVirtualPath(path, _t), Control)
        phControls.Controls.Add(_o)
    Catch ex As Exception
        phControls.Controls.Add(New LiteralControl(ex.Message))
    End Try
End Sub
Was it helpful?

Solution

The ascx compiler only supports referencing dll's by path. The reason for this is security. In the example in my question I had to save the needed dll to a temp folder on disk and load it from that temporary location afterwards. There is no other solution because of security.

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