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 :)
Related
I am struggling to find the data type of AD attributes that are not having a value already.
Thus far a resulting DirecoryEntry only contains a property for attributes that already have a value. I can't find a method to obtain information about all other attributes.
Adding a value to the 'PropertiesToLoad' doesn't seem to do anything. The returned DirectoryEntry object contains all attributes (with values) regardless of what is added here.
Code used:
public void Test(string ldapPath)
{
Type orgType;
try
{
using (DirectoryEntry searchRoot = GetSearchRoot(ldapPath))
{
using (DirectorySearcher search = new DirectorySearcher(searchRoot))
{
search.Filter = "(&(objectCategory=person)(objectClass=user)(sAMAccountName=coen))";
search.PropertiesToLoad.Add("msExchHomeServerName");
SearchResult searchResult = search.FindOne();
{
using (DirectoryEntry entry = searchResult.GetDirectoryEntry())
{
if (entry != null)
{
if (entry.Properties.Contains("msExchHomeServerName"))
{
orgType = entry.Properties["msExchHomeServerName"].GetType();
}
else // The value is empty and NOT present in the current 'entry.Properties' object.
{
throw new Exception("Unknown type");
}
}
}
}
}
}
}
catch (Exception e)
{
<handle exception>
}
}
Is there a way to find the data type of the 'msExchHomeServerName' (or any of the 'empty' attributes) attribute?
Any help would be highly appreciated!
Coen
Active Directory has a schema. Obvious to say, not obvious to think about since, by default, you cannot explore it.
However, you can Register the Active Directory Schema MMC Snap-In1 and then, in a fresh instance of MMC, add that Snap-In.
This then allows you to explore the Classes and Attributes that make up your current AD schema (and add new classes/attributes if you know what you're doing and choose to do so).
msExchHomeServerName can then be discovered to be a "Unicode String", which means a plain string from C# should be acceptable. Note also that some types (particularly numeric ones) may also specify Minimums and Maximums which should be observed.
You can also explore the schema programatically via the ActiveDirectorySchema class, by e.g. calling ActiveDirectorySchema.GetCurrentSchema(); and then explore from there.
1I cannot remember if you need to have installed the general Domain Admin tools (such as Users and Computers) in order for that DLL to exist on your system.
Thanks to Damien_The_Unbeliever who pointed me in the right direction, I managed to create the following method:
public Dictionary<string, ActiveDirectorySyntax> GetAttributeSyntaxes(List<string> lstAttributeNames)
{
Dictionary<string, ActiveDirectorySyntax> dictRes = new Dictionary<string, ActiveDirectorySyntax>();
if (lstAttributeNames.Count > 0)
{
DirectoryContext directoryContext = new DirectoryContext(DirectoryContextType.DirectoryServer,
m_Server, m_UserName, m_Password);
using (ActiveDirectorySchema currentSchema = ActiveDirectorySchema.GetSchema(directoryContext))
{
using (ActiveDirectorySchemaClass objClass = currentSchema.FindClass("user"))
{
if (objClass != null)
{
ReadOnlyActiveDirectorySchemaPropertyCollection propcol = objClass.GetAllProperties();
foreach (ActiveDirectorySchemaProperty schemaProperty in propcol)
{
foreach (string attrName in lstAttributeNames)
{
if (schemaProperty.Name.Equals(attrName))
{
dictRes.Add(attrName, schemaProperty.Syntax);
break;
}
}
}
}
}
}
}
return dictRes;
}
The returned 'schemaProperty.Syntax' contains sufficient information to determine the actual data type.
Thanks Damien!
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
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;
I'm creating a custom workflow activity in VS2010 targeting .NET 3.5. The DLL is actually being used in a Microsoft System Center Service Manager custom workflow, but I don't think that is my issue.
I have a public string property, that the user types in the string of what the activity should use. However, when the WF runs, it errors out 'value cannot be null'. I want to target if it is my code or something else.
When we drag my custom activity onto the designer, I'm able to type in the text of the string on the designer for that property.
public static DependencyProperty ChangeRequestStageProperty = DependencyProperty.Register("ChangeRequestStage", typeof(String), typeof(UpdateChangeRequestStage));
[DescriptionAttribute("The value to set the ChangeRequestStage Property in the ChangeRequest Extension class.")]
[CategoryAttribute("Change Request Extension")]
[BrowsableAttribute(true)]
[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)]
public String Stage
{
get { return ((String)(base.GetValue(UpdateChangeRequestStage.ChangeRequestStageProperty))); }
set { base.SetValue(UpdateChangeRequestStage.ChangeRequestStageProperty, value); }
}
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
EnterpriseManagementGroup emg = CreateEMG();
//System.WorkItem.ChangeRequest Extension - ClassExtension_928bec0a_cac4_4a0a_bd89_7146c9052fbe
ManagementPackClass mpcChangeRequest = emg.EntityTypes.GetClass(new Guid("8c6c6057-56ad-3862-47ec-dc0dde80a071"));
//System.WorkItemContainsActivity Relationship Class
ManagementPackRelationship workItemContainsActivityRelationship = emg.EntityTypes.GetRelationshipClass(new Guid("2DA498BE-0485-B2B2-D520-6EBD1698E61B"));
EnterpriseManagementObject changeRequest = null;
//Loop thru each emo (Change Request in this case), and assign it. There will never be more than 1 emo returned
foreach (EnterpriseManagementObject obj in emg.EntityObjects.GetRelatedObjects<EnterpriseManagementObject>(executionContext.ContextGuid, workItemContainsActivityRelationship, TraversalDepth.OneLevel, ObjectQueryOptions.Default))
{ changeRequest = obj; }
EnterpriseManagementObjectProjection emop = new EnterpriseManagementObjectProjection(changeRequest);
if (emop != null)
{ emop.Object[mpcChangeRequest, "ChangeRequestStage"].Value = Stage; }
emop.Commit();
return base.Execute(executionContext);
}
Since it is getting a 'value cannot be null' error, I'm guessing it's on this line:
emop.Object[mpcChangeRequest, "ChangeRequestStage"].Value = Stage;
I'm going to test and see if hardcoding a value works or not. Any ideas?
enter code here
try this
if (emop != null && emop.Object[mpcChangeRequest, "ChangeRequestStage"] != null)
emop.Object[mpcChangeRequest, "ChangeRequestStage"].Value = Stage
I didn't want to leave this question wide open, so I'm updating it as to how I resolved this (a long time ago).
Rather than working with an EnterpriseManagementObjectProjection (emop), I worked with a standard EnterpriseManagementObject (emo). From there, I was able to follow a similar format from above:
ManagementPackClass mpcChangeRequest = emg.EntityTypes.GetClass(new Guid("8c246fc5-4e5e-0605-dc23-91f7a362615b"));
changeRequest[mpcChangeRequest, "ChangeRequestStage"].Value = this.Stage;
changeRequest.Commit();
I have a class which has the following constructor
public DelayCompositeDesigner(DelayComposite CompositeObject)
{
InitializeComponent();
compositeObject = CompositeObject;
}
along with a default constructor with no parameters.
Next I'm trying to create an instance, but it only works without parameters:
var designer = Activator.CreateInstance(designerAttribute.Designer);
This works just fine, but if I want to pass parameters it does not:
var designer = Activator.CreateInstance(designerAttribute.Designer, new DelayComposite(4));
This results in an MissingMethodException:
Constructor voor type
Vialis.LightLink.Controller.Scenarios.Composites.DelayCompositeDesigner
was not found
Any ideas here?
The problem is I really need to pass an object during construction.
You see I have a designer which loads all the types that inherit from the CompositeBase. These are then added to a list from which the users can drag them to a designer. Upon doing so an instance of the dragged is added to the designer. Each of these classes have custom properties defined on them:
[CompositeMetaData("Delay","Sets the delay between commands",1)]
[CompositeDesigner(typeof(DelayCompositeDesigner))]
public class DelayComposite : CompositeBase
{
}
When the user selects an item in the designer, it looks at these attributes in order to load up a designer for that type. For example, in the case of the DelayComposite it would load up a user control which has a label and a slider which allow the user to set the "Delay" property of the DelayComposite instance.
So far this works fine if I don't pass any parameters to the constructor. The designer creates an instance of the DelayCompositeDesigner and assigns it to the content property of a WPF ContentPresenter.
But since that designer needs to modify the properties of the selected DelayComposite
in the designer, I have to pass this instance to it. That is why the constructor looks lie this:
public DelayCompositeDesigner(DelayComposite CompositeObject)
{
InitializeComponent();
compositeObject = CompositeObject;
}
Suggestions are welcome
#VolkerK
The result of your code is this:
<---- foo
Vialis.LightLink.Controller.Scenarios.Composites.DelayCompositeDesignerVoid
.ctor()
Vialis.LightLink.Controller.Scenarios.Composites.DelayCompositeDesignerVoid
.ctor(Vialis.LightLink.Controller.Scenarios.Composites.DelayComposite)
param:Vialis.LightLink.Controller.Scenarios.Composites.DelayComposite
foo ---->
Leppie, you were correct, I had for some reason referenced the Composites assembly in my UI application... which is not something I should have done as I was loading it at runtime. The following code works:
object composite = Activator.CreateInstance(item.CompositType,(byte)205);
var designer = Activator.CreateInstance(designerAttribute.Designer, composite);
As you can see the code does not have knowledge of the DelayComposite type.
This solves the current problem, but introduces many new ones for what I want to achieve,
either way thank you and thank you to everyone who has replied here.
As for the following code, suggested by multiple people:
var designer = Activator.CreateInstance(
designerAttribute.Designer,
new object[] { new DelayComposite(4) }
);
The Activator.CreateInstance has a signature that looks like this:
Activator.CreateInstance(Type type, params object[] obj)
So it should accept my code, but I will try the suggested code
UPDATE:
I've tried this as suggested:
var designer = Activator.CreateInstance(designerAttribute.Designer, new object[] { new DelayComposite(4)});
The result is the same.
I would think that your call would need to be:
var designer = Activator.CreateInstance(designerAttribute.Designer, new object[] { new DelayComposite(4) });
Unless, of course, it is that, in which case the answer is not immediately obvious.
I think you are dealing with a Type mismatch.
Likely the assembly is referenced in different places, or they are compiled against different versions.
I suggest you iterate through the ConstructorInfo's and do a paramtype == typeof(DelayComposite) on the appropriate parameter.
Though I hate printf-like debugging ...
public static void foo(Type t, params object[] p)
{
System.Diagnostics.Debug.WriteLine("<---- foo");
foreach(System.Reflection.ConstructorInfo ci in t.GetConstructors())
{
System.Diagnostics.Debug.WriteLine(t.FullName + ci.ToString());
}
foreach (object o in p)
{
System.Diagnostics.Debug.WriteLine("param:" + o.GetType().FullName);
}
System.Diagnostics.Debug.WriteLine("foo ---->");
}
// ...
foo(designerAttribute.Designer, new DelayComposite(4));
var designer = Activator.CreateInstance(designerAttribute.Designer, new DelayComposite(4));
What does that print in the visual studio's output window?
If you want to call this contructor...
public DelayCompositeDesigner(DelayComposite CompositeObject)
...just use this:
var designer = Activator.CreateInstance(typeof(DelayCompositeDesigner), new DelayComposite(4));
or
var designer = Activator.CreateInstance<DelayCompositeDesigner>(new DelayComposite(4));
I had a similar issue, however my problem was due to the visibility of the constructor. This stack overflow helped me:
Instantiating a constructor with parameters in an internal class with reflection
I discovered another way of creating an instance of an object without calling the constructor at all while answering another question on SF.
In the System.Runtime.Serialization namespace there is a function FormatterServices.GetUninitializedObject(type) that will create an object without calling constructor.
If you look at that function in Reflector you will see it is making an external call. I don't know how black magic is actually happening under the hood. But I did prove to myself that the constructor was never called but the object was instantiated.
When I encountered this problem, I was using a method that returned the parameter list to plug in to Activator.CreateInstance and it had a different number of arguments than the constructor of the object I was trying to create.
In my case, this code work good with .NET Framework but does not work in .NET Core 3.1. It throws ExecutionEngineException which is uncatchable. But when I change target to .NET 5, it works perfectly. Hope this help some one.
Type type = assembly.GetType(dllName + ".dll");
Activator.CreateInstance(type ), new Stream[] { stream };
You can use the following overload on CreateInstance:
public static Object CreateInstance(
Type type,
Object[] args
)
And in your case it'd be (I think):
var designer = Activator.CreateInstance(
typeof(DelayCompositeDesigner),
new object[] { new DelayComposite(4) }
);
I found a solution to the problem, I was struggling with the same issue.
Here is my activator:
private void LoadTask(FileInfo dll)
{
Assembly assembly = Assembly.LoadFrom(dll.FullName);
foreach (Type type in assembly.GetTypes())
{
var hasInterface = type.GetInterface("ITask") != null;
if (type.IsClass && hasInterface)
{
var instance = Activator.CreateInstance(type, _proxy, _context);
_tasks.Add(type.Name, (ITask)instance);
}
}
}
And here is my class to activate, note that I had to change the constructor params to objects, the only way I could get it to work.
public class CalculateDowntimeTask : Task<CalculateDowntimeTask>
{
public CalculateDowntimeTask(object proxy, object context) :
base((TaskServiceClient)proxy, (TaskDataDataContext)context) { }
public override void Execute()
{
LogMessage(new TaskMessage() { Message = "Testing" });
BroadcastMessage(new TaskMessage() { Message = "Testing" });
}
}