Embedded Unmanaged DLLs don't load in ASP.NET - asp.net

I'm working on an ASP.NET host for a WCF service. The service references a C++/CLI wrapper library, which itself references an unmanaged DLL. Based on this question I've embedded the unmanaged DLL in the ASP.NET DLL. I then extract it like this:
string[] dlls = new [] { "myDLL.dll", "myDLLD.dll" };
Assembly assembly = Assembly.GetExecutingAssembly();
string location = Path.GetDirectoryName(assembly.Location);
Dictionary<string, Stream> streams =
(from dll in dlls
select new KeyValuePair<string, Stream>(
dll, assembly.GetManifestResourceStream(typeof(Global), dll)))
.ToDictionary(p => p.Key, p => p.Value);
foreach (KeyValuePair<string, Stream> stream in streams)
{
using (FileStream file = new FileStream(Path.Combine(location, stream.Key),
FileMode.Create))
{
stream.Value.CopyTo(file);
}
}
I've tried putting this code in Application_Start() in Global.asax.cs and in AppInitialize() in the App_Code folder, but in both cases I get a yellow screen of death about how the wrapper DLL or one of its dependencies could not be loaded before a breakpoint is hit in either function. The only way I can hit a breakpoint is by placing the unmanaged DLL somewhere in the system path (e.g. C:\Windows\system), but this obviously defeats the purpose of embedding the DLLs in the first place. How can I get the DLL where it needs to be before ASP starts looking?

Apparently ASP.NET's eager loading mechanism was the problem. Because the managed wrapper was copied to the output directory, ASP found it and tried to link to the unmanaged DLL on startup, even though it didn't exist yet. To solve the problem, I used the /DELAYLOAD linker option on the C++/CLI DLL and a LoadLibrary() P/Invoke in Application_Start() in combination with the embedded DLL extraction shown above.

Related

ASP.NET Core 7.0 Localization not working

I'm having trouble configuring localization in my asp.net 7.0 MVC project.
Configuration:
.AddLocalization(opts => opts.ResourcesPath = "Resources")
then
CultureInfo[] supportedCultures = new[]
{
new CultureInfo("en-US"),
new CultureInfo("bg-BG")
};
mvcBuilder
.AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
.AddDataAnnotationsLocalization();
mvcBuilder
.Services
.Configure<RequestLocalizationOptions>(options =>
{
options.DefaultRequestCulture = new RequestCulture(culture: "en-US", uiCulture: "en-US");
options.SupportedCultures = supportedCultures;
options.SupportedUICultures = supportedCultures;
});
This is called before
.AddRazorPages();
And at the end
app.UseRequestLocalization(app.Services.GetRequiredService<IOptions<RequestLocalizationOptions>>().Value);
I have installed Microsoft.Extensions.Localization nuget.
I have two resource files in folder Resources
Controllers.HomeController.bg-BG.resx
Controllers.HomeController.en-US.resx
In both resources there is entry "title" with some values
Injected IStringLocalizer into HomeController but everytime it returns only "title", used it like this:
this.stringLocalizer["title"].Value
After hours of trial and error I really can't seem to find what's the problem.
You appear to be mixing up two different localization techniques. The .resx resource files is the classic .NET technique for localization while the IStringLocalizer based approach has been added in .NET Core. Fortunately, it is still perfectly fine to utilize .resx resources .NET Core Views/Pages.
Add a using statement at the top of your view. Make sure that the namespace matches the actual C# namespace of your resource. The example below assumes that the default namespace of the project is WebApp and the .resx files live inside a Resources folder:
#using WebApp.Resources
Referencing a resource string inside your view is straight forward:
<h2>#Controllers_HomeController.title</h2>
NOTE: The underscore in Controllers_HomeController is caused your use of a . in the resource filename, which would cause problems with the strongly typed generated class inside the corresponding Controllers_HomeController.Designer.cs
The problem was something to do with the namespaces, changed the default namespace and it works now.

IIS Shadow Copied DLL accessing dependency DLLs from bin location

I am trying to resolve an issue with ASP.Net Framework 4.8 site using EFCore 3.1.16 in IIS. Microsoft.Data.SqlClient has a process lock on SNI.dll which causes issues with xcopy deployment in IIS.
I have tried a strategy of copying the SNI.dll to the same shadow copy location as Microsoft.Data.SqlClient so it doesn't have to try and access the DLL in the bin folder as outlined in https://github.com/lscorcia/sqlclient.snishadowcopy.
// Look for the main Microsoft.Data.SqlClient assembly in the
// shadow copy path
var sqlClientShadowAssembly = Directory.GetFiles(
currentShadowPath, "Microsoft.Data.SqlClient.dll",
SearchOption.AllDirectories).FirstOrDefault();
// Extract the directory information from the shadow assembly path
var sqlClientShadowPath =
Path.GetDirectoryName(sqlClientShadowAssembly);
// Find out the process bitness and choose the appropriate native
// assembly
var moduleName = Environment.Is64BitProcess ? "x86\\SNI.dll"
: "x64\\SNI.dll";
// Compute the source and target paths for the native assembly
var sourceFile = Path.Combine(currentPrivatePath, moduleName);
var targetFile = Path.Combine(sqlClientShadowPath, "SNI.dll");
File.Copy(sourceFile, targetFile);
However, it still tries to access the bin location first instead of the sni.dll that is in the same folder location.
I have checked that the Microsoft.Data.SqlClient in the shadow location is being used correctly by deleting the DLL and confirming that a FileNotFound exception is thrown.I have also tried copying directly into the same folder and also copying into an x64 sub folder in the shadow location.
In my case, the error occured only when my IIS application is located on an UNC path (e.g. "\\myserver\myshare\myapplication"). Here is a workaround that worked in my scenario.
Use P/Invoke to SetDllDirectory:
[DllImport(#"kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool SetDllDirectory(string lpPathName);
(See also this MSDN article about DLL load order)
Then, early in my Global.asax.cs Application_Start method (before any DB calls), call it like this:
SetDllDirectory(Server.MapPath(#"~/bin"))
After that, everything works as expected.
I still to consider this to be kind of a hack/workaround, but at least, it is working now in my scenario.
As I do understand, you can call SetDllDirectory multiple times to add additional directories (i.e. not overwrite the existing one).
So in case someone reading this might have other assemblies that refer to native DLLs in "x86" or "x64" folders, one might do something like this:
var bitness = Environment.Is64BitProcess ? #"x64" : #"x86";
SetDllDirectory(Server.MapPath($#"~/bin/{bitness}"));
I've also tried serving my test application from a local path (like "C:\Inetpub\wwwroot") and here, the error does not occur, even when not calling SetDllDirectory.
I'm still not sure why the error occurs for UNC paths only, and not for local paths, as I would expect that the shadow copied managed assemblies to fail the DllImports, too.
(I've also posted the above in this GitHub issue)

Loading all referenced assemblies .NET even if not used explicitly in code

We have a windows service which is using Autofac, when we try to load the referenced assemblies not all are listed as some contain objects we aren't using anywhere in the application but interface implementations are in there we need to be included. The following method loads the assemblies:
private IEnumerable<Assembly> GetReferencedAssemblies(Assembly assembly)
{
var assemblyNames = assembly.GetReferencedAssemblies();
List<Assembly> assemblies = new List<Assembly>();
assemblies.Add(assembly);
foreach (var item in assemblyNames)
{
var loadedAssembly = System.Reflection.Assembly.Load(item.FullName);
assemblies.Add(loadedAssembly);
}
return assemblies;
}
If we make a dummy reference to an object contained in the assembly then it loads the assembly and the types are built by autofac, if we remove the dummy object the assembly is no longer included.
Is there any way to include all referenced assemblies regardless of whether you are directly using an object in there (bearing in mind we still need it as the interface implementations are in there).
This works fine on ASP.NET as it just loads all DLLs in the bin.
If you do not actually reference a type in the assembly the compiler will remove the reference as it is assumed to be redundant. You need to manually load the required assemblies into the AppDomain using Assembly.Load(). How you determine the assemblies to load is up to you. You might choose to look through the files in a particular folder or you perhaps use a configuration file that contains the assemblies names.

Unable to load assembly in new appDomain

I have an mvc3 application.
I have a subfolder in my application project called "Plugins"
All of the dll's stored in that folder should be updateable during runtime, so then we an put down the appdomain and reload the new version of the dlls, so I am attemping to load all the plugin dll's in a different appDomain and set it's shadowCopy properties.
Looking through SO, msdn and some blogs, I got to this "solution". (this is called during my Application_Start)
static AppDomain pluginDomain;
static PluginHolder()
{
AppDomainSetup domainSetup = AppDomain.CurrentDomain.SetupInformation;
domainSetup.ApplicationName = "Plugins";
domainSetup.PrivateBinPathProbe = domainSetup.PrivateBinPath;
domainSetup.PrivateBinPath = GetPluginsDirectory();
domainSetup.ShadowCopyFiles = "true";
domainSetup.ShadowCopyDirectories = domainSetup.PrivateBinPath;
pluginDomain= AppDomain.CreateDomain("Plugins", null, domainSetup);
var item = pluginDomain.Load(File.ReadAllBytes(GetPluginsDirectory() + "Item.dll"));
}
"Item.dll" is the dll i am attemping to load. The last line throws a "Could not load file or assembly Item or one of its dependencies".
This seems to be the way other people have succeded with, but it just wont work for me.
I have no previous experience with AppDomains, so I am not sure on how to approach this problem, or if I am tackling it correctly.
Is my Assembly loading via the new AppDomain an ok approach?
To decypher these kind of error messages, you should use the Assembly Binding Log Viewer.
Also pay attention on the remarks section on the documentation page of AppDomain.Load(byte[]). If you want to load the assembly on the new AppDomain, then you should use CreateInstanceAndUnwrap instead.
If you want to implement a plugin engine in .NET, then you will need to understand AppDomains, assembly resolving and loading and a then choose on a mechanism for the communication between objects from different AppDomains. Most good books on .NET and CLR include a chapter or two on AppDomains and they will get you up to speed. Now as far as the cross-AppDomain communication is concerned, you could use WCF or .NET Remoting (MarshalByRefObject).
Good luck.

Unmanaged DLLs fail to load on ASP.NET server

This question relates to an ASP.NET website, originally developed in VS 2005 and now in VS 2008.
This website uses two unmanaged external DLLs which are not .NET and I do not have the source code to compile them and have to use them as is.
This website runs fine from within Visual Studio, locating and accessing these external DLLs correctly. However, when the website is published on a webserver (runnning IIS6 and ASP.NET 2.0) rather than the development PC it cannot locate and access these external DLLs, and I get the following error:
Unable to load DLL 'XYZ.dll': The specified module could not be found. (Exception from HRESULT: 0x8007007E)
The external DLLs are located in the bin directory of the website, along with the managed DLLs that wrap them and all the other DLLs for the website.
Searching this problem reveals that many other people seem to have the same problem accessing external non.NET DLLs from ASP.NET websites, but I haven't found a solution that works.
I have tried the following:
Running DEPENDS to check the dependencies to establish that the first three
are in System32 directory in the path, the last is in the .NET 2
framework.
I put the two DLLs and their dependencies in
System32 and rebooted the server, but website still
couldn't load these external DLLs.
Gave full rights to ASPNET, IIS_WPG and IUSR (for that server) to
the website bin directory and rebooted, but website still couldn't
load these external DLLs.
Added the external DLLs as existing items to the projects and set
their "Copy to Output" property to "Copy Always", and website
still can't find the DLLs.
Also set their "Build Action" property to "Embedded resource" and
website still can't find the DLLs.
Any assistance with this problem would be greatly appreciated!
This happens because the managed dlls get shadow copied to a temporary location under the .NET Framework directory. See http://msdn.microsoft.com/en-us/library/ms366723.aspx for details.
Unfortunately, the unmanaged dlls do NOT get copied and the ASP.NET process won't be able to find them when it needs to load them.
One easy solution is to put the unmanaged dlls in a directory that is in the system path (type "path" at the command line to see the path on your machine) so that they can be found by the ASP.NET process. The System32 directory is always in the path, so putting the unmanaged dlls there always works, but I would recommend adding some other folder to the path and then adding the dlls there to prevent polluting the System32 directory. One big drawback to this method is you have to rename the unmanaged dlls for every version of your application and you can quickly have your own dll hell.
As an alternate to putting the dll in a folder that is already in the path (like system32) you can change the path value in your process by using the following code
System.Environment.SetEnvironmentVariable("Path", searchPath + ";" + oldPath)
Then when LoadLibrary tries to find the unmanaged DLL it will also scan searchPath. This may be preferable to making a mess in System32 or other folders.
Try putting the dlls in the \System32\Inetsrv directory. This is the working directory for IIS on Windows Server.
If this doesn't work try putting the dlls in the System32 directory and the dependency files in the Inetsrv directory.
Adding to Matt's answer, this is what finally worked for me for 64-bit server 2003 / IIS 6:
make sure your dlls / asp.net are the same version (32 / 64 bit)
Put the unmanaged dlls in inetsrv dir (note that in 64 bit windows, this is under syswow64, even though the sys32/inetsrv directory is created)
Leave the managed dlls in /bin
Make sure both sets of dll's have read/execute permissions
Take a look with FileMon or ProcMon and filter on the names of the troublesome DLLs. This will show you what directories are scanned in search of the DLLs, and any permission issues you might have.
Another option is embedding the native DLL as a resource in the managed DLL. This is more complicated in ASP.NET, as it requires writing to temporary folder at runtime. The technique is explained in another SO answer.
Always worth checking the path variable in your environment settings too.
I have come across the same issue. And I tried all above options, copying to system32, inetpub, setting path environment, etc nothing worked.
This issue is finally resolved by copying unmanaged dll to the bin directory of web application or web service.
Аfter struggling all day over this problem and finally I found a solution which suits me. It's just a test, but the method is working.
namespace TestDetNet
{
static class NativeMethods
{
[DllImport("kernel32.dll")]
public static extern IntPtr LoadLibrary(string dllToLoad);
[DllImport("kernel32.dll")]
public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);
[DllImport("kernel32.dll")]
public static extern bool FreeLibrary(IntPtr hModule);
}
public partial class _Default : System.Web.UI.Page
{
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate int GetRandom();
protected System.Web.UI.WebControls.Label Label1;
protected void Page_Load(object sender, EventArgs e)
{
Label1.Text = "Hell'ou";
Label1.Font.Italic = true;
}
protected void Button1_Click(object sender, EventArgs e)
{
if (File.Exists(System.Web.HttpContext.Current.Server.MapPath("html/bin")+"\\DelphiLibrary.dll")) {
IntPtr pDll = NativeMethods.LoadLibrary(System.Web.HttpContext.Current.Server.MapPath("html/bin")+"\\DelphiLibrary.dll");
if (pDll == IntPtr.Zero) { Label1.Text = "pDll is zero"; }
else
{
IntPtr pAddressOfFunctionToCall = NativeMethods.GetProcAddress(pDll, "GetRandom");
if (pAddressOfFunctionToCall == IntPtr.Zero) { Label1.Text += "IntPtr is zero"; }
else
{
GetRandom _getRandom = (GetRandom)Marshal.GetDelegateForFunctionPointer(pAddressOfFunctionToCall,typeof(GetRandom));
int theResult = _getRandom();
bool result = NativeMethods.FreeLibrary(pDll);
Label1.Text = theResult.ToString();
}
}
}
}
}
}
Run DEPENDS on XYZ.dll directly, in the location that you have deployed it to. If that doesn't reveal anything missing, use the fuslogvw tool in the platform SDK to trace loader errors. Also, the event logs sometimes contain information about failures to load DLLs.
On Application_start use this:
(customise /bin/x64 and bin/dll/x64 folders as needed)
String _path = String.Concat(System.Environment.GetEnvironmentVariable("PATH")
,";"
, System.Web.Hosting.HostingEnvironment.MapPath("~/bin/x64")
,";"
, System.Web.Hosting.HostingEnvironment.MapPath("~/bin/dll/x64")
,";"
);
System.Environment.SetEnvironmentVariable("PATH", _path, EnvironmentVariableTarget.Process);
Try putting the DLL in the Windows/SysWOW64 folder. It was the only thing that worked for me.

Resources