10

Here is my code:

public class UserPreferences
{
    /// <summary>
    /// The EMail signature.
    /// </summary>
    [UserPreferenceProperty(Category = "Email", DefaultValue = "My default value")]
    public static string Signature
    {
        get
        {
            return UserPreferenceManager.GetValue();
        }

        set
        {
            UserPreferenceManager.SetValue(value);
        }
    }
}

public static string GetValue()
{
    if (((VTXPrincipal)Thread.CurrentPrincipal).VTXIdentity.OperatorID == null)
    {
        throw new Exception("Missing Operator ID");
    }

    string value = string.Empty;

    var frame = new StackFrame(1);  ***** <------ problem here.....

    var property = frame.GetMethod();
    var propertyname = property.Name.Split('_')[1];
    var type = property.DeclaringType;   ***** <------ problem here.....
    if (type != null)
    {
        var userPreference = typeof(UserPreferences).GetProperty(propertyname).GetCustomAttributes(true).FirstOrDefault() as UserPreferencePropertyAttribute;

        if (userPreference != null)
        {
            string category = userPreference.Category;
            string description = propertyname;
            value = GetValue(category, description, ((VTXPrincipal)Thread.CurrentPrincipal).VTXIdentity.OperatorID);
            if (value == null)
            {
                // always return something
                return userPreference.DefaultValue;
            }
        }
        else
        {
            throw new Exception("Missing User Preference");
        }
    }

    return value;
}

Inside the GetValue method, StackFrame works differently in release mode vs. debug mode.

In debug mode, I correctly get the property name as signature

But in Release mode, property name is GetUserPreferenceValueTest because this is the test method that makes the calls as clients.

There fore my code works in debug mode but fails in release mode.

Q. How can I use StackFrame properly so it works in Debug vs. Release modes. 

Q. Is there any other way to get calling property name and related information at run time?
dotnet-practitioner
  • 13,968
  • 36
  • 127
  • 200
  • 2
    "StackFrame information will be most informative with Debug build configurations. By default, Debug builds include debug symbols, while Release builds do not. The debug symbols contain most of the file, method name, line number, and column information used in constructing StackFrame objects." – Mitch Wheat Dec 28 '12 at 23:46
  • does that mean.. this code would fail in production because in production it will be deployed as Release mode??? – dotnet-practitioner Dec 28 '12 at 23:54
  • 3
    why would you be relying on StackFrame() in Release anyway? Use a different design! – Mitch Wheat Dec 28 '12 at 23:55
  • Handling user prefs like that is heavy handed and over-complicated. There should be many examples online: http://nini.sourceforge.net/ – Mitch Wheat Dec 29 '12 at 00:05
  • You might consider MEF - http://msdn.microsoft.com/en-us/VS2010TrainingCourse_IntroToMEF.aspx – Hogan Dec 29 '12 at 00:47

2 Answers2

8

I answered a similar question once, please read my answer here.

In short, this is a very bad design decision because your method is a hypocrite—it talks different to different callers but doesn't tell it in open. Your API should never, ever rely on who calls it. Also, the compiler can break the stack trace in an unexpected way due to language features like lambdas, yield and await, so even if this worked in Release mode, it would certainly break some day.

You're effectively building a complex indirection mechanism instead of using language feature designed for passing information to methods—method parameters.

Why do you use attributes? Do you read them elsewhere?

If you do, and you don't want to repeat "Email" both as parameter to GetValue call and attribute value, you may consider passing a property Expression<> to GetValue, which will extract the attribute. This is similar to your solution, but it is explicit:

[UserPreferenceProperty(Category = "Email", DefaultValue = "My default value")]
public string Signature
{
    get { return GetValue (prefs => prefs.Signature); }
    set { SetValue (prefs => prefs.Signature, value); }
}

This answer shows how to implement this.

I see you are checking Thread.CurrentPrincipal in your code. Again, this is not a really good practice because it is not obvious to client code that accessing a property can result in an exception. This is going to be a debugging nightmare for someone who supports your code (and trust me, your code may run for years in production, long after you move onto another project).

Instead, you should make VTXIdentity a parameter to your settings class constructor. This will ensure the calling code knows you enforce security on this level and by definition knows where to obtain this token. Also, this allows you to throw an exception as soon as you know something is wrong, rather than when accessing some property. This will help maintainers catch errors earlier—much like compile errors are better than runtime errors.

Finally, while this is a fun exercise, there are plenty performant and tested solutions for storing and reading configuration in C#. Why do you think you need to reinvent the wheel?

Community
  • 1
  • 1
Dan Abramov
  • 264,556
  • 84
  • 409
  • 511
  • 1
    +1 - On target. Also available for this kind of problem / design choice is MEF - http://mef.codeplex.com/ – Hogan Dec 29 '12 at 00:42
5

Assuming your problem survives the discussion of whether you could just use another library rather than rolling your own... if you find yourself using C# 5 &.NET 4.5, take a look at the CallerMemberName attribute. With CallerMemberName you can modify your GetValue() method signature to be

public static string GetValue([CallerMemberName] string callerName = "")

The property can then call GetValue() with no parameter and you'll get the property name passed into GetValue() as you want.

Curt Nichols
  • 2,757
  • 1
  • 17
  • 24