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

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

Domanda

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
È stato utile?

Soluzione

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.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top