Resources in modularized WPF (with Caliburn.Micro and MEF) - c#

I have searched for an answer for this question all day without coming up with any solutions directly applicable to my case, or anything that works (in the one case I found that was applicable).
I have a Caliburn.Micro framework set up to use MEF, and I load my modularized elements just fine. The one thing missing is getting WPF to recognize the resources I use in one of my modules.
How modules are loaded in my app bootstrapper
[ImportMany]
private IEnumerable<IMyModule> _myModules;
protected override void Configure()
{
// Because Configure() is also called from SelectAssemblies(), we cannot instantiate MEF again because it will create conflicts.
if (_configured)
{
return;
}
AggregateCatalog aggregateCatalog = new AggregateCatalog(AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>());
aggregateCatalog.Catalogs.Add(new DirectoryCatalog(ConfigurationManager.AppSettings["MyModuleFolderLocation"]));
aggregateCatalog.Catalogs.Add(new AssemblyCatalog(GetType().Assembly));
_container = new CompositionContainer(aggregateCatalog);
CompositionBatch batch = new CompositionBatch();
batch.AddExportedValue<IWindowManager>(new WindowManager());
batch.AddExportedValue<IEventAggregator>(new EventAggregator());
batch.AddExportedValue(_container);
_container.Compose(batch);
_container.SatisfyImportsOnce(this);
_configured = true;
}
protected override IEnumerable<Assembly> SelectAssemblies()
{
// SelectAssemblies() is called before Configure(), so manually force Configure() to run first so that MEF is instantiated properly
Configure();
if (!_configured)
{
throw new Exception("Unable to configure assemblies");
}
List<Assembly> assemblies = new List<Assembly>();
assemblies.Add(Assembly.GetExecutingAssembly());
// Need to add all module assemblies so that Caliburn will be able to find the View for a ViewModel
foreach(IMyModule myModule in _myModules)
{
Assembly assembly = myModule.GetType().Assembly;
assemblies.Add(assembly);
}
return assemblies.Distinct();
}
This works just fine to get a module to be displayed properly.
But when a module has used an image, this image is never displayed, because this kind of loading apparently doesn't take resources into account.
I create a Resources.resx file in the module project and add an image to that. The image file that is presented in Visual Studio then has a Build Action that says "Resource" and "Do not copy (to output directory)". This should mean that the image is embedded in the resulting DLL file.
The image is placed in a folder called "Resources" in the module project, and the XAML use it like this:
<Image Source="/Resources/myImage.png" />
The image is displayed in the preview in Visual Studio, but is not displayed when the application runs.
What I have tried that didn't work
Referencing the image in another way: <Image Source="pack://application:,,,/Resources/myImage.png" />
Getting the resources in BAML form and reinserting them into the executing assembly, like in this question: Instantiate ResourceDictionary xaml from other Assembly (which causes an OutOfMemoryException on this line var reader = new Baml2006Reader(stream);)
A lot of other answers that reference ResourceDictionary, but I have a Resource.resx file (which only generates an internal class that is not a ResourceDictionary)
The question remains
How can I get WPF/Caliburn.Micro to recognize resources from a DLL loaded by MEF?

Answer
Use this syntax for the Source property for images with Build Action: Resource
<Image Source="/AssemblyName;component/Resources/MyImage.png" />
Where AssemblyName is the name of the assembly (as defined in the project properties), and /Resource/MyImage.png is the path to the image (as defined in the project). component must always be present.
Side note
After a lot of help from #StepUp I initially decided to ask a new question using what was learned from this question and rephrasing everything to be more specific to my problem.
When writing this new question I ended up googling for phrases and commands that might help with the rephrasing, and I stumbled upon this page: http://www.geekchamp.com/tips/wp7-working-with-images-content-vs-resource-build-action
Apparently, the WPF Image control has a ton of ways to define the Source property. I had already tried quite a lot of various Source inputs and thought I had tried them all, but the page linked to above proved me wrong.
As far as I have been able to test, the syntax described above seems to work for images marked with Build Action: Resource. Therefore I no longer need to have a RESX file for the images, and I do not need any special handling when bootstrapping MEF.

At first, you should read the assembly with Style. Then, it is neccessary to read BAML files from external library using Baml2006Reader. Let me show an example:
private GetResourceDictionary()
{
string address = #"WpfCustomControlLibrary1.dll";
Assembly skinAssembly = Assembly.LoadFrom(address);
string[] resourceDictionaries = skinAssembly.GetManifestResourceNames();
Stream bamlStream = null;
string name = "themes/AllStylesDictionary.baml";//themes/AllStylesDictionary.baml
foreach (string resourceName in resourceDictionaries)
{
ManifestResourceInfo info = skinAssembly.GetManifestResourceInfo(resourceName);
if (info.ResourceLocation != ResourceLocation.ContainedInAnotherAssembly)
{
Stream resourceStream = skinAssembly.GetManifestResourceStream(resourceName);
using (ResourceReader reader = new ResourceReader(resourceStream))
{
foreach (DictionaryEntry entry in reader)
{
if (entry.Key.ToString().Equals(name.ToLower()))
{
bamlStream = entry.Value as Stream;
}
}
}
}
}
ResourceDictionary rd = LoadBaml<ResourceDictionary>(bamlStream);
Application.Current.Resources.MergedDictionaries.Add(rd);
Style style = Application.Current.Resources.MergedDictionaries[0]["myStyle"] as Style;
button.Style = style;
}
and:
public static T LoadBaml<T>(Stream stream)
{
var reader = new Baml2006Reader(stream);
var writer = new XamlObjectWriter(reader.SchemaContext);
while (reader.Read())
writer.WriteNode(reader);
return (T)writer.Result;
}
Update:
If you want to load an image from another libary, you should use the following code:
yourImage.Source = new Bitmap(System.Reflection.Assembly.GetEntryAssembly().
GetManifestResourceStream("MyProject.Resources.myimage.png"));
Update1:
To load an image from the external dll.
foreach (DictionaryEntry entry in reader)
{
if (entry.Key.ToString().Equals(name.ToLower()))
{
bamlStream = entry.Value as Stream;
BitmapImage bmp = LoadImage(bamlStream);
img.Source = bmp;
}
}
public static BitmapImage LoadImage(Stream stream)
{
BitmapImage bmi;
using (MemoryStream ms1 = new MemoryStream())
{
stream.CopyTo(ms1);
bmi = new BitmapImage();
bmi.BeginInit();
bmi.StreamSource = new MemoryStream(ms1.ToArray());
bmi.EndInit();
}
return bmi;
}

Related

Reflection only assembly loading in .Net Core

I have a .Net Framework WPF application that I'm currently migrating to .Net6. At startup it examines certain assemblies in the executable folder looking for any with a custom assembly attribute. Those that have this are then loaded into the current appdomain. (Note that some of these assemblies may already be in the appdomain, as they are projects in the running application's solution).
This is the 4.x code:
private void LoadAssemblies(string folder)
{
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve +=
(s, e) => Assembly.ReflectionOnlyLoad(e.Name);
var assemblyFiles = Directory.GetFiles(folder, "*.Client.dll");
foreach (var assemblyFile in assemblyFiles)
{
var reflectionOnlyAssembly = Assembly.ReflectionOnlyLoadFrom(assemblyFile);
if (ContainsCustomAttr(reflectionOnlyAssembly))
{
var assembly = Assembly.LoadFrom(assemblyFile);
ProcessAssembly(assembly);
}
}
}
The custom assembly attribute (that this code is looking for) has a string property containing a path to a XAML resource file within that assembly. The ProcessAssembly() method adds this resource file to the application's merged dictionary, something like this:
var resourceUri = string.Format(
"pack://application:,,,/{0};component/{1}",
assembly.GetName().Name,
mimicAssemblyAttribute.DataTemplatePath);
var uri = new Uri(resourceUri, UriKind.RelativeOrAbsolute);
application.Resources.MergedDictionaries.Add(new ResourceDictionary { Source = uri });
Just to reiterate, all this works as it should in the .Net 4.x application.
.Net6 on the other hand doesn't support reflection-only loading, nor can you create a second app domain in which to load the assemblies. I rewrote the above code by loading the assemblies being examined into what I understand is a temporary, unloadable context:
private void LoadAssemblies(string folder)
{
var assemblyFiles = Directory.GetFiles(folder, "*.Client.dll");
using (var ctx = new TempAssemblyLoadContext(AppDomain.CurrentDomain.BaseDirectory))
{
foreach (var assemblyFile in assemblyFiles)
{
var assm = ctx.LoadFromAssemblyPath(assemblyFile);
if (ContainsCustomAttr(assm))
{
var assm2 = Assembly.LoadFrom(assemblyFile);
ProcessAssembly(assm2);
}
}
}
}
private class TempAssemblyLoadContext : AssemblyLoadContext, IDisposable
{
private AssemblyDependencyResolver _resolver;
public TempAssemblyLoadContext(string readerLocation)
: base(isCollectible: true)
{
_resolver = new AssemblyDependencyResolver(readerLocation);
}
public void Dispose()
{
Unload();
}
protected override Assembly Load(AssemblyName assemblyName)
{
var path = _resolver.ResolveAssemblyToPath(assemblyName);
if (path != null)
{
return LoadFromAssemblyPath(path);
}
return null;
}
protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
{
var path = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
if (path != null)
{
return LoadUnmanagedDllFromPath(path);
}
return IntPtr.Zero;
}
}
(Note the ProcessAssembly() method is unchanged).
This code "works" in so much as it goes through the motions without crashing. However at a later point when the application starts creating the views, I get the following exception:
The component '..ModeSelectorView' does not have a resource identified by the URI '/.;component/views/modeselector/modeselectorview.xaml'.
This particular view resides in a project of this application's solution, so the assembly will already be in the appdomain. The assembly also contains that custom attribute so the above code will be trying to load it, although I believe that Assembly.LoadFrom() should not load the same assembly again?
Just in case, I modified the "if" block in my LoadAssemblies() method to ignore assemblies already in the app domain:
if (ContainsCustomAttr(assm) && !AppDomain.CurrentDomain.GetAssemblies().Contains(assm))
Sure enough, a breakpoint shows that the assembly in question (containing that view) is ignored and not loaded into the app domain. However I still get the same exception further down the line.
In fact I can comment out the entire "if" block so no assemblies are being loaded into the app domain, and I still get the exception, suggesting that it's caused by loading the assembly into that AssemblyLoadContext.
Also, a breakpoint shows that context is being unloaded via its Dispose() method, upon dropping out of the "using" block in the LoadAssemblies() method.
Edit: even with the "if" block commented out, a breakpoint at the end of the method shows that all the assemblies being loaded by ctx.LoadFromAssemblyPath() are ending up in AppDomain.Current. What am I not understanding? Is the context part of the appdomain and not a separate "area"? How can I achieve this "isolated" loading of assemblies in a similar way to the "reflection only" approach that I was using in .Net 4.x?
Okay, so I found the answer, which is to use MetadataLoadContext. This is essentially the .Net Core replacement for reflection-only loading:
private void LoadAssemblies(string folder)
{
// The load context needs access to the .Net "core" assemblies...
var allAssemblies = Directory.GetFiles(RuntimeEnvironment.GetRuntimeDirectory(), "*.Client.dll").ToList();
// .. and the assemblies that I need to examine.
var assembliesToExamine = Directory.GetFiles(folder, "NuIns.CoDaq.*.Client.dll");
allAssemblies.AddRange(assembliesToExamine);
var resolver = new PathAssemblyResolver(allAssemblies);
using (var mlc = new MetadataLoadContext(resolver))
{
foreach (var assemblyFile in assembliesToExamine)
{
var assm = mlc.LoadFromAssemblyPath(assemblyFile);
if (ContainsCustomAttr(assm))
{
var assm2 = Assembly.LoadFrom(assemblyFile);
AddMimicAssemblyInfo(assm2);
}
}
}
}

Assembly loading in Visual Studio extension

I'm writing a visual studio extension and I'm completely confused about how and where and when it's loading assemblies. What I have is this:
My Visual Studio extension project (let's call it MyExtension) references several assemblies including an assembly called Foo.dll.
MyExtension contains a class called FooManager that will be instantiated in response to a menu item being clicked.
When FooManager is instantiated it is passed the output path of a project in the current solution and it creates an AppDomain which should load that assembly, something like this:
public FooManager(string assemblyPath)
{
// The actual ApplicationBase of the current domain will be the one of VS and
// not of my plugin
// We need our new AppDomain to be able to find our assemblies
// without this even the CreateInstanceAndUnwrap will fail
var p = System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
var cachePath = Path.Combine(p, "Cache");
var pluginPath = Path.Combine(p, "Test");
if (Directory.Exists(cachePath))
{
Directory.Delete(cachePath, true);
}
if (Directory.Exists(pluginPath))
{
Directory.Delete(pluginPath, true);
}
Directory.CreateDirectory(cachePath);
Directory.CreateDirectory(pluginPath);
var newPath = Path.Combine(pluginPath, Path.GetFileName(assemblyPath));
File.Copy(assemblyPath, newPath, true);
var setup = new AppDomainSetup()
{
ApplicationBase = p,
ShadowCopyFiles = "true",
ShadowCopyDirectories = pluginPath,
CachePath = cachePath
};
domain = AppDomain.CreateDomain("MyTest_AppDomain", AppDomain.CurrentDomain.Evidence, setup);
// FooCollection is defined in MyExtension. but has several references
// to things defined in Foo.dll - it used MEF to load the assembly
// referenced by pluginPath
collection = domain.CreateInstanceAndUnwrap(
typeof(FooCollection).Assembly.FullName,
typeof(FooCollection).FullName,
false,
BindingFlags.Default,
null,
new object[] { pluginPath }, null, null) as FooCollection;
}
Now one property of FooManager looks like this (FooInfo is defined in
Foo.dll):
public IEnumerable<FooInfo> Spiders
{
get
{
return collection.Foos.Select(s => s.Metadata);
}
}
But when I try to access that I get a System.ArgumentException with the message Object type cannot be converted to target type. which I know happens if two copies of the same assembly are loaded from different locations, and I think, ultimately, that's what's happening here, but I can't figure out how to get it to load from the same place.
So after struggling with this a lot (and the above is only my latest attempt), I thought maybe I could serialize to byte[] and then deserialize again as a way to avoid the problem with types, so I tried something like this:
var msgBytes = collection.SerializeFooInfo();
var msg = FooInfo.DeserializeMessage(msgBytes);
Where my serialize and deserialize just use a BinaryFormatter (the classes are marked as Serializable). The serialization seems to work, but on deserialization, when I get to here:
public static List<FooInfo> DeserializeMessage(byte[] source)
{
using (var stream = new MemoryStream(source))
{
BinaryFormatter formatter = new BinaryFormatter();
var msg = formatter.Deserialize(stream);
return msg as List<FooInfo>;
}
}
msg comes back as null. If I try to run it in the debugger using the immediate window, I see that Deserialize threw a FileNotFoundException with the message:
Cannot load assembly 'C:\Users\matt.burland\AppData\Local\Microsoft\VisualStudio\14.0Exp\ProjectAssemblies\qesxy6ms01\Foo.dll'
But I don't understand where that path came from. It's not where my extension has been installed, which is C:\Users\matt.burland\AppData\Local\Microsoft\VisualStudio\14.0Exp\Extensions\MyCompany\FooTools\1.0 and was set as the ApplicationBase for my AppDomain and contains the file foo.dll. So why is it trying to load from the other mystery location? The other location seems to be created dynamically and contains only the foo.dll assembly.
I've do something very similar to this in a windows service (and using a lot of the same classes) and it works just fine, so this seems to be something particular to the way Visual Studio extensions. Can anybody shine a light here?
So I thought this might help: http://geekswithblogs.net/onlyutkarsh/archive/2013/06/02/loading-custom-assemblies-in-visual-studio-extensions-again.aspx
But if I try to attach an AssemblyResolve handler in package class as suggested it doesn't get called for anything interesting (which isn't surprising, it's not the domain I'm try to load from), but if I try to attach to the new domain I create then if I try something like this:
domain.AssemblyResolve += OnAssemblyResolve;
Then it fails because my FooManager isn't marked as serializable. So I created a proxy just for binding the AssemblyResolve, but the AssemblyResolve never fires. So I tried not setting the ApplicationBase when creating my domain, thinking that would force it to have to try to resolve, but then I can't create my proxy class in the created domain because it doesn't know where to load the assembly from!.

Using MEF to load DLLs with embedded libraries

I am currently writing an application suite with a plugin system that loads plugins at runtime using the MEF framework.
I have currently setup one of my top level WPF applications to embed it's referenced DLLs as embedded resources and load them at runtime using the method described here.
This works fine and I get my single file WPF application that runs fine.
However, another of my top level console applications uses the MEF framework to load plugins at runtime (the WPF application is fixed and includes the plugins explicitly). My plugins have several dependencies themselves on various libraries and the extensions folder that the console application loads the plugins from is littered with all the various library dlls.
I would like to embed the dependencies of each plugin within itself so that my extensions directory contains only the top level DLL files. The method that I have used above does not cater for this approach as the plugin component cannot find the required dependency as it is only the executing assembly that is being searched for these embedded resources.
My current OnResolveAssembly method looks like this:
public static Assembly OnResolveAssembly(object sender, ResolveEventArgs args)
{
Assembly executingAssembly = Assembly.GetExecutingAssembly();
var assemblyName = new AssemblyName(args.Name);
string path = assemblyName.Name + ".dll";
if (assemblyName.CultureInfo.Equals(CultureInfo.InvariantCulture) == false)
{
path = String.Format(#"{0}\{1}", assemblyName.CultureInfo, path);
}
using (Stream stream = executingAssembly.GetManifestResourceStream(path))
{
if (stream == null)
return null;
var assemblyRawBytes = new byte[stream.Length];
stream.Read(assemblyRawBytes, 0, assemblyRawBytes.Length);
return Assembly.Load(assemblyRawBytes);
}
}
I'm thinking that the best way to proceed would be to add in functionality to keep track of all assemblies loaded in a list and once a new assembly has been loaded in this way, recursively do the same; load any embedded DLLs within those as you go. You can then add these DLLs to the list which will act as a cache.
Is there perhaps a better way to proceed with this?
I have implemented a very similar solution to yours and it works very fine for me. As you can see I keep track of already loaded assemblies in a _references dictionary.
In my case, I do not need to "eagerly" load all embedded dependencies in any recursive way, but rather my embedded assemblies do register themselves with the application host on-demand.
public static class ApplicationHost
{
private static readonly Dictionary<string, Assembly> _references = new Dictionary<string, Assembly>();
[STAThread]
private static void Main()
{
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => _references.ContainsKey(args.Name) ? _references[args.Name] : null;
RegisterAssemblyAndEmbeddedDependencies();
// continue application bootstrapping...
}
public static void RegisterAssemblyAndEmbeddedDependencies()
{
var assembly = Assembly.GetCallingAssembly();
_references[assembly.FullName] = assembly;
foreach (var resourceName in assembly.GetManifestResourceNames())
{
using (var resourceStream = assembly.GetManifestResourceStream(resourceName))
{
var rawAssembly = new byte[resourceStream.Length];
resourceStream.Read(rawAssembly, 0, rawAssembly.Length);
var reference = Assembly.Load(rawAssembly);
_references[reference.FullName] = reference;
}
}
}
}

Compiling an ASPX page into a standalone program

As I mentioned here, I'm trying to generate HTML from an ASPX page inside a WinForms.
I'm trying to compile the ASPX page directly into the EXE; I'd like to be able to write something like this:
var page = new ASP.MyPageName();
var stringWriter = new StringWriter();
using(var htmlWriter = new HtmlTextWriter(stringWriter))
page.RenderControl(htmlWriter);
I added an ASPX page, set the Build Action to Compile, and put in the following Page declaration:
<%# Page Language="C#" ClassName="MyPageName" %>
The code compiles, and the properties that I defined in the ASPX are usable from the calling code, but the StringWriter remains empty. I tried calling htmlWriter.Flush, and it didn't help.
The page instance's Controls collection is empty, and it probably shouldn't be.
I looked at the EXE in Reflector and I couldn't find the page content anywhere. I therefore assume that the page isn't being compiled properly.
What is the correct way to do this?
I ended up using ApplicationHost.CreateApplicationHost to run the entire application in the ASP.Net AppDomain. This is far simpler and more reliable than my attempt to fake the ASP.Net AppDomain.
Note: In order to do this, you must put a copy of your EXE file (or whatever assembly contains the type passed to CreateApplicationHost) in your ASP.Net folder's Bin directory. This can be done in a post-build step. You can then handle AssemblyResolve to locate other assemblies in the original directory.
Alternatively, you can place the program itself and all DLLs in the ASP.Net's Bin directory.
NOTE: WinForms' Settings feature will not work in an ASP.Net AppDomain.
I believe what you want to use is the SimpleWorkerRequest.
Unfortunately, however, it requires that the resource (I believe) live on disk. From your description it sounds like you prefered for the whole app to reside in your DLL. If that is the case you will most likely need to implement your own HttpWorkerRequest.
Warning
This does not work reliably, and I've given up on it.
I ended up copying the files to the output folder and initializing ASP.Net in same AppDomain, using the following code: (I tested it; it sometimes works)
static class PageBuilder {
public static readonly string PageDirectory = Path.Combine(Path.GetDirectoryName(typeof(PageBuilder).Assembly.Location), "EmailPages");
static bool inited;
public static void InitDomain() {
if (inited) return;
var domain = AppDomain.CurrentDomain;
domain.SetData(".appDomain", "*");
domain.SetData(".appPath", PageDirectory);
domain.SetData(".appVPath", "/");
domain.SetData(".domainId", "MyProduct Domain");
domain.SetData(".appId", "MyProduct App");
domain.SetData(".hostingVirtualPath", "/");
var hostEnv = new HostingEnvironment();//The ctor registers the instance
//Ordinarily, the following method is called from app manager right after app domain (and hosting env) is created
//Since CreateAppDomainWithHostingEnvironment is never called here, I need to call Initialize myself.
//Here is the signaature of the method.
//internal void Initialize(ApplicationManager appManager, IApplicationHost appHost, IConfigMapPathFactory configMapPathFactory, HostingEnvironmentParameters hostingParameters) {
var cmp = Activator.CreateInstance(typeof(HttpRuntime).Assembly.GetType("System.Web.Hosting.SimpleConfigMapPathFactory"));
typeof(HostingEnvironment).GetMethod("Initialize", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(hostEnv, new[] { ApplicationManager.GetApplicationManager(), null, cmp, null });
//This must be done after initializing the HostingEnvironment or it will initialize the config system.
SetDefaultCompilerVersion("v3.5");
inited = true;
}
static void SetDefaultCompilerVersion(string version) {
var info = CodeDomProvider.GetCompilerInfo("c#");
var options = (IDictionary<string, string>)typeof(CompilerInfo).GetProperty("ProviderOptions", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(info, null);
options["CompilerVersion"] = version;
}
public static TPage CreatePage<TPage>(string virtualPath) where TPage : Page {
return BuildManager.CreateInstanceFromVirtualPath(virtualPath, typeof(TPage)) as TPage;
}
//In a base class that inherits Page:
internal string RenderPage() {
var request = new SimpleWorkerRequest("", null, null);
ProcessRequest(new HttpContext(request));
using (var writer = new StringWriter(CultureInfo.InvariantCulture)) {
using (var htmlWriter = new HtmlTextWriter(writer))
RenderControl(htmlWriter);
return writer.ToString();
}
}
InitDomain must be called right when the program starts; otherwise, it throws an exception about the configuration system being already initialized.
Without the call to ProcessRequest, the page's Controls collection is empty.
UPDATE: The page is rendered during the call to ProcessRequest, so that must be done after manipulating the Page instance.
This code will not work if the program has a .config file; I made a method to set the default C# compiler version without a .config file using reflection.
Why dont you just look at hosting the ASP.NET runtime in your app?
There are several snippets online to show you how.
Here is one.
Most likely you are using wrong page class. You need to use not the actual nice-named class in code behind. During compilation ASP.NET generates page class, which inherits from class defined in code behind and within this class happens initialization of all the controls. Therefore you should use generated class (check its name using Reflector).
If you're looking for the MVC version of this answer, see:
Is there a way to process an MVC view (aspx file) from a non-web application?
The code uses a separate AppDomain, but as far as I could tell, this is required as all the code generated from an ASPX file depends on HttpContext and HostingEnvironment.VirtualPathProvider.
public class AspHost : MarshalByRefObject
{
public string _VirtualDir;
public string _PhysicalDir;
public string AspxToString(string aspx)
{
StringBuilder sb = new StringBuilder();
using (StringWriter sw = new StringWriter(sb))
{
using (HtmlTextWriter tw = new HtmlTextWriter(sw))
{
var workerRequest = new SimpleWorkerRequest(aspx, "", tw);
HttpContext.Current = new HttpContext(workerRequest);
object view = BuildManager.CreateInstanceFromVirtualPath(aspx, typeof(object));
Page viewPage = view as Page;
if (viewPage == null)
{
UserControl viewUserControl = view as UserControl;
if (viewUserControl != null)
{
viewPage = new Page();
viewPage.Controls.Add(viewUserControl);
}
}
if (viewPage != null)
{
HttpContext.Current.Server.Execute(viewPage, tw, true);
return sb.ToString();
}
throw new InvalidOperationException();
}
}
}
public static AspHost SetupFakeHttpContext(string physicalDir, string virtualDir)
{
return (AspHost)ApplicationHost.CreateApplicationHost(
typeof(AspHost), virtualDir, physicalDir);
}
}
Then, to render a file:
var host = AspHost.SetupFakeHttpContext("Path/To/Your/AspNetApplication", "/");
String rendered = host.AspxToString("~/Views/MyView.aspx");
you can use the ClienBuildManager class to compile ASPX files.

Getting image names from DLL as a List?

I created a DLL for encapsulating my Images and after that I want to get image names from DLL as a list. Before posting this post I googled about it and I saw an example that is below.
public static List<string> GetImageList()
{
List<string> imageList;
System.Reflection.Assembly BOAUIResources = System.Reflection.Assembly.GetExecutingAssembly();
string[] resources = BOAUIResources.GetManifestResourceNames();
return resources.ToList<string>();
}
This code just accessing image names that build action property is "embedded resource". because of accessing in WPF, my images build action type must define as "resource".
So How can I list image names, that build action property is defined as resource, from DLL ?
Image resources can be added to an assembly in a couple of different ways, that will have some impact on the code to enumerate the image names.
You can add images to a resx file.
You can add the images directly to the solution (as with your code files), and set their build action to 'Embedded Resource'.
The code sample that you supplied in your question will work in the second case. Note however that it will also list any other manifest resources (such as embedded resx files) and not just your images.
If you have added the images to a resx file you can enumerate resources using a ResourceSet obtained from a ResourceManager:
// This requires the following using statements in the file:
// using System.Resources;
// using System.Collections;
ResourceManager rm = new ResourceManager(typeof(Images));
using (ResourceSet rs = rm.GetResourceSet(Thread.CurrentThread.CurrentUICulture, true, true))
{
IDictionaryEnumerator resourceEnumerator = rs.GetEnumerator();
while (resourceEnumerator.MoveNext())
{
if (resourceEnumerator.Value is Image)
{
Console.WriteLine(resourceEnumerator.Key);
}
}
}
In the first line, where it says ResourceManager(typeof(Images)), you will need to exchange Images with the name of the resource file i which your images are located (in my sample, it was called "Images.resx").
Try this. (Taken from the book - Programming WPF By Chris Sells, Ian Griffiths)
public static List<string> GetImageList()
{
System.Reflection.Assembly asm = System.Reflection.Assembly.GetExecutingAssembly();
System.Globalization.CultureInfo culture = Thread.CurrentThread.CurrentCulture;
string resourceName = asm.GetName().Name + ".g";
System.Resources.ResourceManager rm = new System.Resources.ResourceManager(resourceName, asm);
System.Resources.ResourceSet resourceSet = rm.GetResourceSet(culture, true, true);
List<string> resources = new List<string>();
foreach (DictionaryEntry resource in resourceSet)
{
resources.Add((string)resource.Key);
}
rm.ReleaseAllResources();
return resources;
}

Categories

Resources