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:
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.
This contains the usercontrol and nothing else.
<%@ 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.
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.
Here is where the magic happens
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
This one has this control:
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
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.