Caliburn Micro - View & viewmodel in separate DLL - c#

I've been trying this for a while and i have some issues. I have a project which dynamically loads 1 or more DLLs and I can't get the view binding to work.
I've overridden the SelectAssemblies method as such:
protected override IEnumerable<Assembly> SelectAssemblies()
{
string[] AppFolders = Directory.GetDirectories(Config.AppsFolder);
List<Assembly> assemblies = new List<Assembly>();
assemblies.Add(Assembly.GetExecutingAssembly());
foreach (string f in AppFolders)
{
Assembly ass = Directory.GetFiles(f, "*.dll", SearchOption.AllDirectories).Select(Assembly.LoadFrom).SingleOrDefault();
if (ass != null)
{
assemblies.Add(ass);
}
}
Apps = assemblies;
return assemblies;
}
This works as intended, i then have a method which runs on a button click which does:
public void OpenApp(string appName)
{
//AppName should be the same as the dll.
string assName = string.Format("TabletApp.{0}", appName);
Assembly ass = AppBootstrapper.Apps.SingleOrDefault(x => x.GetAssemblyName() == assName);
if (ass != null)
{
dynamic vm = ass.CreateInstance(string.Format("TabletApp.{0}.ViewModels.{0}ViewModel", appName));
IoC.Get<IWindowManager>().ShowDialog(vm);
}
}
This finds the viewmodel fine, however i get the error "unable to find contract for 'ExampleView'" when i load ExampleViewModel. I've also had to add [Export(typeof(view)] for each view in the base assembly since I've made this changes. It appears that Caliburn micro has stopped initialising views automatically.
Anyone know what i've done wrong?

So it turns out i was doing nothing wrong, Along the way I've updated my caliburn.micro to 3.0.2. As it turns out a small change they made became a major breaking update. I wont go into it fully here other than to point out its the GetInstance in the bootstrapper that needs to be changed.
protected override object GetInstance(Type service, string key)
{
// Skip trying to instantiate views since MEF will throw an exception
if (typeof(UIElement).IsAssignableFrom(service))
return null;
var contract = string.IsNullOrEmpty(key) ? AttributedModelServices.GetContractName(service) : key;
var exports = container.GetExportedValues<object>(contract);
if (exports.Any())
return exports.First();
throw new Exception(string.Format("Could not locate any instances of contract {0}.", contract));
}
Please review the following link for more detailed information.
https://github.com/Caliburn-Micro/Caliburn.Micro/pull/339

Related

How to shim property being set on a 3rd party external dll

I am working on writing a unit test for the following function..
public virtual FacadeClass InitNewAMSObject()
{
FacadeClass output = null;
if (ClassMapInfo != null)
{
// Override the metadata caching option
// sets property in external dll
Avectra.netForum.Common.Config.CacheMetaData = false;
if (!String.IsNullOrEmpty(ClassMapInfo.AMSClassName))
{
output = DataUtils.InstantiateFacadeObject(ClassMapInfo.AMSClassName);
}
}
else
{
throw new System.ApplicationException("Need to add the attribute");
}
return output;
}
I cannot get past the line with the comment "sets property in external dll". My shim never seems to 'make it over' to the function being tested. It always throws an error, and its actually trying to use the dll instead of my shim. It may look like i'm setting a property to false only, but the dll uses the property like a method in it's Setter. The .Config is always in an errored state because its constructors (in the dll) are attempting to set up database connections. I expect it to be in at least a null state, and also have the .Fakes. in the class name. I just want to skip over it because I do not write this dll, its 3rd party.
I've researched for a day now and cannot find an example anywhere of how to shim a property that is set on a referenced dll.
Here is my current test for what its worth
[TestMethod]
public void InitNewAMSObjectTEST()
{
using (ShimsContext.Create())
{
Address amsc = new Address();
bool test = true;
ShimConfig.CacheMetaDataGet = () => test;
ShimConfig.CacheMetaDataSetBoolean = value => test = value;
amsc.InitNewAMSObject();
}
}

How to get the value of a property of a class in a non executing assembly through reflection

I'm having trouble while getting the value of the text property in a non-executing assembly; I read an assembly from disk via reflection, then i get all classes in the assembly to search for the Text property in a windows form class which is initialized by win forms designer. So far i have the following code:
static void Main(string[] args)
{
Assembly asm = Assembly.LoadFrom(Path.Combine(path, "Assembly.exe"));
PropertyInfo[] props;
foreach (Type t in asm.GetTypes())
{
var value = t.GetProperty("Text").GetValue(/*Not sure what to put here*/)
}
}
And this is how the designer generated the form
Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None
Me.BackColor = System.Drawing.Color.FromArgb(CType(CType(0, Byte), Integer), CType(CType(128, Byte), Integer), CType(CType(128, Byte), Integer))
Me.ClientSize = New System.Drawing.Size(234, 181)
Me.Cursor = System.Windows.Forms.Cursors.Default
Me.Font = New System.Drawing.Font("Arial", 8.25!, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, CType(0, Byte))
Me.ForeColor = System.Drawing.SystemColors.WindowText
Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog
Me.Location = New System.Drawing.Point(581, 222)
Me.MaximizeBox = False
Me.MinimizeBox = False
Me.Name = "winform"
Me.RightToLeft = System.Windows.Forms.RightToLeft.No
Me.StartPosition = System.Windows.Forms.FormStartPosition.Manual
Me.Text = "Title"
Me.fraDías.ResumeLayout(False)
Me.ResumeLayout(False)
Keep in mind that the assembly is on disk and non-executing and that I want to retrieve the value of the Text property of every winform (I guess it should be somewhere hardcoded in the assembly since it was generated by the winforms designer)
Please tell me if this is possible, thanks!
Your requirements are contradictory, when you load an aseembly via reflection, and instantiate an object or try to get a property value, what happens is that some code begins to run, there is no way around that.
Remember that properties are just "syntax sugar" for a pair of methods, the getter and setter. Their current value is nothing but the value returned by the getter method, and when you change its value, you're in fact calling its setter method. So, to retrieve property values, you must make some code to run, even if it's a trivial get method.
I think maybe your confusion comes from the fact that you're using a designer to create the form. Particularly with the WinForms designer (WPF for instance is substantially different), all it does is to autogenerate some code for you. Setting properties, placing and moving controls around, what's happening under the hood is that it writes code that replicate your actions at runtime, specifically, it codes the InitializeComponent method. The real property value is set when the constructor is called (that in turn calls InitializeComponent), and then you may read/change using many properties.
What you would need to read those designer attributes is that those were hardcoded in some form of metadata, so that it's simply read as data and not as the result of code execution. That's not the case with WinForms, as it "saves" the form as code.
You cannot read a property of a class that has not been instantiated! The parameter you are missing is an instance of your type.
You must create an instance of the type with object o = Activator.CreateInstance(type); before accessing its members (unless they are static).
Your problem is related to how add-ins (plug-ins) can be loaded at runtime.
Here is how I made an Add-In Loader. Below, I will explain how you can adapt it to your problem. Add-Ins have to implement the IAddIn interface in my example. You are totally free in the definition of IAddIn. You could define it like this:
public interface IAddIn
{
bool OnLoad();
string Version { get; set; }
string Text { get; set; }
}
This allows you to access members without reflection.
public class AddInLoader
{
// Loads all non abstract types implementing IAddIn in all DLLs found in a folder.
public IList<IAddIn> Load(string folder)
{
var addIns = new List<IAddIn>();
string[] files = Directory.GetFiles(folder, "*.dll");
foreach (string file in files) {
addIns.AddRange(LoadFromAssembly(file));
}
return addIns;
}
// Loads all non abstract types implementing IAddIn found in a file.
private static IEnumerable<IAddIn> LoadFromAssembly(string fileName)
{
Assembly asm = Assembly.LoadFrom(fileName);
string addInInterfaceName = typeof(IAddIn).FullName;
foreach (Type type in asm.GetExportedTypes()) {
Type interfaceType = type.GetInterface(addInInterfaceName);
if (interfaceType != null &&
(type.Attributes & TypeAttributes.Abstract) != TypeAttributes.Abstract){
IAddIn addIn = (IAddIn)Activator.CreateInstance(type);
addIn.Version = asm.GetName().Version.ToString();
yield return addIn;
}
}
}
}
Now you can load and access the add-ins like this:
var loader = new AddInLoader();
IList<IAddIn> addIns = loader.Load(folderPath);
foreach (IAddIn addIn in addIns) {
if (addIn.OnLoad()) {
Console.WriteLine("Version = {0}, Text = {1}", addIn.Version, addIn.Text);
}
}
Reading the titles of Forms at runtime:
You can easily adapt this example. Instead of searching for types implementing an interface, search for types deriving from System.Windows.Forms.Form.
private static IEnumerable<Form> LoadFormsFromAssembly(string fileName)
{
Assembly asm = Assembly.LoadFrom(fileName);
foreach (Type type in asm.GetExportedTypes()) {
if (typeof(Form).IsAssignableFrom(type) &&
(type.Attributes & TypeAttributes.Abstract) != TypeAttributes.Abstract) {
Form form = (Form)Activator.CreateInstance(type);
yield return form;
}
}
}
Now you can get the texts of the forms like this:
var forms = LoadFormsFromAssembly(path);
foreach (Form frm in forms) {
Console.WriteLine(frm.Text);
}
Note: You must instantiate the forms, however you do not need to open (show) them. The code works only if the forms have a default constructor, i.e. a constructor without parameters.
You need an instance object for that type to get the value of a property.
It looks like you just want to check if a type has a "Text" property or not. You can to it by checking
bool hasTextProperty = t.GetProperty("Text") !=null;

Serialize a domain type of Type

I have some domain classes having a property of type Type (the class).
The user can select any class loaded in his project using a custom TypeBrowserEditor.
The serialization works fine, I serialize as myType.AssemblyQualifiedName
But during the deserialization, Type.GetType(str) returns null since the assembly isn't loaded in the app domain.
I can't do AssemblyLoad(str) because he won't find the file.
I need to have access to the IVSHierarchy and enumerate through the References of the user's project. But i can't find a way to have access to it in my DomainPropertyXmlSerializer. If anyone can point me to the right direction for a service provider or anyway to let me connect to the VS current project it would be great.
Thanks in advance.
Edit : I could worst case scenario only work with String and just cast it in my type editor since i can have access to the IVSHierarchy there but I don't really like that solution.
Ok I managed to do it !
In the DslPackage, make a DocData.cs, and create a part for the MyLanguageDocData class (partial)
then in it :
protected override void OnDocumentLoading(EventArgs e)
{
mRes = new ResolveEventHandler(CustomAssemblyResolverDocData);
availableTypes = new Dictionary<string, Type>();
availableAssemblies = new Dictionary<string, Assembly>();
PreloadAssemblies();
if (availableAssemblies.Count == 0)
throw new Exception("Problem");
base.OnDocumentLoading(e);
AppDomain.CurrentDomain.TypeResolve += mRes;
AppDomain.CurrentDomain.AssemblyResolve += mRes;
}
The exception happens when the designer is opened before the solution is loaded (happens when you closed visual studio while editing your design).
Throwing an exception here will prevent the load from happening silently.
In the PreloadAssemblies :
IVsHierarchy hier = VsHelper.ToHierarchy(dteProject);
DynamicTypeService typeService = (DynamicTypeService)this.GetService(typeof(DynamicTypeService));
ITypeDiscoveryService discovery = typeService.GetTypeDiscoveryService(hier);
try
{
foreach (Type type in discovery.GetTypes(typeof(object), true))
{
if (!availableTypes.ContainsKey(type.FullName))
{
availableTypes.Add(type.FullName, type);
}
if (!availableAssemblies.ContainsKey(type.Assembly.GetName().Name))
{
availableAssemblies.Add(type.Assembly.GetName().FullName, type.Assembly);
}
}
}
catch (Exception e)
{
}
And in the resolver just check if the assembly name is in the dictionary. otherwise return null.
override the DocumentClosed as well to remove the Assembly resolver :)

C# - Loading dlls and creating instances

I have a situation and I need to know how to deal with it in the best way.
I have an application (MVC3) and I have couple of integrations for it. I have an interface "IntegrationInterface" and every integration implements it.
I want to load the dlls of the integrations, create a list of them, and run a loop that runs a method for every integration in the list.
For example - let's say I have integrations for facebook, myspace and twitter (for my appliction), and every time the user post a message in my application I want to post a message on his\her facebook, myspace and twitter.
I don't want that the code will know which integrations I have, so if tomorrow I'll create a new integration for google+, I'll just need to add a new DLL without changing the code of my application.
How can I do that?
First, you'll have to find all relevant dlls and classes:
loadedIntegrations.Clear();
if (!Directory.Exists(path))
return;
DirectoryInfo di = new DirectoryInfo(path);
FileInfo[] files = di.GetFiles("*.dll");
foreach (var file in files)
{
Assembly newAssembly = Assembly.LoadFile(file.FullName);
Type[] types = newAssembly.GetExportedTypes();
foreach (var type in types)
{
//If Type is a class and implements the IntegrationInterface interface
if (type.IsClass && (type.GetInterface(typeof(IntegrationInterface).FullName) != null))
loadedIntegrations.Add(type);
}
}
loadedIntegrations is of type List<Type>. Then you can instantiate each integration and call its methods:
foreach(var integrationType in loadedIntegrations)
{
var ctor = integrationType.GetConstructor(new Type[] { });
var integration = ctor.Invoke(new object[] { }) as IntegrationInterface;
//call methods on integration
}
I am doing something similar to what you described in an import utility that wrote. My issue was that I didn't want to load ALL the assemblies. I only wanted to load assemblies that contained types that were requested.
To accomplish this I've used the AppDomain.CurrentDomain.AssemblyResolve event handler.
This event handler is raised just before the AppDomain throw an exception notifying that an assembly is not found. I execute similar code to what Nico suggested in that handler and return the requested assembly.
NOTE: I have a sub-directory called 'Tasks' (think Import Tasks) where I store all of my assemblies I want to load at runtime.
Here is the code:
var tasks = GetTasks();
var basedir = AppDomain.CurrentDomain.BaseDirectory; // Get AppDomain Path
var tasksPath = Path.Combine(basedir, "Tasks"); // append 'Tasks' subdir
// NOTE: Cannot be factored, relies on 'tasksPath' variable (above).
AppDomain.CurrentDomain.AssemblyResolve += (s, e) => // defined 'AssemblyResolve' handler
{
var assemblyname = e.Name + ".dll"; // append DLL to assembly prefix
// *expected* assembly path
var assemblyPath = Path.Combine(tasksPath, assemblyname); // create full path to assembly
if (File.Exists(assemblyPath)) return Assembly.LoadFile(assemblyPath); // Load Assembly as file.
return null; // return Null if assembly was not found. (throws Exception)
};
foreach (var task in tasks.OrderBy(q => q.ExecutionOrder)) // enumerate Tasks by ExecutionOrder
{
Type importTaskType = Type.GetType(task.TaskType); // load task Type (may cause AssemblyResolve event to fire)
if (importTaskType == null)
{
log.Warning("Task Assembly not found");
continue;
}
ConstructorInfo ctorInfo = importTaskType.GetConstructor(Type.EmptyTypes); // get constructor info
IImportTask taskInstance = (IImportTask)ctorInfo.Invoke(new object[0]); // invoke constructor and cast as IImportTask
taskInstances.Add(taskInstance);
}
// rest of import logic omitted...
If u know MEF (Managed extensibility framework) this might help you I personally use it but have to say that using MEF with MVC is not trivial i think for more information please visit
http://msdn.microsoft.com/en-us/library/dd460648.aspx

Custom control generate code at design time

Is it possible to write a control which can create/ modify a code file in the same project - (Along the lines of the how the form designer works). I can't see the actual creating / modification of files being troublesome - however I would like to avoid the 'xyz file has been modified - would you like to reload it?' dialogue.
To be honest I'm not expecting that I can without writing a plug in or something of the like.
Absolutley, take a look at the CodeDom: -
http://msdn.microsoft.com/en-us/library/y2k85ax6.aspx
Alternatively look into creating a Visual Studio Add-in: -
http://www.c-sharpcorner.com/UploadFile/mgold/AddIns11292005015631AM/AddIns.aspx
Cheers,
Phil.
This is indeed possible to do. After referencing EnvDTE
using EnvDTE;
var host = this.Container as IDesignerHost;
var dte = host.GetService(typeof(DTE)) as DTE;
var activeDoc = dte.ActiveDocument;
var project = activeDoc.ProjectItem.Collection.Parent as Project;
project.ProjectItems.AddFromFile("\\Test.cs");
As Plip stated, use CodeDom and more specifically - CodeDocSerialier.
Here`s a short example:
[Serializer(typeof(MySerializer))]
class MyControl : Control {}
class MySerializer : CodeDomSerializer
{
public override object Serialize(IDesignerSerializationManager manager, object value)
{
CodeDomSerializer baseSerializer;
CodeStatementCollection statements;
CodeExpression targetObject;
if(manager == null || value == null)
{
return null;
}
baseSerializer = (CodeDomSerializer)manager.GetSerializer(typeof(MyControl).BaseType, typeof(CodeDomSerializer));
statements = baseSerializer.Serialize(manager, value) as CodeStatementCollection;
if(statements == null)
{
statements = new CodeStatementCollection();
}
targetObject = GetExpression(manager, value);
if(targetObject != null)
{
// add 'myControl.Visible = true;' statement.
statements.Add(
new CodeAssignStatement(
new CodeFieldReferenceExpression(targetObject, "Visible"),
new CodePrimitiveExpression(true)));
}
return statements;
}
}

Categories

Resources