How to move CefSharp dependencies and files to subdirectory? - cefsharp

CefSharp has a lot of dependencies and libraries that it requires to run. The build folders are cluttered. How can I move the required .dll and .pak dependencies to a sub folder?

First off, to make everything easier, I would recommend adding a folder to Visual Studios and putting all the required files in there. If you make this folder in explorer, click 'show all files' above your solution in the solution explorer:
This right click the folder(s) and file(s) you wish to include and choose 'include in project.'
Be sure to include all required CefSharp files - more info on github
You should end up with a file tree that looks similar to this:
Be sure to change 'Copy to Output Directy' to 'Copy always' under properties for all files.
Now for the code. Your solution should have an 'App.config' file (if not, google around and you'll find the way to generate one).
You are going to add a new assemblyBinding and probing element to it (MSDN - probing)
The probing element tells windows it should look in additional folders for libraries. Thus we can load in all the required .dll's for CefSharp this way.
Example App.config:
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="resources/cefsharp" />
</assemblyBinding>
</runtime>
</configuration>
Note: the path is relative the location of the .exe file.
Now that takes care of the .dll files, but we now need to change the settings for CefSharp so it knows where to look for the .pak files, as well as the locales, and BrowserSubprocess.exe.
To do this we are going to define all the file paths and manual assign them to CefSharp.
Here's an example of what it should look like:
// File location variables
static string lib, browser, locales, res;
[STAThread]
static void Main()
{
// Assigning file paths to varialbles
lib = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, #"resources\cefsharp\libcef.dll");
browser = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, #"resources\cefsharp\CefSharp.BrowserSubprocess.exe");
locales = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, #"resources\cefsharp\locales\");
res = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, #"resources\cefsharp\");
var libraryLoader = new CefLibraryHandle(lib);
bool isValid = !libraryLoader.IsInvalid;
Console.WriteLine($"Library is valid: {isValid}");
LoadForm();
libraryLoader.Dispose();
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void LoadForm()
{
var settings = new CefSettings();
settings.BrowserSubprocessPath = browser;
settings.LocalesDirPath = locales;
settings.ResourcesDirPath = res;
Cef.Initialize(settings, shutdownOnProcessExit: false, performDependencyCheck: false);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new CefWinForm());
}
All this is adapted from: https://github.com/cefsharp/CefSharp/issues/601
The original issue was difficult to follow completely and get working properly so I thought I'd share the knowledge in case anyone encounters similar trouble in the future.
Note: Visual studio will still include the .dll's, .pak's, .xml's, etc. in the output directory, but you can check if your build is successful by deleting the dependencies from your main folder (leaving the resources folder).

Related

ASP.NET6 Blazor: Can a directory be mapped as a virtual subdirectory under wwwroot?

Can I map an arbitrary directory as a subdirectory of wwwroot? That is, the directory is not under wwwroot in the file system, but within my app, it is treated like so.
For example, I have created an ASP.NET 6.0 Blzor Server project. The programme dll path is in /app/proj1/BlazorApp1.dll. If there is an image at /app/proj1/wwwroot/images/dog.jpg, it can be rendered using <img src="images/dog.jpg"/> in my page. But what if I do not want to have this images directory actually under the wwwroot directory on the file system? Can the directory be somewhere else like /data/images, and I map that path to wwwroot/images, so that /data/images/dog.jpg can be rendered with <img src="images/dog.jpg"/> within my app?
There are 3 ways to do this that i know of, Firstly adjusting the settings, which is clearly documented on msdn, under the 'Serve files outside of web root' header;
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(builder.Environment.ContentRootPath, "MyStaticFiles")),
RequestPath = "/StaticFiles"
});
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/static-files?view=aspnetcore-6.0#serve-files-outside-of-web-root
For your case, since you want both wwwroot and another file, see 'Serve files from multiple locations';
var webRootProvider = new PhysicalFileProvider(builder.Environment.WebRootPath);
var newPathProvider = new PhysicalFileProvider(
Path.Combine(builder.Environment.ContentRootPath, "MyStaticFiles"));
var compositeProvider = new CompositeFileProvider(webRootProvider,
newPathProvider);
// Update the default provider.
app.Environment.WebRootFileProvider = compositeProvider;
app.UseStaticFiles();
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/static-files?view=aspnetcore-6.0#serve-files-from-multiple-locations
Another way would be to use MSBuild to copy the files from another directory into the wwwroot folder, see for example the following SO;
Copy files to output directory using csproj dotnetcore
Lastly, and this is not as portable, you could use symlinks, see:
A symbolic link is a file-system object that points to another file system object. The object being pointed to is called the target.
Symbolic links are transparent to users; the links appear as normal files or directories, and can be acted upon by the user or application in exactly the same manner.
https://learn.microsoft.com/en-us/windows/win32/fileio/symbolic-links
https://en.wikipedia.org/wiki/Symbolic_link
https://superuser.com/questions/1020821/how-can-i-create-a-symbolic-link-on-windows-10
The first one should satisfy your needs.
You could try as below to update the default provider with a composite fileprovider :
var webRootProvider = new PhysicalFileProvider(builder.Environment.WebRootPath);
var newPathProvider = new PhysicalFileProvider(
Path.Combine(builder.Environment.ContentRootPath, "data"));
var compositeProvider = new CompositeFileProvider(webRootProvider,
newPathProvider);
app.Environment.WebRootFileProvider = compositeProvider;
app.UseStaticFiles();
Result:

Blazor: How to run my command before JS files compression / load blazor app from different domain

TL;DR: is there a Target during build/publish (in asp.net core Blazor app) for JS files compression which I can use in csproj to run my script before this Target?
Background:
I have Blazor frontend application which is loaded to different web application (different domains). So: main application loads many other applications and one of these applications is Blazor app (hosted at different URL).
What I did: I load manually _framework/blazor.webassembly.js with autostart property set to false and start Blazor manually:
Blazor.start({
loadBootResource: (
type: string,
name: string,
defaultUri: string,
integrity: string
) => {
const newUrl = ...;// here I can make some URL replacements for defaultUri
// so my `newUrl` points to place where Blazor app is hosted
return newUrl;
},
})
similar as described here: https://learn.microsoft.com/en-us/aspnet/core/blazor/host-and-deploy/webassembly?view=aspnetcore-3.1#custom-boot-resource-loading
It works correctly but one file is NOT loaded through loadBootResource. It is blazor.boot.json. Code which loads this file is located in blazor.webassembly.js (fetch("_framework/blazor.boot.json"...): https://github.com/dotnet/aspnetcore/blob/master/src/Components/Web.JS/src/Platform/BootConfig.ts#L6
Problem is described also in this issue https://github.com/dotnet/aspnetcore/issues/22220
There is also possible solutions suggested by me: https://github.com/dotnet/aspnetcore/issues/22220#issuecomment-683783971
I decided to replace content of blazor.webassembly.js (replace fetch("_framework/blazor.boot.json" with fetch(${someGlobalVariableForSpecificDomainURL}/_framework/blazor.boot.json) but there are also compressed files (GZ and BR). How to run my script for replacement before compression is started but after JS file is generated? Is it possible? Is there any Target which I can use in csproj?
I do not want to disable dotnet files compression and I do not want to overwrite compressed files (compress by my own).
My current csproj contains something like this (script is started after compression so too late):
<Target Name="ReplacementDuringBuild" BeforeTargets="Build">
<Exec WorkingDirectory="$(MyDirWithScripts)" Command="node replace.js --output=$(MSBuildThisFileDirectory)$(OutDir)" />
</Target>
<Target Name="ReplacementDuringPublish" AfterTargets="AfterPublish">
<Exec WorkingDirectory="$(MyDirWithScripts)" Command="node replace.js --output=$(MSBuildThisFileDirectory)$(PublishDir)" />
</Target>
Thanks for a help or any suggestion! If there is also another workaround to solve main issue, then I will be glad to see it (base tag does not work; replacement of fetch also is not so good).
I didn't find any fitting Target for my purpose. Code from question worked correctly but only with my own compression. So I reverted this and finished with overriding window.fetch to resolve main issue. If URL contains blazor.boot.json then I modify URL and pass it to original fetch. After all files are loaded, I restore original fetch. Similar to code suggested here: https://github.com/dotnet/aspnetcore/discussions/25447
const originalFetch = window.fetch;
window.fetch = function(requestInfo, options) {
if (requestInfo === '_framework/blazor.boot.json') {
return originalFetch('https://example.com/myCustomUrl/blazor.boot.json', options);
} else {
// Use default logic
return originalFetch.apply(this, arguments);
}
};

Sass SCSS by BundleTranslator

I have set up the BundleTranslator in my MVC 5 project via NuGet (BundleTransformer.SassAndScss v1.9.96 with the Core and LibSassHost components). Next I have added the bundle reference to my View.cshtml
#Styles.Render("~/Content/sass")
and redefined the BundleConfig.cs:
var nullOrderer = new NullOrderer();
var commonStylesBundle = new CustomStyleBundle("~/Content/sass");
commonStylesBundle.Include("~/Content/Custom/the-style.scss");
commonStylesBundle.Orderer = nullOrderer;
bundles.Add(commonStylesBundle);
After build, the website has a .scss reference:
<link href="/Content/Custom/the-style.scss" rel="stylesheet">
and everything is working locally probably only because I have installed SassAndCoffee package with SassyStudio. The problem emerges when I deploy on external IIS server. The file exists in the Content/Custom directory, but the website is broken. The HTML code also has the file reference (it links to .scss file, not compiled .css) but if I try to navigate to it, I get error 500.
I have changed the Sass file Build Action to Content (from None) and left Copy to Output Directory as Do not copy. I have also added httpHandlers to Web.config (but I actually don't know whatfor) but still nothing helps.
<httpHandlers>
<add path="*.sass" verb="GET" type="BundleTransformer.SassAndScss.HttpHandlers.SassAndScssAssetHandler, BundleTransformer.SassAndScss" />
<add path="*.scss" verb="GET" type="BundleTransformer.SassAndScss.HttpHandlers.SassAndScssAssetHandler, BundleTransformer.SassAndScss" />
</httpHandlers>
I didn't check all of the settings in Web.config because of the NuGet installation which (as I see) provides this kind of data for the BundleTransformer.
How do I configure the BundleTransformer to work correctly on IIS? Do I have to override the BundleResolver as in example code?
BundleResolver.Current = new CustomBundleResolver();
There are a few things to try to diagnose the problem. Firstly it works locally! :)
1.
It is worth testing that your bundling works correctly. You can do this by temporarily setting the following (take this out once you have finished).
public class BundleConfig
{
public static void RegisterBundles(BundleCollection bundles)
{
BundleTable.EnableOptimizations = true;
// Bundles and stuff...
}
}
Run the site and then in your browser dev tools you should get something like this:
/bundles/commonStyles?v=BzJZwKfP1XV8a6CotGRsHhbxraPDst9zDL2X4r36Y301
If this works then we can be happy bundling will work in production.
2.
Permissions. If your getting a 500 internal server error, check the permissions on the folder that contains the scss files. Also check they are there :)
3.
Mime type. There may be a slight chance that IIS needs a mime type added for .scss, but I'm not convinced it will.

Can't deploy precompiled, merged webapp to Azure

I'm trying to deploy an ASP.NET web application to Azure. It's hybrid Web Forms, MVC, and WebAPI, and there are a TON of aspx/ascx files, such that they really need to be precompiled or every deploy will render the site sluggish for awhile.
I am trying to deploy via SCM integration with GitHub via kudu, with precompiled views, all merged to a single assembly.
Note that:
Deploy works fine with precompilation disabled.
Deploy works fine from Visual Studio
Build works fine if I copy the msbuild command from the Azure log, replace the relevant paths, and run it locally on my Windows 8.1 machine.
I've set up the Advanced Precompile settings as:
Don't allow precompiled site to be udpatable
Don't emit debug information
Merge all pages and control outputs to a single assembly = AppViews.dll
Here's the .deployment file for Azure
[config]
project = WebSite/WebSite.csproj
SCM_BUILD_ARGS=/p:Configuration=Release;PublishProfile=azure-prod /v:n
You notice I'm sending the verbosity /v to "normal" for extra diagnostic information.
Here is info I get toward the tail of the deployment log:
AspNetPreCompile:
D:\Windows\Microsoft.NET\Framework\v4.0.30319\aspnet_compiler.exe -v \ -p D:\home\site\repository\WebSite\obj\Release\AspnetCompileMerge\Source -c D:\home\site\repository\WebSite\obj\Release\AspnetCompileMerge\TempBuildDir
GenerateAssemblyInfoFromExistingAssembleInfo:
Creating directory "obj\Release\AssemblyInfo".
D:\Windows\Microsoft.NET\Framework\v4.0.30319\Csc.exe /out:obj\Release\AssemblyInfo\AssemblyInfo.dll /target:library Properties\AssemblyInfo.cs
AspNetMerge:
Running aspnet_merge.exe.
D:\Program Files (x86)\Microsoft SDKs\Windows\v8.0A\bin\NETFX 4.0 Tools\aspnet_merge.exe D:\home\site\repository\WebSite\obj\Release\AspnetCompileMerge\TempBuildDir -w AppViews.dll -copyattrs obj\Release\AssemblyInfo\AssemblyInfo.dll -a
aspnet_merge : error 1003: The directory 'D:\home\site\repository\WebSite\obj\Release\AspnetCompileMerge\TempBuildDir' does not exist. [D:\home\site\repository\WebSite\WebSite.csproj]
Done Building Project "D:\home\site\repository\WebSite\WebSite.csproj" (Build;pipelinePreDeployCopyAllFilesToOneFolder target(s)) -- FAILED.
Build FAILED.
It looks like aspnet_compiler.exe runs, but doesn't do what it's supposed to, which is why the TempBuildDir directory (supposed to be the output of the compiler) does not exist in time for the AspNetMerge target. Contrast that with my system, where that directory DOES in fact exist, containing the marker aspx/ascx/etc. files, static content, a PrecompiledApp.config file, and a whole mess of stuff in the bin directory.
aspnet_compiler.exe has an -errorstack flag but it's not clear to me how I could get MSBuild to add this just via the .deployment file, or even if that app is really even throwing an error.
I could just deploy via Visual Studio, but I would really like to take advantage of the SCM integration so I can just push to my prod branch and let it go. Any suggestions?
I replied on https://github.com/projectkudu/kudu/issues/1341, but copying my answer here in case someone lands here...
Way back, we had found that aspnet_compiler.exe was not working within Azure Websites due to how it dealt with the profile folder. We made a change at the time that's a bit of a hack but got us going: we turned it into a no-op, by pointing HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\aspnet_compiler.exe to our own dummy exe (D:\Program Files (x86)\aspnet_compiler\KuduAspNetCompiler.exe).
But trying it now, it appears to work correctly today, likely thanks to improvements in the Azure Websites hosting environment. So we will try getting rid of this hack and doing a full test pass to make sure it doesn't cause any major regressions. If all goes well, we can get that into production, which should enable those scenarios.
In the short term, you may be able to work around this by having your build script:
copy aspnet_compiler.exe from D:\Windows\Microsoft.NET\Framework\v4.0.30319 into your own site files, but under a different name (e.g. aspnet_compiler2.exe)
convince msbuild to use that one
Note: This GitHub issue on projectkudu will eventually make this solution obsolete, but for the meantime, that issue is filed as Backlog, and this works right now.
Thank you thank you David Ebbo. With this information, I was able to bootstrap my build to work for the short term.
First, I downloaded the aspnet_compiler.exe from the Azure instance using the Diagnostic Console available at https://{WEBSITE_NAME}.scm.azurewebsites.net/DebugConsole and added that to my own repository. This way there's no question about any difference between 32/64-bit, etc. I renamed it to azure_aspnet_compiler.exe in my repository.
Second, the AspNetCompiler task doesn't give you the option to change the tool name. It's hardcoded, but as a virtual property so it's overrideable. So I had to create my own task class, and package it in its own assembly, which I built in Release mode and also included in my repository.
public class AzureAspNetCompiler : Microsoft.Build.Tasks.AspNetCompiler
{
private string _toolName = "aspnet_compiler.exe";
protected override string ToolName
{
get { return _toolName; }
}
public string CustomToolName // Because ToolName cannot have a setter
{
get { return _toolName; }
set { _toolName = value; }
}
}
Next I needed to replace the AspNetPreCompile task in MSBuild, but I couldn't figure out how to do that directly. But that task wasn't doing anything anyway, so why not just run right after it?
I added this to the top of my Website.csproj file to import the DLL containing the AzureAspNetCompiler class. Note that the path is relative to the Website.csproj file I'm editing.
<UsingTask TaskName="AzureBuildTargets.AzureAspNetCompiler"
AssemblyFile="..\DeploymentTools\AzureBuildTargets.dll" />
Then I added this right below it, which is basically stealing the MSBuild target definition of AspNetPreCompile from C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v11.0\Web\Transform\Microsoft.Web.Publishing.AspNetCompileMerge.targets, with some of the property setting stuff near the top of it left out (because the original task will do that for us anyway.) Just take note of the ToolPath and CustomToolName values at the bottom of the (renamed) AzureAspNetCompiler element.
<PropertyGroup>
<!--Relative to solution root apparently-->
<LocalRepoDeploymentTools>.\DeploymentTools</LocalRepoDeploymentTools>
<AzureAspnetCompilerPath>$([System.IO.Path]::GetFullPath($(LocalRepoDeploymentTools)))</AzureAspnetCompilerPath>
</PropertyGroup>
<Target Name="NoReallyAspNetPreCompile" AfterTargets="AspNetPreCompile">
<AzureAspNetCompiler
PhysicalPath="$(_PreAspnetCompileMergeSingleTargetFolderFullPath)"
TargetPath="$(_PostAspnetCompileMergeSingleTargetFolderFullPath)"
VirtualPath="$(_AspNetCompilerVirtualPath)"
Force="$(_AspNetCompilerForce)"
Debug="$(DebugSymbols)"
Updateable="$(EnableUpdateable)"
KeyFile="$(_AspNetCompileMergeKeyFile)"
KeyContainer="$(_AspNetCompileMergeKeyContainer)"
DelaySign="$(DelaySign)"
AllowPartiallyTrustedCallers="$(AllowPartiallyTrustedCallers)"
FixedNames="$(_AspNetCompilerFixedNames)"
Clean="$(Clean)"
MetabasePath="$(_AspNetCompilerMetabasePath)"
ToolPath="$(AzureAspnetCompilerPath)"
CustomToolName="azure_aspnet_compiler.exe"
/>
<!--
Removing APP_DATA is done here so that the output groups reflect the fact that App_data is
not present
-->
<RemoveDir Condition="'$(DeleteAppDataFolder)' == 'true' And Exists('$(_PostAspnetCompileMergeSingleTargetFolderFullPath)\App_Data')"
Directories="$(_PostAspnetCompileMergeSingleTargetFolderFullPath)\App_Data" />
<CollectFilesinFolder Condition="'$(UseMerge)' != 'true'"
RootPath="$(_PostAspnetCompileMergeSingleTargetFolderFullPath)" >
<Output TaskParameter="Result" ItemName="_AspnetCompileMergePrecompiledOutputNoMetadata" />
</CollectFilesinFolder>
<ItemGroup Condition="'$(UseMerge)' != 'true'">
<FileWrites Include="$(_PostAspnetCompileMergeSingleTargetFolderFullPath)\**"/>
</ItemGroup>
With this in place, everything works as I would expect it to.

Setting Jetty resourcebase to static file embedded in the same jar file

I am trying to access static resource (eg. first.html) packed inside the same .jar file (testJetty.jar), which also has a class which starts the jetty (v.8) server (MainTest.java). I am unable to set the resource base correctly.
The structure of my jar file (testJetty.jar):
testJetty.jar
first.html
MainTest.java
==
Works fine on local machine, but when I wrap it in jar file and then run it, it doesn't work, giving "404: File not found" error.
I tried to set the resourcebase with the following values, all of which failed:
a) Tried setting it to .
resource_handler.setResourceBase("."); // Results in directory containing the jar file, D:\Work\eclipseworkspace\testJettyResult
b) Tried getting it from getResource
ClassLoader loader = this.getClass().getClassLoader();
File indexLoc = new File(loader.getResource("first.html").getFile());
String htmlLoc = indexLoc.getAbsolutePath();
resource_handler.setResourceBase(htmloc); // Results in D:\Work\eclipseworkspace\testJettyResult\file:\D:\Work\eclipseworkspace\testJettyResult\testJetty1.jar!\first.html
c) Tried getting the webdir
String webDir = this.getClass().getProtectionDomain()
.getCodeSource().getLocation().toExternalForm();
resource_handler.setResourceBase(webdir); // Results in D:/Work/eclipseworkspace/testJettyResult/testJetty1.jar
None of these 3 approaches worked.
Any help or alternative would be appreciated
Thanks
abbas
The solutions provided in this thread work but I think some clarity to the solution could be useful.
If you are building a fat jar and use the ProtectionDomain way you may hit some issues because you are loading the whole jar!
class.getProtectionDomain().getCodeSource().getLocation().toExternalForm();
So the better solution is the other provided solution
contextHandler.setResourceBase(
YourClass.class
.getClassLoader()
.getResource("WEB-INF")
.toExternalForm());
The problem here is if you are building a fat jar you are not really dumping your webapp resources into WEB-INF but are probably going into the root of the jar, so a simple workaround is to create a folder XXX and use the second approach as follows:
contextHandler.setResourceBase(
YourClass.class
.getClassLoader()
.getResource("XXX")
.toExternalForm());
Or change your build tool to export the webapp files into that given directory. Maybe Maven does this on a Jar for you but gradle does not.
Not unusually, I found a solution to my problem. The 3rd approach mentioned by Stephen in Embedded Jetty : how to use a .war that is included in the .jar from which Jetty starts? worked!
So, I changed from Resource_handler to WebAppContext, where WebAppContext is pointing to the same jar (testJetty.jar) and it worked!
String webDir = MainTest.class.getProtectionDomain()
.getCodeSource().getLocation().toExternalForm(); ; // Results in D:/Work/eclipseworkspace/testJettyResult/testJetty.jar
WebAppContext webappContext = new WebAppContext(webDir, "/");
It looks like ClassLoader.getResource does not understand an empty string or . or / as an argument. In my jar file I had to move all stuf to WEB-INF(any other wrapping dir will do). So the code looks like
contextHandler.setResourceBase(EmbeddedJetty.class.getClassLoader().getResource("WEB-INF").toExternalForm());
so the context looks like this then:
ContextHandler:744 - Started o.e.j.w.WebAppContext#48b3806{/,jar:file:/Users/xxx/projects/dropbox/ui/target/ui-1.0-SNAPSHOT.jar!/WEB-INF,AVAILABLE}

Resources