Hangfire + Crystal Reports email? - c#

I'm trying to make it so that we can generate certain reports daily and email them to a bunch of people in a list.
I've tested out Hangfire for recurring jobs and it works well. So that is not an issue. But I'm trying to create a report from my existing Crystal Report file (.rpt). Basically I want to make it so that when this job gets executed, the code would create the report, save it to the disk in a specified path as a PDF, and then I can email it to people as an attachment. So there is no need to be able to see the report on a web page. The idea is to literally just generate the report in the code behind, save it as a PDF, and email it from the code behind after it is saved.
The issue I'm having has to do with the actual generating and saving of the crystal report. Btw, I'm generating an excel file in the test but I'd change it to PDF for the actual report. This is what I have so far for generating the report:
string path = #"Save folder relative-path";
//"report" is declared at the class level and instantiated below.
report = new ReportDocument();
report.SetDatabaseLogon(ConfigurationManager.AppSettings["Username"], ConfigurationManager.AppSettings["Password"]);
report.Load(Server.MapPath("Relative path to the report"));
report.SetDataSource(GetDataSet()); //This gets the dataset filled with data for the report
try
{
ExportOptions options = new ExportOptions();
DiskFileDestinationOptions diskFileOptions = new DiskFileDestinationOptions();
ExcelFormatOptions excelOptions = new ExcelFormatOptions();
diskFileOptions.DiskFileName = path + "Test Report.xls";
options.ExportDestinationType = ExportDestinationType.DiskFile;
options.ExportFormatType = ExportFormatType.Excel;
options.ExportDestinationOptions = diskFileOptions;
options.ExportFormatOptions = excelOptions;
report.Export();
/*
This is where I would call a method to email the report to people
*/
}
catch (Exception ex)
{
Console.WriteLine("Error generating report: " + ex.Message);
}
This code is in a method which is being called at Application_Start in the global.asax file of the web application. When I run the application, the job fails and throws this error when I look under the failed jobs in the Hangfire dashboard even though I know the path in my code is correct:
System.IO.FileNotFoundException Could not load file or assembly
'App_global.asax.twm32qri, Version=0.0.0.0, Culture=neutral,
PublicKeyToken=null' or one of its dependencies. The system cannot
find the file specified.
System.IO.FileNotFoundException: Could not load file or assembly
'App_global.asax.twm32qri, Version=0.0.0.0, Culture=neutral,
PublicKeyToken=null' or one of its dependencies. The system cannot
find the file specified. File name: 'App_global.asax.twm32qri,
Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' at
System.RuntimeTypeHandle.GetTypeByName(String name, Boolean
throwOnError, Boolean ignoreCase, Boolean reflectionOnly,
StackCrawlMarkHandle stackMark, IntPtr pPrivHostBinder, Boolean
loadTypeFromPartialName, ObjectHandleOnStack type) at
System.RuntimeTypeHandle.GetTypeByName(String name, Boolean
throwOnError, Boolean ignoreCase, Boolean reflectionOnly,
StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean
loadTypeFromPartialName) at System.Type.GetType(String typeName,
Boolean throwOnError, Boolean ignoreCase) at
Hangfire.Storage.InvocationData.Deserialize()
WRN: Assembly binding logging is turned OFF. To enable assembly bind
failure logging, set the registry value
[HKLM\Software\Microsoft\Fusion!EnableLog] (DWORD) to 1. Note: There
is some performance penalty associated with assembly bind failure
logging. To turn this feature off, remove the registry value
[HKLM\Software\Microsoft\Fusion!EnableLog].
EDIT:
I have another error I'm getting too. This one has something to do
with loading the report file.
Failed An exception occurred during performance of the job.
CrystalDecisions.CrystalReports.Engine.LoadSaveReportException
Invalid report file path.
CrystalDecisions.CrystalReports.Engine.LoadSaveReportException:
Invalid report file path. at
CrystalDecisions.CrystalReports.Engine.ExceptionThrower.ThrowEngineException(String
messageID, EngineExceptionErrorID id) at
CrystalDecisions.CrystalReports.Engine.ReportDocument.Load(String
filename, OpenReportMethod openMethod, Int16 parentJob) at
CrystalDecisions.CrystalReports.Engine.ReportDocument.EnsureLoadReport()
at
CrystalDecisions.CrystalReports.Engine.ReportDocument.SetDatabaseLogon(String
user, String password) at Intranet.Global.GenerateReport() in
path\Global.asax.cs:line 98

Figured out the issue. I apparently needed to use a CrystalReportViewer object and set the ReportDocument object as its source. The CrystalReportViewer class is in the CrystalDecisions.Web namespace.
using (ReportDocument report = new ReportDocument())
{
using (CrystalReportViewer viewer = new CrystalReportViewer())
{
string path = System.Web.Hosting.HostingEnvironment.MapPath(#"Destination path here");
report.Load(System.Web.Hosting.HostingEnvironment.MapPath(#"Path to .rpt file here"));
report.SetDatabaseLogon(ConfigurationManager.AppSettings["Username"], ConfigurationManager.AppSettings["Password"]);
string file = path + "TestReport.xls";
//These two lines below are important. The report won't generate without them.
viewer.ReportSource = report;
viewer.RefreshReport();
//Just deleting the file if it exists.
if (File.Exists(file))
File.Delete(file);
report.ExportToDisk(ExportFormatType.Excel, diskFileOptions.DiskFileName);
}
}

Related

C# appDomain failed to work for "CodeAccessPermission" reason, why?

I'm trying C# appdomain on win10 using vs2017, I've got this quick example. I've a directory called c:\git, I can create files under this directory with C# app, but when I tried app domain, it throws exception, my code as below:
class UseAppDomain
{
public static void Test()
{
var perm = new PermissionSet(PermissionState.None);
perm.AddPermission(
new SecurityPermission(SecurityPermissionFlag.Execution));
perm.AddPermission(
new FileIOPermission(FileIOPermissionAccess.NoAccess, #"c:\"));
var setup = new AppDomainSetup();
setup.ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
AppDomain secureDomain = AppDomain.CreateDomain("secure", null, setup, perm);
ThirdParty third = new ThirdParty();
Type thirdParty = typeof(ThirdParty);
secureDomain.
CreateInstanceAndUnwrap(thirdParty.Assembly.FullName,
thirdParty.FullName); //exception!!!!!!!!!!
AppDomain.Unload(secureDomain);
}
}
[Serializable]
class ThirdParty
{
public ThirdParty()
{
Console.WriteLine("3p loadling");
System.IO.File.Create(#"c:\git\test.txt");//Try to create file failed!
}
}
The exception message is:
Unhandled Exception: System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.Security.SecurityException: Request for the permission of type 'System.Security.Permissions.FileIOPermission, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' failed.
at System.Security.CodeAccessSecurityEngine.Check(Object demand, StackCrawlMark& stackMark, Boolean isPermSet)
at System.Security.CodeAccessSecurityEngine.Check(CodeAccessPermission cap, StackCrawlMark& stackMark)
at System.Security.CodeAccessPermission.Demand()
... ...
I don't quite get what problem my program has, how to fix this issue?
Thanks.
If you want to create files from a partially trusted domain you need to use FileIOPermissionAccess.Write instead. Or FileIOPermissionAccess.AllAccess if you want to allow also reading and directory content discovery.
Side note:
You use the CreateInstanceAndUnwrap for a simple serializable class, which is not derived from MarshalByRefObject. Its effect is that the class will be serialized in the created domain and a copy will be deserialized in the main domain but as you omit the return value it will be dropped anyway.
So either do not unwrap the created object or derive it from the MarshalByRefObject class so its public members can be accessed from the main domain via remoting.

Configuration.Save method throws Unauthorized access error when the config file is placed in redirected folder

I have implemented a winform application. I store the user settings for the application in the config file using the Configuration class.I store the exe along with the config file under the folder
C:\Users\\AppData\Local
This works fine in normal case, but I am facing problem in cases where user has redirected Appdata folder to some server address.In that case I get the exception:
Attempted to perform an unauthorized operation.
I found some similar questions here but none of them have any satisfactory answers.I tried to delete the config file before running the Configuration.Save command but that gives the exception:
The configuration file has been changed by another program.
So, how do I solve this problem.
Here is my code to Update the config file:
string exePath = Path.Combine(Path.GetDirectoryName(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)), #"Local\<folder name>\<exe file name>");
Configuration configFile = ConfigurationManager.OpenExeConfiguration(exePath);
if (configFile.AppSettings.Settings[key] != null)
{
configFile.AppSettings.Settings.Remove(key);
}
if (param)
{
configFile.AppSettings.Settings.Add(key, value);
}
configFile.Save(ConfigurationSaveMode.Modified);
Here is the stack trace for the exception:
at System.Security.AccessControl.Win32.SetSecurityInfo(ResourceType
type, String name, SafeHandle handle, SecurityInfos securityInformation, SecurityIdentifier owner, SecurityIdentifier group, GenericAcl sacl, GenericAcl dacl)
at System.Security.AccessControl.NativeObjectSecurity.Persist(String
name, SafeHandle handle, AccessControlSections includeSections, Object
exceptionContext)
at System.Security.AccessControl.NativeObjectSecurity.Persist(String
name, AccessControlSections includeSections, Object exceptionContext)
at System.Security.AccessControl.NativeObjectSecurity.Persist(String
name, AccessControlSections includeSections)
at System.Security.AccessControl.FileSystemSecurity.Persist(String
fullPath)
at System.IO.File.SetAccessControl(String path, FileSecurity fileSecurity)
at System.Configuration.Internal.WriteFileContext.DuplicateTemplateAttributes(String source, String destination)
at System.Configuration.Internal.WriteFileContext.DuplicateFileAttributes(String source, String destination)
at System.Configuration.Internal.WriteFileContext.Complete(String
filename, Boolean success)
at System.Configuration.Internal.InternalConfigHost.StaticWriteCompleted(String
streamName, Boolean success, Object writeContext, Boolean assertPermissions) at System.Configuration.Internal.InternalConfigHost.System.Configuration.Internal.IInternalConfigHost.WriteCompleted(String streamName, Boolean success, Object writeContext, Boolean assertPermissions)
at System.Configuration.Internal.InternalConfigHost.System.Configuration.Internal.IInternalConfigHost.WriteCompleted(String streamName, Boolean success, Object writeContext)
at
System.Configuration.Internal.DelegatingConfigHost.WriteCompleted(String streamName, Boolean success, Object writeContext)
at System.Configuration.UpdateConfigHost.WriteCompleted(String
streamName, Boolean success, Object writeContext)
at System.Configuration.MgmtConfigurationRecord.SaveAs(String filename,
ConfigurationSaveMode saveMode, Boolean forceUpdateAll)
at System.Configuration.Configuration.SaveAsImpl(String filename, ConfigurationSaveMode saveMode, Boolean forceSaveAll)
at UtilityClasses.ConfigurationHandler.UpdateConfigFile(String key, String value, Boolean param)
I had a very similar expierience with changing a configuration file stored on a network share using ConfigurationManager.
It truns out that the Configuration.Save initially creates a temp file in the same location with the desired access set to Write DAC (Write Directory Access Control).
It even says so in the documentation:
When 'Creator Owner' is listed in the ACL (Access Control List) of the
directory containing the configuration file, the current user of Save
becomes the new owner of the file and inherits the permissions granted
to 'Creator Owner'. This results in an elevation of privileges for the
current user and a removal of privileges for the previous owner.
The only workaround I could find in my environment was to copy the config to local directory, alter the file locally and then copy it back.
You have two ways to solve this problem:
1. Share network folder so that it does not autorization for users of the same network.
2. Use user credentials to pass authorization and connect to network path.
Workarounds
Check for a specific user's attribute on shared folder. it may have missing WriteExtended attribute.
Check creator of shared folder which inherits all permissions from creator.
Check if you can change configuration of your shared folder to non c: drive folder.

Using reflection to instantiate a class from an external assembly

I am currently trying to develop a method of running test classes in external projects programmatically using reflection. Here is a simplified chunk of code that should showcase my problem.
string pathToDLL = #"C:\Path\To\Test\Project\UnitTests.dll";
IEnumerable<Type> testClasses = assembly.GetExportedTypes();
Type testClass = testClasses.First();
object testClassInstance = assembly.CreateInstance(testClass.FullName);
This code throws the following exception:
'assembly.CreateInstance(testClass.FullName)' threw an exception of type 'System.Reflection.TargetInvocationException'
Data: {System.Collections.ListDictionaryInternal}
HResult: -2146232828
HelpLink: null
InnerException: {System.IO.FileNotFoundException: Could not load file or assembly 'Project.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.
File name: 'Project.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
at Project.UnitTests.TestClass..ctor()}
Message: "Exception has been thrown by the target of an invocation."
Source: "System.Private.CoreLib"
StackTrace: " at System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor, Boolean& bNeedSecurityCheck)\r\n at System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark)\r\n at System.Activator.CreateInstance(Type type, Boolean nonPublic)\r\n at System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes, StackCrawlMark& stackMark)\r\n at System.Activator.CreateInstance(Type type, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes)\r\n at System.Reflection.Assembly.CreateInstance(String typeName, Boolean ignoreCase, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes)\r\n at System.Reflection.Assembly.CreateInstance(String typeName)"
In the stack trace it states that it "Could not load file or assembly 'Project.Core...'".
This project is one that the target DLL references directly (one that it tests). Does anyone know why this won't be able to pick up these DLLs automatically?
I've investigated ways of solving this problem:
It could be the way that the dlls have been compiled - this can be changed as I am in control of this - which is currently by running dotnet build */*/project.json at solution level. This successfully compiles everything, and all of the relevant DLLs seem to be populated in the bin folder. I've also investigated whether or not changing to dotnet publish or dotnet build */*/project.json --configuration Release though neither seem to have helped.
I've also looked into using different methods of compilation like Activator.CreateInstance again no dice.
I don't seem to see a way to load multiple DLLs into the same Assembly class so that I can control the references. Since AppDomains have been removed from .NET Core this doesn't look like it is possible, though I may be mistaken/looking in the wrong area.
If what I'm doing doesn't seem like it will be possible, does anyone know if this kind of functionality can be achieved using a different method? I.e. Roslyn?
I just thought that I would update this question with the solution that I managed to find, just in case someone else was having the same problem as I was. Though I would like to thank #Emrah Süngü for pointing me in the right direction.
Emrah drew my attention to the fact that I needed to import the dependencies of the DLL that I wanted to load in order to invoke the classes stored within it. One way to do this is to extend your app.config in order to import those dependencies - however I wanted to do this at runtime (with projects that I didn't know I was going to run prior starting the program) so I needed to look for another solution.
If you aren't using .NET Core this is relatively simple since AppDomains can be used to load all of the dependencies and execute your code. However, since this has been removed from .NET Core I needed to find another solution that would be compatible.
I toyed with the idea of running a separate process (or Powershell), and changing the working directory so that the process was running in the directory that stored all of the dependencies it needed. However, I couldn't find a way of doing this that allowed me to react to the outcome of running the methods.
Later I investigated manipulating the AssemblyLoadContext class, but (at the time of writing) there is little to no documentation on how this class. I did find this answer which was able to helped significantly... https://stackoverflow.com/a/37896162/6012159
In order for it to work I did have to make a slight change, instead of creating a new AssemblyLoader every time (which would cause exceptions to be thrown when trying to invoke methods within the Assembly), I reused the AssemblyLoader each time (Which removed this problem).
public class AssemblyLoader : AssemblyLoadContext
{
private string folderPath;
public AssemblyLoader(string folderPath)
{
this.folderPath = folderPath;
}
protected override Assembly Load(AssemblyName assemblyName)
{
var deps = DependencyContext.Default;
var res = deps.CompileLibraries.Where(d => d.Name.Contains(assemblyName.Name)).ToList();
if (res.Count > 0)
{
return Assembly.Load(new AssemblyName(res.First().Name));
}
else
{
var apiApplicationFileInfo = new FileInfo($"{folderPath}{Path.DirectorySeparatorChar}{assemblyName.Name}.dll");
if (File.Exists(apiApplicationFileInfo.FullName))
{
return this.LoadFromAssemblyPath(apiApplicationFileInfo.FullName);
}
}
return Assembly.Load(assemblyName);
}
}
Which can be use to load assemblies like this:
string directory = #"C:\Path\To\Project\bin\Debug\netcoreapp1.0\publish\";
string pathToDLL = #"C:\Path\To\Project\bin\Debug\netcoreapp1.0\publish\project.dll";
AssemblyLoader al = new AssemblyLoader(directory);
Assembly assembly = al.LoadFromAssemblyPath(pathToDLL);
I am assuming that "UnitTests.dll" depends on (references) other dll(s) and your program does not know where to look for those referenced dll(s). You should (in fact have to) tell it to where to look for those dll(s) as well. By default is the same directory as your EXE. You can use app.config for telling where else to look. For Load() to succeed dependant dll(s) must be stored in your app's probing path.
That is the reason why you are getting an error.
Here you can find related article.
https://msdn.microsoft.com/en-us/library/823z9h8w.aspx

Deserialization in a CLR Function fails

I have written some routines for the phonetic conversion of a text in C#. These routines require a bunch aof defined rules for the conversion (search-string/replace-string). The idea was to store thes rules as an embedded ressource within the assembly and then read the rules from it. The starting point for the the deserialization is as follows
public static phonet42n.Core.Rules Deserialize(phonet42n.Core.Rules.Ressources ressource)
{
string ressourceName;
phonet42n.Core.Rules returnValue;
System.Xml.XmlReader reader;
System.Xml.Serialization.XmlSerializer xmlSerializer;
phonet42n.Core.SerializableRules serializeableRules;
returnValue = new phonet42n.Core.Rules();
switch (ressource)
{
case Ressources.German_01:
ressourceName = RESSOURCE_XML_GERMAN_01;
break;
case Ressources.German_02:
ressourceName = RESSOURCE_XML_GERMAN_02;
break;
default:
ressourceName = RESSOURCE_XML_GERMAN_01;
break;
}
using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(ressourceName))
{
using (reader = System.Xml.XmlReader.Create(stream))
{
xmlSerializer = new System.Xml.Serialization.XmlSerializer(typeof(phonet42n.Core.SerializableRules));
serializeableRules = (phonet42n.Core.SerializableRules)xmlSerializer.Deserialize(reader);
}
}
foreach (phonet42n.Core.Rule entry in serializeableRules.Rules)
{
if (entry.SearchString != null && entry.SearchString.Length > 0)
{
returnValue.Add(entry.Index, entry);
}
}
return returnValue;
}
The application works fine when executed in an regular executable.
When executing the registered function in SQL Server I get the following error:
SELECT [dbo].[Phonet42n]('mayer', 1)
produces...
Meldung 6522, Ebene 16, Status 1, Zeile 22
.NET Framework-Fehler beim Ausführen der benutzerdefinierten Routine oder des benutzerdefinierten Aggregats 'Phonet42n':
System.InvalidOperationException: Fehler im XML-Dokument (3,4). ---> System.MethodAccessException: Fehler beim Versuch der Methode "Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReader1.Read8_SerializableRules(Boolean, Boolean)", auf Methode "phonet42n.Core.Rule..ctor()" zuzugreifen.
System.MethodAccessException:
bei System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor, Boolean& bNeedSecurityCheck)
bei System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark)
bei System.Activator.CreateInstance(Type type, Boolean nonPublic)
bei System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes, StackCrawlMark& stackMark)
bei System.Activator.CreateInstance(Type type, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes)
bei System.Activator.CreateInstance(Type type, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture)
bei Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReader1.Read8_SerializableRules(Boolean isNullable, Boolean checkType)
bei Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReader1.Read19_Rules()
System.InvalidOperationException:
bei System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader, String encodingStyle, XmlDeserializationEvents events)
bei System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader)
bei phonet42n.Core.Rules.Deserialize(Ressources ressource)
bei phonet42n.Core.Rules..ctor(Ressources ressource, Characters characters)
bei phonet42n.Core.HashTable..ctor(Ressources ressource)
bei phonet42n.Core.Match..ctor(Re...
Any idea?
Whenever there are security exceptions, you can first try setting the Assembly to PERMISSION_SET = EXTERNAL_ACCESS, and if that doesn't work you can try UNSAFE. However, if one is attempting to dynamically load an Assembly, then that is supposed to be forbidden even for Assemblies marked as UNSAFE.
Since the issue here is of wanting to include a set of rules, that could probably be done in another Assembly. Then the main Assembly can reference the one containing the rules, and you just load the one containing the rules into SQL Server first. This would allow both Assemblies to remain marked as SAFE.
Of course, if there is no pressing need to keep the rules separate, then you could also just place them directly into a Collection in a Class.
I did an undo on evreything except the constructor part (private/public). With or without signing the assembly I get the following message (10327):
CREATE ASSEMBLY for assembly 'phonet42n.Core' failed because assembly
'phonet42n.Core' is not authorized for PERMISSION_SET =
EXTERNAL_ACCESS. The assembly is authorized when either of the
following is true: the database owner (DBO) has EXTERNAL ACCESS
ASSEMBLY permission and the database has the TRUSTWORTHY database
property on; or the assembly is signed with a certificate or an
asymmetric key that has a corresponding login with EXTERNAL ACCESS
ASSEMBLY permission.
Unfortunately I'm not familiar with signing an assembly: I just checked the checkbox in the property pages of the project properties:
Don't know whether additional steps are missing.
Anyhow, as suggested in the message I then tried to create an asymmetric key using the following statements:
USE master;
GO
IF EXISTS (SELECT * FROM [sys].[syslogins] WHERE [sid] = SUSER_SID('SQLCLRTestLogin'))
BEGIN
PRINT 'Dropping Login...'
DROP LOGIN [SQLCLRTestLogin]
PRINT 'End'
END
GO
IF EXISTS (SELECT * FROM [sys].[asymmetric_keys] WHERE [name] = 'SQLCLRTestKey')
BEGIN
PRINT 'Dropping Asymmetric Key...'
DROP ASYMMETRIC KEY [SQLCLRTestKey]
PRINT 'End'
END
GO
BEGIN
PRINT 'Creating Login...'
CREATE ASYMMETRIC KEY [SQLCLRTestKey] FROM EXECUTABLE FILE = 'D:\phonet42n.net\Core\bin\Debug\phonet42n.Core.dll'
PRINT 'End'
PRINT 'Creating Asymmetric Key...'
CREATE LOGIN [SQLCLRTestLogin] FROM ASYMMETRIC KEY [SQLCLRTestKey]
PRINT 'End'
PRINT 'Granting Access...'
GRANT EXTERNAL ACCESS ASSEMBLY TO [SQLCLRTestLogin];
PRINT 'End'
END
GO
In case of a signed assembly these statements succeed, whereas in case of an unsigned assembly I get the following Error message (15208):
The certificate, asymmetric key, or private key file does not exist or
has invalid format.
By the way.. don't know whether this matters. The target .NET Framework aof the assemly is 4.5. Finally... coming to the initial error message: Adwaenyth was right with the missing public default construtor. The message was so far misleading, as the missing constructor does not affect the registrion of the assembly. You get the message only after a successfull registration at runtime, when making a call for the function phonet42n.
I really don't know, whether this is the solution by design. And I cannot not tell whether I opend the door with that for any potential threads.
I just can emphasize the the Stairway series articles especially on on SQLCLR. Excellent! It helps to get a understanding how SQLCLR works and what it is good for.
And (really) finally: the overall outcome for me is, that I will extract the rules from the XML and put load them directly by populating a collection as suggested by srutzky. Will be probably much more performant.

Load Current Assembly into different AppDomain

I have created an AppDomain with a different base directory. However, I cannot seem to load the currently executing assembly into the other AppDomain without having a copy of the current executing assembly in the base directory. I've even tried to load it from the bytes.
I get no exception when I try to load, but when I try to use:
domain.DoCallBack(new CrossAppDomainDelegate(...
I get:
Could not load file or assembly ........... The system cannot find the file specified.
My code is as follows:
private static void SaveAssemblies(Assembly ass, List<byte[]> assemblyByteList)
{
AssemblyName[] assNames = ass.GetReferencedAssemblies();
foreach (AssemblyName assName in assNames)
{
Assembly referedAss = Assembly.Load(assName);
if (!referedAss.GlobalAssemblyCache)
{
SaveAssemblies(referedAss, assemblyByteList);
}
}
byte[] rawAssembly = File.ReadAllBytes(ass.Location);
assemblyByteList.Add(rawAssembly);
}
public static AppDomain CreateAppDomain(string dir, string name)
{
AppDomainSetup domainSetup = new AppDomainSetup();
domainSetup.ApplicationBase = dir;
domainSetup.ApplicationName = Path.GetFileName(dir);
domainSetup.PrivateBinPath = Path.Combine(dir, "Libs");
AppDomain domain = AppDomain.CreateDomain(name, null, domainSetup);
//Load system assemblies needed for the module
List<byte[]> assemblyByteList = new List<byte[]>();
SaveAssemblies(Assembly.GetExecutingAssembly(), assemblyByteList);
foreach (byte[] rawAssembly in assemblyByteList)
domain.Load(rawAssembly);
domain.DoCallBack(new CrossAppDomainDelegate(SetupLogging));
return domain;
}
Update:
It seems the assembly is loaded if i look in output i see this
'TaskExecuter.Terminal.vshost.exe' (Managed (v4.0.30319)): Loaded 'NLog'
'TaskExecuter.Terminal.vshost.exe' (Managed (v4.0.30319)): Loaded 'TaskExecuter', Symbols loaded.
but i still get the exception... i don't understand this
System.IO.FileNotFoundException was unhandled Message=Could not load
file or assembly 'TaskExecuter, Version=1.0.4244.31921,
Culture=neutral, PublicKeyToken=null' or one of its dependencies. The
system cannot find the file specified. Source=mscorlib
FileName=TaskExecuter, Version=1.0.4244.31921, Culture=neutral,
PublicKeyToken=null FusionLog==== Pre-bind state information ===
LOG: User = Peter-PC\Peter LOG: DisplayName = TaskExecuter,
Version=1.0.4244.31921, Culture=neutral, PublicKeyToken=null
(Fully-specified) LOG: Appbase =
file:///C:/ProgramData/TaskExecuter/TaskLib/uTorrentTasks LOG: Initial
PrivatePath = C:\ProgramData\TaskExecuter\TaskLib\uTorrentTasks\Libs
Calling assembly : (Unknown).
=== LOG: This bind starts in default load context. LOG: Using
application configuration file: d:\users\peter\documents\visual studio
2010\Projects\TaskExecuter\TaskExecuter.Terminal\bin\Release\TaskExecuter.Terminal.vshost.exe.Config
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:///C:/ProgramData/TaskExecuter/TaskLib/uTorrentTasks/TaskExecuter.DLL.
LOG: Attempting download of new URL
file:///C:/ProgramData/TaskExecuter/TaskLib/uTorrentTasks/TaskExecuter/TaskExecuter.DLL.
LOG: Attempting download of new URL
file:///C:/ProgramData/TaskExecuter/TaskLib/uTorrentTasks/Libs/TaskExecuter.DLL.
LOG: Attempting download of new URL
file:///C:/ProgramData/TaskExecuter/TaskLib/uTorrentTasks/Libs/TaskExecuter/TaskExecuter.DLL.
LOG: Attempting download of new URL
file:///C:/ProgramData/TaskExecuter/TaskLib/uTorrentTasks/TaskExecuter.EXE.
LOG: Attempting download of new URL
file:///C:/ProgramData/TaskExecuter/TaskLib/uTorrentTasks/TaskExecuter/TaskExecuter.EXE.
LOG: Attempting download of new URL
file:///C:/ProgramData/TaskExecuter/TaskLib/uTorrentTasks/Libs/TaskExecuter.EXE.
LOG: Attempting download of new URL
file:///C:/ProgramData/TaskExecuter/TaskLib/uTorrentTasks/Libs/TaskExecuter/TaskExecuter.EXE.
StackTrace:
at System.Reflection.RuntimeAssembly._nLoad(AssemblyName
fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly
locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound,
Boolean forIntrospection, Boolean suppressSecurityChecks)
at System.Reflection.RuntimeAssembly.nLoad(AssemblyName
fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly
locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound,
Boolean forIntrospection, Boolean suppressSecurityChecks)
at
System.Reflection.RuntimeAssembly.InternalLoadAssemblyName(AssemblyName
assemblyRef, Evidence assemblySecurity, StackCrawlMark& stackMark,
Boolean forIntrospection, Boolean suppressSecurityChecks)
at System.Reflection.RuntimeAssembly.InternalLoad(String
assemblyString, Evidence assemblySecurity, StackCrawlMark& stackMark,
Boolean forIntrospection)
at System.Reflection.Assembly.Load(String assemblyString)
at
System.Runtime.Serialization.FormatterServices.LoadAssemblyFromString(String
assemblyName)
at
System.Reflection.MemberInfoSerializationHolder..ctor(SerializationInfo
info, StreamingContext context)
at System.AppDomain.DoCallBack(CrossAppDomainDelegate
callBackDelegate)
at TaskExecuter.AppDomainHelper.CreateAppDomain(String dir,
String name) in d:\users\peter\documents\visual studio
2010\Projects\TaskExecuter\TaskExecuter\AppDomainHelper.cs:line 50
at TaskExecuter.TaskManagment.TaskFinder.Probe() in
d:\users\peter\documents\visual studio
2010\Projects\TaskExecuter\TaskExecuter\TaskManagment\TaskFinder.cs:line
29
at TaskExecuter.TaskManagment.TaskManager.LoadTasks() in
d:\users\peter\documents\visual studio
2010\Projects\TaskExecuter\TaskExecuter\TaskManagment\TaskManager.cs:line
63
at TaskExecuter.TaskManagment.TaskManager.Start() in
d:\users\peter\documents\visual studio
2010\Projects\TaskExecuter\TaskExecuter\TaskManagment\TaskManager.cs:line
95
at TaskExecuter.Terminal.Program.Main(String[] args) in
d:\users\peter\documents\visual studio
2010\Projects\TaskExecuter\TaskExecuter.Terminal\Program.cs:line 16
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly,
String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile,
Evidence assemblySecurity, String[] args)
at
Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object
state)
at System.Threading.ExecutionContext.Run(ExecutionContext
executionContext, ContextCallback callback, Object state, Boolean
ignoreSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext
executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
InnerException:
I was able to recover the linked to blog post using archive.org and also come up with a working solution.
My goal was to dynamically compile an exe into a temporary location, and then have that exe shadow load all main dlls in a child appdomain so that the main application that spawned the exe can be updated easily. The basic approach is using childAppDomain.CreateInstanceFrom to create a type that in the constructor installs the assembly resolve event handler. My code looked like
var exportAppDomain = AppDomain.CreateDomain(
runnerName,
null,
appDomainSetup,
new PermissionSet(PermissionState.Unrestricted));
exportAppDomain.CreateInstanceFrom(
Assembly.GetExecutingAssembly().Location,
"ExportLauncher.AppDomainResolver",
true,
BindingFlags.Public | BindingFlags.Instance,
null,
new object[] { Assembly.GetExecutingAssembly().Location },
null,
null);
And the type that creates the needed AssemblyResolve handler (the blog post below describes why you need another type)
class AppDomainResolver
{
string _sourceExeLocation;
public AppDomainResolver(string sourceExeLocation)
{
_sourceExeLocation = sourceExeLocation;
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
}
Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
if (args.Name.Contains("exporterLauncher")) // why does it not already know it has this assembly loaded? the seems to be required
return typeof(AppDomainResolver).Assembly;
else
return null;
}
}
Here is the original blog post:
Application Domains is hard…
Have you ever been working with Application Domain in .NET?, in the beginning it doesn’t seem all that difficult, but ones you get to know them you begin to realize all the little difficulties.
Everything works fine as long as you don’t move outside the Host AppDomains.BaseDirectory, but in our case we wanted to have Plug-ins deployed at say location “C:\My Plug-ins” while the host application would run at “C:\Program Files\My App”, since we might run into dependencies from the AppDomain to some of the Host Assemblies problems was apparently inevitable.
The Classic
Here is some simple code and our first attempt.
1: string applicationBase = Path.GetDirectoryName(interOperabilityPackageType.AssemblyDescription.AssemblyPath);
2: AppDomainSetup setup = new AppDomainSetup
3: {
4: ApplicationName = name,
5: ApplicationBase = applicationBase,
6: PrivateBinPath = AppDomain.CurrentDomain.BaseDirectory,
7: PrivateBinPathProbe = AppDomain.CurrentDomain.BaseDirectory,
8: ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile
9: };
10:
11: Evidence evidence = new Evidence(AppDomain.CurrentDomain.Evidence);
12: AppDomain domain = AppDomain.CreateDomain(name, evidence, setup);
Seems very simple, but because “ApplicationBase” is different from “AppDomain.CurrentDomain.BaseDirectory” we ran into what seems to be a very well know exception.
System.IO.FileNotFoundException: Could not load file or assembly 'Host.Services, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.
If you have worked with any sort of dynamically loading assemblies I am fairly sure that is familiar to you. And the issue is that “Host.Services” was know within the Host Application Domain because it is stored in “C:\Program Files\My App”, and the Application Domain looking for it is looking in “C:\My Plug-ins”.
Well we Thought we instructed it to also look in “AppDomain.CurrentDomain.BaseDirectory” which would be “C:\Program Files\My App”, but that was not the case.
AppDomain.AssemblyResolve to the rescue?
Ok so we have been working with these quirks before, so we knew how we could use “AppDomain.AssemblyResolve” to manually resolve any assemblies that the AppDomain it self could not handle.
1: string applicationBase = Path.GetDirectoryName(interOperabilityPackageType.AssemblyDescription.AssemblyPath);
2: AppDomainSetup setup = new AppDomainSetup
3: {
4: ApplicationName = name,
5: ApplicationBase = applicationBase,
6: PrivateBinPath = AppDomain.CurrentDomain.BaseDirectory,
7: PrivateBinPathProbe = AppDomain.CurrentDomain.BaseDirectory,
8: ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile
9: };
10:
11: Evidence evidence = new Evidence(AppDomain.CurrentDomain.Evidence);
12: AppDomain domain = AppDomain.CreateDomain(name, evidence, setup);
13: domain.AssemblyResolve += Resolve;
That should work right, well we thought so and ones again we were wrong, what happens now is that instead of actually getting as far as initializing the Application Domain and using it, instead it fails right where we hooked up the event handler for resolving assemblies.
Again the exception looks very much like the previous mentioned, but this time it can’t find the Assembly that contains the Type that has the “Resolve” handler we set up in the very last line in the above snippet.
AppDomain.Load then!
Ok, so obviously when hooking up the event handler, the Application Domain needs to know the Type of the object handling that event, that is actually fairly understandable when you think about it, so if the Application Domain can’t even find that one and load we can’t really handle anything.
So what is next? Our idea was to manually instruct the Application Domain to load a shallow assembly that didn’t have any other dependencies that what could be found in the GAC, and the hook an event handler up.
1: string applicationBase = Path.GetDirectoryName(interOperabilityPackageType.AssemblyDescription.AssemblyPath);
2: AppDomainSetup setup = new AppDomainSetup
3: {
4: ApplicationName = name,
5: ApplicationBase = applicationBase,
6: PrivateBinPath = AppDomain.CurrentDomain.BaseDirectory,
7: PrivateBinPathProbe = AppDomain.CurrentDomain.BaseDirectory,
8: ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile
9: };
10:
11: Evidence evidence = new Evidence(AppDomain.CurrentDomain.Evidence);
12: AppDomain domain = AppDomain.CreateDomain(name, evidence, setup);
13: domain.Load(File.ReadAllBytes(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Host.AssemblyLoader.dll")));
14: domain.AssemblyResolve += new AssemblyLoader(AppDomain.CurrentDomain.BaseDirectory).Handle;
Using a very simple little class like the following, and don’t mind the odd Resolve behavior.
1: [Serializable]
2: public class AssemblyLoader
3: {
4: private string ApplicationBase { get; set; }
5:
6: public AssemblyLoader(string applicationBase)
7: {
8: ApplicationBase = applicationBase;
9: }
10:
11: public Assembly Resolve(object sender, ResolveEventArgs args)
12: {
13: AssemblyName assemblyName = new AssemblyName(args.Name);
14: string fileName = string.Format("{0}.dll", assemblyName.Name);
15: return Assembly.LoadFile(Path.Combine(ApplicationBase, fileName));
16: }
17: }
So yes or no?… NO!… same problem still.
Things are much more simple!
Actually things became much more simple in the end when we managed to make it work.
I Can’t say how exactly the .NET team has envisioned that this should work, we couldn't really find out any useable things that the “PrivateBinPath” and “PrivateBinPathProbe” was used for. Well we use them now, and made them work as we expected they would!
So we changed the “AssemblyLoader” class to look like this instead:
1: [Serializable]
2: public class AssemblyLoader : MarshalByRefObject
3: {
4: private string ApplicationBase { get; set; }
5:
6: public AssemblyLoader()
7: {
8: ApplicationBase = AppDomain.CurrentDomain.SetupInformation.PrivateBinPath;
9: AppDomain.CurrentDomain.AssemblyResolve += Resolve;
10: }
11:
12: private Assembly Resolve(object sender, ResolveEventArgs args)
13: {
14: AssemblyName assemblyName = new AssemblyName(args.Name);
15: string fileName = string.Format("{0}.dll", assemblyName.Name);
16: return Assembly.LoadFile(Path.Combine(ApplicationBase, fileName));
17: }
18: }
So rather than hooking up the event where we created the Application Domain, we let the class do it by it self, and to “CurrentDomain” instead.
Ok so wait, doesn’t that cause an issue when creating it in the factory since it is now loading for the wrong domain? Well thankfully you are able to create objects within domains from the outside.
So creating the domain is now done as follows:
1: string applicationBase = Path.GetDirectoryName(interOperabilityPackageType.AssemblyDescription.AssemblyPath);
2: AppDomainSetup setup = new AppDomainSetup
3: {
4: ApplicationName = name,
5: ApplicationBase = applicationBase,
6: PrivateBinPath = AppDomain.CurrentDomain.BaseDirectory,
7: PrivateBinPathProbe = AppDomain.CurrentDomain.BaseDirectory,
8: ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile
9: };
10:
11: Evidence evidence = new Evidence(AppDomain.CurrentDomain.Evidence);
12: AppDomain domain = AppDomain.CreateDomain(name, evidence, setup);
13: domain.CreateInstanceFrom(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Host.AssemblyLoader.dll"),"Host.AssemblyLoader");
We don’t even care for maintaining a reference to the “AssemblyLoader” since it should pretty much be kept alive by hooking it self up to the Event.
Hopefully this can help some that has stumbled over the same problem, I see many workarounds where people then either just let Plug-ins be installed in same host directory, having all the necessary dependencies deployed along with the plug-in even though it isn’t something the plug-in knows it is dependant on and so forth.
The above at least enables us to install plug-ins away from our host application base which I think is nice.
If anyone have solved this differently, then please make a response, maybe we can find pros and cons in either way, or just discover a better solution.
If you have any questions or can’t get the above to work, then feel free to ask.
author: Jens Melgaard | posted # Thursday, July 01, 2010 3:08 PM | Feedback (0)
Is there any reason why you do not use the original assemblies ?
So unless your foreign appdomain uses credentials that prevent it from accessing the original assemblies, the method AppDomain.CreateInstanceFromAndUnwrap is capable of doing so.
I suggest you isolate your remotely executed code in a MarshalByRefObject class, using a class like this :
public class MyRemoteClass : MarshalByRefObject
{
public void SetupLogging()
{
// ...
}
}
And use it like this :
var assemblyPath = new Uri(typeof(MyRemoteClass).Assembly.CodeBase).LocalPath;
var remote = (MyRemoteClass)domain.CreateInstanceFromAndUnwrap(assemblyPath, "NameSpace.MyRemoteClass");
remote.SetupLogging();
This will avoid the unnecessary trouble of passing return values via appdomain state, as DoCallBack does not return values. This will also avoid mixing AppDomain plumbing code with your application logic.
Finally, you may need to intercept AppDomain.AssemblyResolve inside MyRemoteClass for other dependencies to load properly, though.
Found a solution after loading the assembly from byte setting the .GetName().CodeBase to null resolved the problem...
After looking around i found this page and it has a better solution then mine!
According to http://msdn.microsoft.com/en-us/library/aehss7y0.aspx the behaviour of AppDomain.CreateDomain has changed with .NET4 and you should use http://msdn.microsoft.com/en-us/library/ms130766.aspx and setup Evidence and grants "manually"...
If you need to load assembly yourself avoid loading from bytes... I'd recommend to use at least loading by full assembly path.
In general to investigate problems with loading assemblies serach for "fusion log viewer" ( http://www.bing.com/search?q=fussion+log+viewer ) and use the tool to see where code tries to load assemblies from.
My educated guess is that you missed an important part of the error message:
System.IO.FileNotFoundException was unhandled Message=Could not load file or
assembly 'TaskExecuter, Version=1.0.4244.31921, Culture=neutral, PublicKeyToken=null'
or one of its dependencies. The system cannot find the file specified. Source=mscorlib
Besides not being able to load your assembly from another location than under your ApplicationBase there is probably some dependend assembly missing from where it could be resolved and loaded.
By the way, if you start loading from bytes you should have a look at the assemblies loaded to your domain. The dependend assembly might be loaded already, but the dependency cannot be resolved automatically. If you have the same assembly loaded twice, its types will be incompatible. You'll get funny CastExceptions saying an object of YourClass cannot be cast to YourClass.
You can try to register an AssemblyResolve event handler to your domain, but with this you end up easily with some black magic conjuring stuff from .dll hell.
If everything else fails and you go to .dll hell yourself, meet me here:
Need to hookup AssemblyResolve event when DisallowApplicationBaseProbing = true

Categories