Dynamically load class for rules engine - c#

I have a XML file that defines a lot of rules.
I load the XML file into my rules engine.
Depending on what XML file I load i need to pick which namespace I will find the classes I need. Then on each row of the XML I need to determine what class to load.
My XML
<RuleList assembly="BMW">
<rule>
<code>2345</code>
<errorMessage>foo bar</errorMessage>
<order>1</order>
</rule>
</RuleList>
<RuleList assembly="FORD">
<rule>
<code>0045</code>
<errorMessage>foo bar</errorMessage>
<order>3</order>
</rule>
</RuleList>
I only process one rule list at a time.
Should I be adding an extra XML attribute to each rule defining the ClassName to load?
As I do not want to use the code as the classname? Or can I just add the code as an attribute to my class and use that to load it dynamically
For example
namespace FORD
{
[code=0045]
public bool IsValidColor(foo) : IisValid
{
return true
}
}
Can I load classes from the [code=0045] or should I just stored "IsValidColor" in the XML. Is there a performance difference.

Your attribute syntax doesn't work. But something like [Code("0045")] would, if you create CodeAttribute.
There is going to be some performance difference, because you'll have to find the correct type based on the attribute among all types in the assembly. But the difference is most likely going to be negligible.
To actually do it, define the attribute like this:
[AttributeUsage(AttributeTargets.Class)]
class CodeAttribute : Attribute
{
public CodeAttribute(string code)
{
Code = code;
}
public string Code { get; private set; }
}
And then find the class like this:
var type =
(from t in assembly.GetTypes()
let attr = (CodeAttribute)t
.GetCustomAttributes(typeof(CodeAttribute), false)
.SingleOrDefault()
where attr != null && attr.Code == code
select t)
.Single();

Either option would have similar performance. If the error codes, error messages, and order will never vary across XML files, you could even have all of the metadata about a rule inside of an attribute instead, and at runtime enumerate through all classes that implement IisValid:
[Rule(Code = "0045", ErrorMessage = "foo bar", Order = 1)]
public class IsValidColor : IisValid
{
public bool IsValid(Foo bar)
{
// validation rules here
}
}
However, if all of this is customizable, I'd personally go with having the XML file specify the names of the classes to use.

Related

Add attributes inside custom attribute in c#

I want to dynamically add attributes to properties of a given class, because the project will get bigger and the procedure is totally repeatinglly, something like so :
public class MyAttr : Attribute {
public MyAttr () {
foreach(var prop in properties) {
prop.Attributes.Add([Display(Name = nameof(prop.Name), ResourceType = typeof(Labels))]);
}
}
}
And use it on classes :
[MyAttr]
public class SomeClass {
public string Name {get; set;}
public string Description {get; set;}
}
of course the code above is not a real program, but I tried to make it simple.
Is it possible to do this? or if not, is there a workaround?
Update: I know there's an attribute called CallerMemberName that retrieves name of a given property, but how to get the property itself and add more attributes to it?
I think you might be able to make something work combining the below answers from other posts.
Access object properties from Attribute: Can C# Attributes access the Target Class?
Add attributes at runtime: How to add an attribute to a property at runtime by creating new types at run-time.
Unless you're going to be making regular changes across all the properties it may just be better to deal with the repetitive work up front. There are inevitably risks/issues down the road when doing stuff across the board like this.

'constructor' is not a recognized attribute location

After trying to compile the following code:
public sealed class Program
{
[constructor: CLSCompliant(false)]
public Program()
{
}
}
I am getting the following error:
'constructor' is not a recognized attribute location. Valid attribute locations for this declaration are 'method'. All attributes in this block will be ignored. [Console.NET]csharp(CS0658)
I know that there are the following locations are present: assembly, module, method, parameter, return, etc. So, my guess was that the constructor should be present as well (since we can have a constructor as a target for an attribute as well). But it seems that it is not the case here.
Also, I was not able to find a full list of the recognized attribute locations on the MSDN. So, that would be helpful if someone would provide a link to the list of the locations on the MSDN.
My guess about the presence of the constructor location was based after I met the following code sample in the C# via CLR book:
using System;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
[assembly: CLSCompliant(true)]
[Serializable]
[DefaultMemberAttribute("Main")]
[DebuggerDisplayAttribute("Richter", Name = "Jeff", Target = typeof(Program))]
public sealed class Program
{
[Conditional("Debug")]
[Conditional("Release")]
public void DoSomething() { }
public Program()
{
}
[CLSCompliant(true)]
[STAThread]
public static void Main()
{
// Show the set of attributes applied to this type
ShowAttributes(typeof(Program));
// Get the set of methods associated with the type
var members =
from m in typeof(Program).GetTypeInfo().DeclaredMembers.OfType<MethodBase>()
where m.IsPublic
select m;
foreach (MemberInfo member in members)
{
// Show the set of attributes applied to this member
ShowAttributes(member);
}
}
private static void ShowAttributes(MemberInfo attributeTarget)
{
var attributes = attributeTarget.GetCustomAttributes<Attribute>();
Console.WriteLine("Attributes applied to {0}: {1}",
attributeTarget.Name, (attributes.Count() == 0 ? "None" : String.Empty));
foreach (Attribute attribute in attributes)
{
// Display the type of each applied attribute
Console.WriteLine(" {0}", attribute.GetType().ToString());
if (attribute is DefaultMemberAttribute)
Console.WriteLine(" MemberName={0}",
((DefaultMemberAttribute)attribute).MemberName);
if (attribute is ConditionalAttribute)
Console.WriteLine(" ConditionString={0}",
((ConditionalAttribute)attribute).ConditionString);
if (attribute is CLSCompliantAttribute)
Console.WriteLine(" IsCompliant={0}",
((CLSCompliantAttribute)attribute).IsCompliant);
DebuggerDisplayAttribute dda = attribute as DebuggerDisplayAttribute;
if (dda != null)
{
Console.WriteLine(" Value={0}, Name={1}, Target={2}",
dda.Value, dda.Name, dda.Target);
}
}
Console.WriteLine();
}
}
And the output of this program is the following:
Attributes applied to Program:
System.SerializableAttribute
System.Diagnostics.DebuggerDisplayAttribute
Value=Richter, Name=Jeff, Target=Program
System.Reflection.DefaultMemberAttribute
MemberName=Main
Attributes applied to DoSomething:
System.Diagnostics.ConditionalAttribute
ConditionString=Release
System.Diagnostics.ConditionalAttribute
ConditionString=Debug
Attributes applied to Main:
System.CLSCompliantAttribute
IsCompliant=True
System.STAThreadAttribute
Attributes applied to .ctor: None
The output makes it clear that a constructor is treated differently from the class and method. And since there are locations class and method, the constructor location is expected by me to be present as well.
I need this solely for the learning purposes.
Attribute targets list present in two places:
limit what an attribute can be applies to (via AttributeUsageAttribute)
disambiguate what the attribute applies to in each particular case (via "Attribute targets
")
So why we'd have "method" but not "constructor" targets (my interpretation, I'm not aware of an official reasoning):
"method" applies to methods and properties. It includes constructor as a special variation of a method. So there is a way to allow an attribute to be used on a constructor and not on an assembly or a field.
when attribute applied to a constructor there is no other possible choices for default "method" target as a constructor matches only "method" option from the list of targets (unlike auto-property for example where attribute can be considered as targeting "method" or "property" or even "field")
presumably there is no useful case where attribute must be restricted to just constructors.
Side note: since attributes by themselves generally don't do anything attribute targets are mainly to limit potential cases where attribute is set but impacts nothing. If you want to "target" just constructors some good naming may be enough. If you really want to limit to just constructors you can check all loaded types for improper use of your custom attribute at startup/first need to check for your custom attribute.

Xml deserialization not binding list

I have a XML file looking like this:
<Stamdata xmlns="http://schemas.datacontract.org/2004/07/a">
<Liste xmlns:d3p1="http://schemas.datacontract.org/2004/07/System.Collections.Generic">
<d3p1:KeyValuePairOfStringDatagxNgnmsk>
<d3p1:key>key value</d3p1:key>
<d3p1:value>
......
</d3p1:value>
</d3p1:KeyValuePairOfStringDatagxNgnmsk>
</Liste>
</Stamdata>
Any my models looks like
[DataContract(Name = "Stamdata", Namespace = "http://schemas.datacontract.org/2004/07/a")]
public class Stamdata
{
[DataMember]
public KeyValuePair<string, Data>[] Liste { get; set; }
}
[DataContract(Name = "Data", Namespace = "http://schemas.datacontract.org/2004/07/System.Collections.Generic")]
public class Data
{
//.... Many properties
}
My problem is that the list keeps containing 0 elements, even thought the xml contains around 100.
And I'm not sure id the "gxNgnmsk" at the end of <d3p1:KeyValuePairOfStringDatagxNgnmsk> means anything... the one behind the response doesn't know wither what it is.
The "gxNgnmsk" at the end of <d3p1:KeyValuePairOfStringDatagxNgnmsk> is the cause of your problem. The presence of this suffix is explained in Data Contract Names for Generic Types:
Special rules exist for determining data contract names for generic types...
By default, the data contract name for a generic type is the name of the type, followed by the string "Of", followed by the data contract names of the generic parameters, followed by a hash computed using the data contract namespaces of the generic parameters. ... When all of the generic parameters are primitive types, the hash is omitted.
The "gxNgnmsk" is the hash. And if I use your classes to generate XML from instances created in memory, I get a different hash:
<Stamdata xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/a">
<Liste xmlns:d2p1="http://schemas.datacontract.org/2004/07/System.Collections.Generic">
<d2p1:KeyValuePairOfstringDatatwCi8m_S7>
<d2p1:key>key value</d2p1:key>
<d2p1:value />
</d2p1:KeyValuePairOfstringDatatwCi8m_S7>
</Liste>
</Stamdata>
Apparently your Data type does not have the correct data contract namespace, causing an inconsistent hash to get generated. (And, from experiment, neither "http://schemas.datacontract.org/2004/07/System.Collections.Generic" nor "http://schemas.datacontract.org/2004/07/a" nor "" seem to generate the correct hash. Possibly I could guess the correct namespace from a complete XML sample, but there's not enough information in the simplified XML provided.)
So, what can be done to fix this?
You could get the "one behind the response" to tell you the correct data contract name and namespace for all contract types including Data. This is necessary anyway for data contract equivalence when sending data over the wire so they should be able to provide this information.
If the "one behind the response" provides a WSDL (and they should) you should be able to auto-generate a client which should just work.
But in the absence of the above, you can work around the problem by creating a collection with a custom collection data contract with the hash baked in:
[DataContract(Name = "Stamdata", Namespace = "http://schemas.datacontract.org/2004/07/a")]
public class Stamdata
{
[DataMember]
public DataList Liste { get; set; }
}
[CollectionDataContract(
Namespace = "http://schemas.datacontract.org/2004/07/System.Collections.Generic",
ItemName = "KeyValuePairOfStringDatagxNgnmsk")]
public class DataList : List<KeyValuePair<string, Data>>
{
public DataList() : base() { }
public DataList(IEnumerable<KeyValuePair<string, Data>> list) : base(list) { }
}
With this version of Stamdata the sample XML can be deserialized without knowing the correct namespace for Data.

Adding suffix to XmlElement C# parser

I'm dealing with a XML file which has support for different languages, I want to parse this XML into C# classes using XDocument/XElement (using System.Xml.Serialization). The XML is slightly complex but what I want to achieve should be simple, yet I can't figure it out.
Basix XML example:
<root>
<word_EN>Hello</word_EN>
<word_DE>Hallo</word_DE>
<word_FR>Bonjour</word_FR>
<root>
How I want my parser to look like:
[XmlRoot("root")]
public class Root
{
[XmlElement("word_" + LanguageSetting.SUFFIX)]
public string word { get; set; }
}
I want to get the suffix from another class and I want to be able to change it. I can set the suffix as a const string but then I can't change it. Using a global variable also does not work.
static class LanguageSetting
{
private static string _suffix = "EN";
public static string SUFFIX
{
get { return _suffix; }
set { _suffix = value; }
}
}
Error:
An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type
What is the proper way of adding the suffix?
The correct way of doing this would be for your language suffix to be an XML attribute on the word element but this may not be possible for you.
You are receiving this error because a compile time constant must be use in attribute decorations. LanguageSetting.Suffix is static, but not a constant. Try using the const keyword instead.
In XML, different tag names represent different object types. The best solution for your current XML document is you have seperate classes for each supported language, all inherited from a common class (eg. WordBase).

Create Attribute to represent multiple sub attributes in C#

I have the following type of code sample across one of my projects...
[Obfuscation(Exclude = true)]
[UsedImplicitly]
public DelegateCommand<object> OpenXCommand { get; private set; }
I am finding the attributes are adding a lot of "noise" to the code - I also see it in a way violating the DRY principle since I may have several properties like this in one class, all with the same attribute decoration.
Q: Is there some way I can set up an attribute that will represent a combination of sub attributes?
Ideally I would like something that looks like this..
[MyStandardCommandAttribute]
public DelegateCommand<object> OpenXCommand { get; private set; }
I have not implemented my own attributes before so I am unsure if this is possible. Any suggestions?
No. Your one attribute cannot "be" Obfuscation and UsedImplicitly at the same time (there's no multiple inheritance in C#).
Code looking for e.g. UsedImplicitlyAttribute has no way of knowing that MyStandardCommandAttribute is supposed to represent UsedImplicitlyAttribute (unless you control all of the code using all of these attributes).
Unfortunately, there's no way to do this in C#.
But, if you control the places that read these attributes (with reflection), you can do it by convention.
For example, you can have a marker interface that will "annotate" your attribute with the attributes it proxies (sounds like a meta-attribute):
public interface AttributeProxy<T>
where T : Attribute {}
public class MyStandardCommandAttribute :
Attribute,
AttributeProxy<ObfuscationAttribute>,
AttributeProxy<UsedImplicitlyAttribute> {}
(Of course, you also have to match the right AttributeUsages. And you can't set properties on the proxied attributes like this.)
Now, you could go a step further and use an IL manipulation library, like Mono.Cecil, to actually transfer the attributes appropriately in a post-compilation step. In this case, it would work even if it weren't you reflecting on these attributes.
Update: still in the reflect-your-own-attributes scenario, you can use the below code to get to proxied attributes, even setting properties values:
public interface IAttributeProxy {
Attribute[] GetProxiedAttributes();
}
public class MyStandardCommandAttribute : Attribute, IAttributeProxy {
public Attribute[] GetProxiedAttributes() {
return new Attribute[] {
new ObfuscationAttribute { Exclude = true },
new UsedImplicitlyAttribute()
};
}
}
Use this extension method on your reflection code:
public static object[] GetCustomAttributesWithProxied(this MemberInfo self, bool inherit) {
var attributes = self.GetCustomAttributes(inherit);
return attributes.SelectMany(ExpandProxies).ToArray();
}
private static object[] ExpandProxies(object attribute) {
if (attribute is IAttributeProxy) {
return ((IAttributeProxy)attribute).GetProxiedAttributes().
SelectMany(ExpandProxies).ToArray(); // don't create an endless loop with proxies!
}
else {
return new object[] { attribute };
}
}

Categories