All, I am localising my C# application and I have come to my settings control. As I am using a PropertyGrid I am using properties to provide the options. I have a CategoryAttribute for "Server Access" I need to show in different languages - I had:
[CategoryAttribute("ServerAccess"),
ReadOnlyAttribute(false),
XmlElement,
DescriptionAttribute("Message about the Server Access Option.")]
public bool ShowSystemDatabases
{
get { return this.showSystemDatabases; }
set { this.showSystemDatabases = value; }
}
and I attempted to change this to:
[CategoryAttribute(ObjectStrings.SettingsServerAccess),
ReadOnlyAttribute(false),
XmlElement,
DescriptionAttribute(MessageStrings.SettingsShowSystemDatabaseInfo)]
public bool ShowSystemDatabases
{
get { return this.showSystemDatabases; }
set { this.showSystemDatabases = value; }
}
Where ObjectStrings is my resource file ('ObjectStrings.resx' et al.). This is throwing a compile-time error
An attribute argument must be a constant expression, typeof expression or array
creation expression of an attribute parameter type...
clearly I can't use a string when the constructor is expecting a const string. I have tried casting from string to const string using various round-the-houses method but all have failed. it is a simple enough issue, but...
What is the easiest way around this problem?
Thanks for your time.
I don't know how you could handle this especially in the case of control's attributes, but a general way to achieve this is to make Resources resolved at runtime, using parameters like these on your attribute :
MessageResourceType = typeof (MyResource), MessageResourceName = "MyResourceKey")
instead of directy passing the pointer to resource key.
I don't know if there is an equivalent way for CategoryAttribute, DescriptionAttribute and others Controls' properties attributes, or if there is a way to overload them.
Related
I am trying to write a MongoDb serializer in c# that will allow me to decorate properties via a [Encrypt()] attribute and then at runtime it would allow me to generate an additional property called PropertyName_Encrypted which would contain the encrypted value.
On deserialization, the encrypted property value would be set in the parent property so that the default GET for the property always returns the encrypted value. Users will then call an optional Decrypt() method on the object to get decrypted values.
In doing so, I'm running into some interesting challenges:
How do I add Additional properties to the document when I am serializing current Element? How do I get the current element's name?
Is there a way I can read a specific property from the document/object? For e.g. say I want to pass a symmetric encryption key and read that to encrypt the data while serializing the current element? Is there any way I can do that?
Here are things I have done so far:
I've built an Encrypt Attribute as follows:
[AttributeUsage(AttributeTargets.Property)]
public class EncryptAttribute : Attribute
{
private readonly EncryptedFieldType _fieldType;
private readonly bool _tokenizeDisplay;
private readonly string _encryptedFieldName;
/// <summary>
///
/// </summary>
/// <param name="fieldType">The field type to encrypt. Useful if display needs to show some formatting. If no formatting is necessary, simply set to "Other".</param>
/// <param name="tokenizeDisplay">If set to true, will persist the tokenized value in the original field for display purposes.</param>
/// <param name="encryptedFieldName">Optional. If set, will save the encrypted value in the field name specified. By default all encrypted field values are stored in the corresponding _Encrypted field name. So EmailAddress field if encrypted, would have value under EmailAddress_Encrypted.</param>
public EncryptAttribute(EncryptedFieldType fieldType, bool tokenizeDisplay, string encryptedFieldName = "")
{
_fieldType = fieldType;
_tokenizeDisplay = tokenizeDisplay;
_encryptedFieldName = encryptedFieldName;
}
}
I read this Attribute on Startup and add an Encryption Serializer to the properties that are decorated using this attribute. The code that does that is like so:
var assemblies = AppDomain.CurrentDomain.GetAssemblies()
.Where(x => x.FullName.StartsWith("MongoCustomSerializer"))
.ToList();
var mapper = new Mapper();
foreach (var assembly in assemblies)
{
mapper.Map(assembly);
}
The mapper simply checks which properties in the document have the Encrypt attribute to add the serializer:
public sealed class Mapper
{
public void Map(Assembly assembly)
{
var encryptableTypes = assembly.GetTypes().Where(p =>
typeof(IEncryptable).IsAssignableFrom(p) && p.IsClass && !p.IsInterface && !p.IsValueType &&
!p.IsAbstract).ToList();
if (encryptableTypes.Any())
{
foreach (var encryptableType in encryptableTypes)
{
Map(encryptableType);
}
}
}
private void Map(Type documentType)
{
var properties =
documentType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
if (properties.Length <= 0)
{
return;
}
foreach (var property in properties)
{
RegisterEncrpytionSerializer(property, typeof(EncryptAttribute), documentType);
}
}
private void RegisterEncrpytionSerializer(PropertyInfo property, Type encryptAttributeType, Type documentType)
{
var encryptAttributes = property.GetCustomAttributes(encryptAttributeType, false).ToList();
if (!encryptAttributes.Any()) return;
var memberMap = BsonClassMap.LookupClassMap(documentType).GetMemberMap(property.Name);
memberMap?.SetSerializer(new EncryptionSerializer());
}
}
In my unit tests, I'm getting an error stating that the Bson Class Map is already frozen. Even if I were to figure out a way to bypass that, how would this EncryptionSerializer class work to where I could write an additional property?
Would love to see if someone can assist!
UPDATE 1 - I was able to get the FREEZE error taken care of. It would appear that the LookupClassMap freezes the Member and Class Map info.
This change from the link allows me to take care of that issue:
private void RegisterEncrpytionSerializer(PropertyInfo property, Type encryptAttributeType, Type documentType)
{
var encryptAttributes = property.GetCustomAttributes(encryptAttributeType, false).ToList();
if (!encryptAttributes.Any()) return;
var classMapDefinition = typeof(BsonClassMap<>);
var classMapType = classMapDefinition.MakeGenericType(documentType);
var classMap = (BsonClassMap)Activator.CreateInstance(classMapType);
classMap.AutoMap();
var memberMap = classMap.GetMemberMap(property.Name);
memberMap?.SetSerializer(new KeyVaultEncryptionSerializer(memberMap.ElementName));
}
Are you using a service for saving/retrieving your items that actually call the DB?
I believe you should move the responsibility for writing/reading encrypted values to the calling service (i.e a repository implementation) instead of the BsonSerializer.
It would make sense to me that encryption/decryption is part of the persistence layer and something not handled in the application when needed.
Your implementation targets only the specified property you want to serialize. It doesn't make sense that it creates another property.
A second thought is that your suggested approach with properties that change value based on Decrypt() probably isn't a good idea since it makes your code unpredictable and hard to read. Make your properties dead simple.
What extra security in your code does it really give you if you can decrypt properties by just calling a method anyway?
If you still need to have a Decrypt() would suggest that you create methods for decrypting that return the decrypted value like GetUnencryptedCode() etc, it could just as well be an extension method but still not a readable property.
You should also be looking into using SecureString depending on your use case.
Since you can only add pages to a FixedDocument, I wrote a derived class:
public class CustomFixedDocument : FixedDocument
{
public void RemoveChild(object child)
{
base.RemoveLogicalChild(child);
}
}
to replace FixedDocument, which works fine, until I try to print the document and receive the following error:
An unhandled exception of type
'System.Windows.Xps.XpsSerializationException' occurred in
ReachFramework.dll
Additional information: Serialization of this type of object is not
supported.
I haven't worked with serialization that much in the past and have read up on it but still can't solve the issues. I have also tried the
[Serializable]
attribute, but it doesn't make any difference.
Can anybody guide me in the correct direction or have any ideas what to do?
If you look at decompiled source code of the method which checks if certain type is supported, you will see roughly the following:
internal bool IsSerializedObjectTypeSupported(object serializedObject)
{
bool flag = false;
Type type = serializedObject.GetType();
if (this._isBatchMode)
{
if (typeof (Visual).IsAssignableFrom(type) && type != typeof (FixedPage))
flag = true;
}
else if (type == typeof (FixedDocumentSequence) || type == typeof (FixedDocument) || (type == typeof (FixedPage) || typeof (Visual).IsAssignableFrom(type)) || typeof (DocumentPaginator).IsAssignableFrom(type))
flag = true;
return flag;
}
Here you see that this type should either inherit DocumentPaginator, Visual, or be exactly of type FixedDocument, FixedDocumentSequence, FixedPage. So, types inherited from FixedDocument will not work, whatever serializable attributes you will use, so you have to find a different approach. I think that is a bug in XpsSerializationManager, but maybe there is some deep reason.
I decided to try the OP's approach and see if I can get it to work.
According to the snippet posted by Evk, although the IsSerializedObjectTypeSupported() function will not accept our own custom derivative of FixedDocument, it will accept a DocumentPaginator, and one of the overloads of XpsDocumentWriter.Write() accepts a paginator, so that should work, right?
Well, it turns out that if you do XpsDocumentWriter.Write( myFixedDocument.DocumentPaginator ) (where myFixedDocument is a custom derivative of FixedDocument) then something throws a NullReferenceException somewhere deep in library code. So, no luck there.
However, according to the same snippet, a FixedPage is also a supported type, and the XpsDocumentWriter.Write() method has another overload which accepts individual instances of FixedPage.
So, the following code worked for me:
foreach( FixedPage fixedPage in
fixedDocument.Pages.Select( pageContent => pageContent.Child ) )
xpsDocumentWriter.Write( fixedPage );
(Where Select() comes from using System.Linq)
I am writing a Fody Addin and I am able to inject my code and to provide error messages to the user. I am able to determine the sequence points of instructions, but I cannot find a way to find the sequence points of CustomAttributes.
I need to get this information to provide the debugger a hint where to find the location of an error, in case that an attribute has been applied wrongly.
So basically I have something like this:
[MyAttribute]
public void Test()
{
}
Now I want to get the SequencePoint of the MyAttribute attribute.
**Edit: ** As I got down voted (without any information why) here some additional information. I can access the sequence point of instructions like this:
public static SequencePoint GetSP(MethodDefinition method)
{
return method.Body.Instructions
.Where(instruction => instruction.SequencePoint != null)
.Select(instruction => instruction.SequencePoint)
.FirstOrDefault();
}
That works fine for instructions but when I access an attribute I am not sure how to get the sequence point:
public static SequencePoint GetSP(MethodDefinition method)
{
var attribute = method.CustomAttributes.First();
// what to enter here to get SequencePoint of attribute?
}
this is not possible. attributes dont have sequence points. i suggest you just use the first sequencepoint for the method instead
I was looking at the question Use 'dynamic' throw a RuntimeBinderException. I face a similar problem:
Basically, I want to create a "HTML helper" in ASP.NET MVC that uses dynamic arguments, akin to the htmlArguments parameter for many of the existing helpers (more code below):
public BootstrapCell(Action<string> emitContentAction, dynamic args)
View:
#using (grid.Cell(ViewContext.Writer.Write, new {Position = 4}))
{
<p>zomg!</p>
}
However in the naive approach, i get RuntimeBinderException thrown at me, declaring that 'object' does not contain a definition for 'Position', even though when debugging and hovering over the _args variable, it clearly does have a Position property.
The caller and the callee are in separate assemblies. Why is that problem happening?
(The solution to that has been shown in the same question: Manually create an ExpandoObject to hold the args.)
Implementation:
public class Cell
{
private readonly string _tagName;
private dynamic _args;
private Action<string> EmitContentAction;
public BootstrapCell(Action<string> emitContentAction, dynamic args) : DisposableBaseClass
{
_args = args;
EmitContentAction = emitContentAction;
OnContextEnter();
}
protected void OnContextEnter()
{
var sb = new StringBuilder("<");
sb.Append(_tagName);
if (_args.Position > 0)
{
sb.Append(" class=\"offset");
sb.Append(args.Position);
sb.Append("\"");
}
sb.Append(">");
EmitContentAction(sb.ToString());
}
}
[Edited to make clearer that my problem arises when "obviously" the Position property is set. I am aware that if the property never was defined in the first place, an exception must be raised.]
That code is fatally flawed.
It does work, as long as you specify that property:
void Bar()
{
Foo(new {Position = 0});
}
void Foo(dynamic args)
{
Console.WriteLine(args.Position);
}
That will output 0, it will not throw a RuntimeBinderException.
But the purpose of such code is the possibility for the caller to specify only the properties needed and omit the rest.
You are trying to check for this omission via if(args.Position != null). But that doesn't work, it already requires Position to exist.
When you have a look at the routing API of ASP.NET that also supports those anonymous configuration objects you will notice that the type of the parameter is object and not dynamic.
Using object instead of dynamic will enable your API to be used across assembly boundaries.
So how does it work?
Just like in the linked answer, you need to manually create a dictionary of the properties. Whether you use a plain old Dictionary<string, object> or an ExpandoObject is a matter of preference.
Using ExpandoObject will make your code a bit simpler to read and write, but it is not required.
About the actual exception you are getting:
Please note that it tells you it can't find the Position property on object. If it would be an anonymous type that was missing the Position property the exception message wouldn't refer to object but to an anonymous type. Something like this:
'<>f__AnonymousType0' does not contain a definition for 'Position'
This is just a matter of taste but I'd like to hear some of your opinions (that's also why this question is marked as subjective).
If I have a property, say
private string _Text;
public string Text;
get
{
object tmp = ViewState["Text"];
if (tmp != null)
_Text = Convert.ToString(tmp);
return _Text;
}
set
{
ViewState.Add("Text", value);
}
Now this is the property which may be specified by the programmer, by setting some custom text. This is then mapped - say - to some control on the UI. In the default case however, the Text of the control comes from a predefined resource file. So internally to handle that internally in a better way, I'd have some central point where I check whether the user has specified the "Text" property (above) and if so, use that data, otherwise rely on the default one from the resource file.
So what approach would you take? I have two options in mind:
private string ResolvedText
{
get
{
if(!string.IsNullOrEmpty(Text))
return Text;
else
//return the one from the resource file
}
}
Or put everything in a method
public string GetResolvedText()
{
if(!string.IsNullOrEmpty(Text))
return Text;
else
//return the one from the resource file
}
The question may sound stupid to you since it's really a minor difference. But I'd like to know whether there are some conventions about this.
Thx
Personally, I'd take the body of your GetResolvedText method, and use it in the property, thus:
private string _Text;
public string Text
{
get
{
if(string.IsNullOrEmpty(_Text))
//return the one from the resource file
else
return _Text;
}
set
{
_Text = value;
}
}
This puts all the responsibility for managing the string into the one place. The class itself can access _Text internally, if it needs the raw value.
I find that the best general rule here is: if calling the action twice results in multiple resource calls or different behaviour - use a method.
So, in your example use of a property is fine if it caches:
public string ResolvedText
{
get { return Text ?? (Text = GetResolvedText()); }
}
However the method doesn't need to - users expect it to be a more intensive operation:
public string GetResolvedText()
{
//return the one from the resource file
}
The design question is how do you want this class to be used?
A property will get called as if it is a 'cheap' operation:
if( myInstance.ResolvedText != null &&
myInstance.ResolvedText.Length > 5 )
Response.Write( myInstance.ResolvedText );
A method hints to the developer that they should call it as few times as possible:
string resolvedText = myInstance.GetResolvedText();
if( resolvedText != null &&
resolvedText.Length > 5 )
Response.Write( resolvedText );
Personally I prefer to keep interim classes simple, so in the vast majority of cases I would use the method model.
As this is a fairly standard convention you should avoid properties that don't cache and methods that do.
I would keep this as a property, since it represents a single value that does not require a lot of computing to retrieve.
To me, if the getter can't throw an exception, or null/invalid value, it should be a property. It is what properties are made for.
BUT, if you do some complicated stuff, if has to be a a function getter. Obviously here you have only 1 if, so I would use a property.
Reworking together your example and Steve's answer, plus adding in some caching, as obviously the resource value should be read only once since it never changes and by contract we must return the value from the property as fast as possible:
private static string ResourceText;
static [constructor]
{
ResourceText = //get resource;
}
private string text;
public string Text;
get
{
string tmp = (string)ViewState["Text"];
if (!String.IsNullOrEmpty(tmp))
text = tmp;
else
text = ResourceText;
return text;
}
set
{
ViewState.Add("Text", value);
// Note: passing null or empty strings will not work.
}