asp.netsql-serverpinvokeshadow-copy

Is it safe to manually copy native DLLs to a Shadow Copy directory?


I'm using the Microsoft.SqlServer.Types library in an ASP.NET application. The library depends on a native DLL that has to be loaded with LoadLibrary() via P/Invoke.

The documentation of the library suggests loading the native DLL from Server.MapPath("~/bin"), but this causes the native DLL to be locked, preventing new deployments of the application.

I was therefore thinking of retrieving the shadow copy location of the main website DLL with Assembly.GetExecutingAssembly().Location, copying the native DLL to that location, and then loading it from there.

Is it safe to do that? Or is there a safer method that should be used?


Solution

  • I've gone ahead and implemented this, it seems to work fine. No locking issues when deploying.

    Sample code is below. You may need to modify this depending on your environment, it's written for Web API 2 hosted in OWIN.

    private void LoadSqlServerTypes()
    {
        var shadowCopyDirectoryPath = new FileInfo(Assembly.GetExecutingAssembly().Location).DirectoryName;
        var nativeDllDirectory = new DirectoryInfo(HostingEnvironment.MapPath("~/bin/SqlServerTypes"));
        var nativeDllShadowCopyDirectory = Path.Combine(shadowCopyDirectoryPath, "SqlServerTypes");
        if (Directory.Exists(nativeDllShadowCopyDirectory) == false)
        {
            nativeDllDirectory.CopyTo(nativeDllShadowCopyDirectory, true);
        }
    
        SqlServerTypes.Utilities.LoadNativeAssemblies(shadowCopyDirectoryPath);
    }
    

    The CopyTo method is a modified version of the code at https://learn.microsoft.com/en-us/dotnet/standard/io/how-to-copy-directories

    public static partial class DirectoryInfoExtensions
    {
        /*
         * Source adapted from https://learn.microsoft.com/en-us/dotnet/standard/io/how-to-copy-directories
         */
         /// <summary>
         /// Copy a directory and its contents to a specified location.
         /// </summary>
         /// <param name="sourceDirectory">The directory to copy.</param>
         /// <param name="destinationDirectoryPath">The path of the new directory to create.</param>
         /// <param name="recursive">True if subdirectories must be copied; otherwise, false.</param>
        public static void CopyTo(this DirectoryInfo sourceDirectory, string destinationDirectoryPath, bool recursive)
        {
            if (Directory.Exists(destinationDirectoryPath) == false)
            {
                Directory.CreateDirectory(destinationDirectoryPath);
            }
    
            var files = sourceDirectory.GetFiles();
            foreach (var file in files)
            {
                string destinationFilePath = Path.Combine(destinationDirectoryPath, file.Name);
                file.CopyTo(destinationFilePath, false);
            }
    
            if (recursive == true)
            {
                var subdirectories = sourceDirectory.GetDirectories();
                foreach (DirectoryInfo directory in subdirectories)
                {
                    string destinationSubdirectoryPath = Path.Combine(destinationDirectoryPath, directory.Name);
                    CopyTo(new DirectoryInfo(directory.FullName), destinationSubdirectoryPath, recursive);
                }
            }
        }
    }