Introduction
In Part 1 of this multipart series, I discussed the plug-in model that powers the core of the framework. If you have not read Part 1, I strongly urge you to go back and do so now. This article will assume that you have read and understands the plug-in model. This article will focus on how to use the configuration objects and events in order to speed development time by providing configuration files for your SnapIn's options.
Here's a link to Part 1 :: Plugins/Extensibility so check it out if you need to get up to speed.
What do you mean when you speak of configuration and options?
In virtually every program, be it a small program or large, inevitably you as a programmer will be faced with some decisions. What kind of options you allow the users to change, how they will make those changes, how you will store those options, and how you will access those options with the code. With the breakout success of XML, it seems silly and almost dumb to fall back upon previously successful methods like the Windows Registry or ini files.
You could choose to implement a custom serialized file, MFC has been doing this for years, but the intelligent design would be to construct a robust design that allowed multiple programs to all rely upon the same code base. It's all too common to discover that one program needs to read the options of another program, or worse yet to make changes to those options. Why compound the complexity by relying on different schemes. Find a common ground that works, whatever your choice is, and stick to it.
The Razor Framework provides a rich suite of classes to make creating, editing, and storing your options trivial. Using XML to store the serialized configuration files, you will open the door for easily allowing multiple programs to read and write a common format. And, XML is a proven technology, why else would you see the App.Config file using XML if that were not the case? Of course, Microsoft has never been a big fan of following the pack, nor do they like to stick to standards. I can't knock them too much, I make a living with their technology, so I'm not complaining.
Speaking of the App.config, and the .NET Configuration
namespace, why not use that? Well, for some painfully obvious reasons, it really doesn't provide any means of writing changes back out to disk for starters. What good is it if you can't save your changes when you need to. OK, you can with effort, but it's a pain. Why the creators of the Configuration
namespace didn't see fit to provide a good way is beyond me. Besides the glaring omission of a decent saving method, there aren't any events. How do you know when to reload an option, if nothing tells you that an option has changed?
Why is the Razor Framework's methodology better?
You should be asking what Razor can do for you, and why any of it is better than existing ideas. That is the focus of this article. For starters, the Razor.Configuration
namespace deploys a rich object and event model to allow you to be notified when a particular option or a category of options has changed. Gone are the days of making file system watchers to watch files for changes, and reloading of every single option to find the one that is different. With the Razor configuration model, you will be notified when an option changes.
In addition to events, the data that you will be storing can be organized hierarchically using categories. Such categorization allows you to logically separate options into different categories, instead of lumping them all together in a registry key or ini section. Again, think back to the App.config file that allows you to separate your options.
Once you have created your view of the options, you will also be privy to displaying the options using an extensible user interface. The .NET PropertyGrid
powers the display of the Razor options dialog, mimicking the VS.NET options. Options are easily grouped into categories, making the user interface less cluttered and easy to navigate.
Using the .NET PropertyGrid
to display the options, allows the framework to rely upon tested and proven .NET classes such as the UITypeEditor
, PropertyDescriptor
, and TypeConvertor
. The framework allows native serialization of all serializable types. This means that all you need to do, is provide serializable types to allow your data to be stored using the framework. If you need to provide custom views of your options, it's as easy as implementing a new UITypeEditor
, or relying upon an existing one.
And for the love of my monkey, can we get some cancel support up in here? I swear, the next app I use that doesn't supply me with a cancel feature in the Options dialog, I'm going to hunt the guy down who wrote it, and kick his... er. okay. My point is, all options and changes to those options should be cancelable. Both from code, and via the dialogs. You'll get this capability in Razor, and my money is that you're going to like it. May be you don't, but if not, I don't want to hear about it, cuz my fingers are sore from coding it. :P
Does it support more than one user?
Yes. The framework provides you with a configuration file that is common to all users, and a configuration file that is specific to each Windows user. It is Windows compliant in that it will store the files using the data paths specifically designated in Documents and Settings, instead of the executable path. Separating the files logically gives you the option of choosing where to store the options.
The files are created automatically, and are loaded when the hosting engine starts up, before any plugins would require its presence. It will detect and prompt for action, when write access is denied to the configuration files, or when and if a file becomes corrupted. This can occur due to a number of unforeseen factors, the most common arises during development of new plugins and new options.
Because of the distributed file model, and the location of the files, you can easily store program wide options in the common configuration, and user specific options in the local user configuration. Your plugin will be notified when it should install, read, write, or upgrade options to both the common and local user configuration files, helping to eliminate the hassle of upgrades or option management.
So in review, there are two configuration files, created by the hosting engine, accessible through the hosting engine as instance properties. They are as follows:
SnapInHostingEngine.Instance.CommonConfiguration
SnapInHostingEngine.Instance.LocalUserConfiguration
Anytime you need to add categories or options, just grab a hold of these babies, and get to coding.
Why would I need separate files?
This question can only be answered by you, the developer, as you design and implement your plugins. It is quite common to find options that are common to all users, such as database connections, and options that are specific to each user. User specific options might include toolbar and window positions. You would certainly not want to overwrite another user's window settings, and make someone mad. Remember, in the end we're attempting to make life easier for the user.
In addition to the separation of options, due to design considerations, you may also find that making a demo application that runs from a CD or DVD, becomes temperamental if you aren't careful about where your configuration files are stored. You could use the Registry, but the .NET recommendation for that is no. In addition, troubleshooting becomes even more dangerous when you are helping a user through a tight spot. Personally, I'd rather have the option to fall back upon deleting a single XML file, than having them delete Registry keys. What if they get the wrong one? Chances are pretty high that not everyone should be digging at the registry! Especially considering most of them won't know it's there unless you tell them. And then there's the whole left vs. right click, man I could rant on this for a long time. Keep the users out of the important system stuff please. For my sanity.
By storing the configuration files in well known and approved locations, designated by the .NET Framework, you are at least provided the guarantee that your program will have at minimum read access to your configuration files. Even roaming users are allowed write access to their profile's Documents and Settings path. And even the strictest of file system permissions should not interfere with your ability to read from the All Users data path where the common configuration is stored.
Setting up demo applications on salesmen's computers, can be simplified by running the program from an account once, to allow the files to be created with the options. Once the files are created, switching users should not pose a threat to your program's ability to function. Storing the options in the executable's path is pretty much out of the question if the program is running from non-writeable media such as a CD, DVD, or memory stick.
With those considerations in mind, let's move on to witnessing the Razor Framework's Configuration
namespace in action.
What classes will I be working with?
The majority of your work, as a developer when working with options, will focus on three main classes. They are as follows:
XmlConfiguration
XmlConfigurationCategory
XmlConfigurationOption
The XmlConfiguration
object is the top level wrapper class that exposes an infinitely hierarchical collection of categories.
The XmlConfigurationCategory
class exposes a list of options, as well as other properties that allow you to name each category, and mark it as read-only or persistent. Categories can be made volatile so that they are not persisted when the configuration is saved.
The XmlConfigurationOption
class exposes properties that will allow you to name each option, provide a description, and a UITypeEditor
that will be responsible for displaying and editing the option when it is viewed in the Options dialog. Each option may be marked read-only, so that changes are not possible from the dialog or code. Each option may also be marked visible or invisible, to completely hide it from the user. Sometimes you just need options that you don't want anyone to see. And just like its container, the category, individual options may be made volatile by marking them as non-persistent.
In addition to providing a robust object model, each object exposes events to allow you easy notification when changes are made. You may wire up independently to a single option, or collectively to an entire configuration or category. Each object derives from a common base class that provides a Changed
event. The eventargs
carry along an Action
property that will help you to understand the nature of the event. Whether the object is being changed, added, or removed is marked for each event.
The events all trickle upwards ending finally with the top level configuration object in which they are held. Wiring one event handler to the configuration's Changed
event will allow you notification of any modifications to the configuration object, the categories, or options that are contained inside.
How do I use this?
Enough horn blowing, let's get started and see how what I've described so far ties together. I've included a sample SnapIn that will install several options, using both the common and local user configuration files. The sample SnapIn will help to demonstrate how a SnapIn should accomplish the following:
- Installing options
- Reading options
- Writing options
- Upgrading options
Let's review the ISnapIn
interface really fast, to show you the events and virtual methods that you will have at your disposal.
using System;
using Razor.Features;
namespace Razor.SnapIns
{
public interface ISnapIn
{
event EventHandler InstallCommonOptions;
event EventHandler InstallLocalUserOptions;
event EventHandler UpgradeCommonOptions;
event EventHandler UpgradeLocalUserOptions;
event EventHandler ReadCommonOptions;
event EventHandler ReadLocalUserOptions;
event EventHandler WriteCommonOptions;
event EventHandler WriteLocalUserOptions;
event EventHandler UninstallCommonOptions;
event EventHandler UninstallLocalUserOptions;
event EventHandler Start;
event EventHandler Stop;
void OnInstallCommonOptions(object sender, System.EventArgs e);
void OnInstallLocalUserOptions(object sender, System.EventArgs e);
void OnUpgradeCommonOptions(object sender, System.EventArgs e);
void OnUpgradeLocalUserOptions(object sender, System.EventArgs e);
void OnReadCommonOptions(object sender, System.EventArgs e);
void OnReadLocalUserOptions(object sender, System.EventArgs e);
void OnWriteCommonOptions(object sender, System.EventArgs e);
void OnWriteLocalUserOptions(object sender, System.EventArgs e);
void OnUninstallCommonOptions(object sender, System.EventArgs e);
void OnUninstallLocalUserOptions(object sender, System.EventArgs e);
void OnStart(object sender, System.EventArgs e);
void OnStop(object sender, System.EventArgs e);
}
}
Every SnapIn that you create should inherit one of the classes in the SnapIn
namespace. Again to review, those classes are as follows:
SnapIn
(A generic lightweight plugin with no user interface, just a class).
SnapInControl
(A Control plugin)
SnapInWindow
(A Form plugin)
Choosing your base class determines whether you maintain support for the Windows Forms designer. Sometimes you may not need it, but it does prove quite useful to create a control or window, and then make it a plugin directly. For most cases, I would recommend inheriting from the SnapIn
class, as for most things you just won't need designer support.
Installing options from a SnapIn
The first time that the hosting engine loads your SnapIn, it will determine that it has not run the SnapIn before, and proceed to install it. This will trigger the InstallXXXOptions
events. These events will fire first before any other events, and only the first time the SnapIn is run, or every time that you install it. You can install/uninstall/reinstall any SnapIn on the fly using the SnapIns dialog. This dialog should be accessible from the Tools menu if you are looking at Part 1's demo application, or using my ApplicationWindowSnapIn
.
The hosting engine keeps its own configuration file that tracks some basic information about each SnapIn. Things like run count, install date, and version. Remember the SnapInVersionAttribute
that you applied to your SnapIn? Well, the hosting engine surely does. It will check this each time it loads and runs a SnapIn. If you want to bump the version number, because of some added functionality, the hosting engine will kindly see that you are upgrading or installing a new version of a SnapIn, triggering the Upgrade/Install events for you.
Let's take a look at some of the sample code that I have written to demonstrate how to install your options into the categories you want. Remember, the categories and options will show up in the Options dialog unless you mark them as Hidden, or volatile so be careful in your category naming. The user will most likely see this stuff. And that's really what it's all about, providing a way for you, and the user to get and change these options.
The next bit of code, is just the event handlers for the events. I always override the associated method for whatever event I'm handling, and place my code there. This allows me to call my override from the event, and have a nice wrapped up method that I can call later. Here're the event handlers for the installation events:
private void OptionsSampleSnapIn_InstallCommonOptions(object sender,
EventArgs e)
{
this.InstallMyCommonOptions();
}
private void OptionsSampleSnapIn_InstallLocalUserOptions(object sender,
EventArgs e)
{
this.InstallMyLocalUserOptions();
}
Now, once you've wired up the appropriate events, you'll want to override the associated method. There's one for each event, so don't sweat it. Keep in mind that these overrides are not natively called by the hosting engine. They are simply there for you as a guide. Instead of always just coding directly inside the event handlers, it's a good practice to move the code to a method. Between upgrades and other logic bombs, having methods around that are the same in every SnapIn makes larger projects easier. Trust me on this one, I've been maintaining and working on a large project that combines about 160+ SnapIns between three developers for well over a year now, and if we didn't have some standards, I'd go nuts trying to remember what each SnapIn did and where it did it. This is not a rule, it's just something I'd recommend that you follow. :P Trying to remember what you did nine months ago is hard enough, let alone trying to understand someone else's messed up logic, you just don't need the hassle. Hear me loud and clear on this please!
Here're the overrides for the installation events. Notice I always assert after I grab a category or configuration. Not one time have I ever seen this fail, it's just something I've picked up over the years, makes me feel safe and cozy. I always like to do it, because that Assert, well it's one of those "buzzwords", and I can go, yeah I use asserts. They sure aren't necessary, but if for some reason a configuration gets corrupted, I'm gonna know about it. The hosting engine will let you know before it gets to this point, this is just my own personal good luck charm. Call me superstitious. You can ignore that junk and focus on the rest of the code.
protected override void InstallMyCommonOptions()
{
base.InstallMyCommonOptions ();
try
{
XmlConfiguration configuration =
SnapInHostingEngine.Instance.CommonConfiguration;
Debug.Assert(configuration != null);
XmlConfigurationCategory category =
configuration.Categories[@"General", true];
Debug.Assert(category != null);
if (category.Options[OptionNames.Int.ToString()] == null)
category.Options.Add(SnapInOptions.CommonOptions.IntOption);
if (category.Options[OptionNames.String.ToString()] == null)
category.Options.Add(SnapInOptions.CommonOptions.StringOption);
if (category.Options[OptionNames.Float.ToString()] == null)
category.Options.Add(SnapInOptions.CommonOptions.FloatOption);
if (category.Options[OptionNames.ByteArray.ToString()] == null)
category.Options.Add(SnapInOptions.CommonOptions.ByteArrayOption);
if (category.Options[OptionNames.Bool.ToString()] == null)
category.Options.Add(SnapInOptions.CommonOptions.BoolOption);
}
catch(Exception ex)
{
Debug.WriteLine(ex);
}
}
protected override void InstallMyLocalUserOptions()
{
base.InstallMyLocalUserOptions ();
try
{
XmlConfiguration configuration =
SnapInHostingEngine.Instance.LocalUserConfiguration;
Debug.Assert(configuration != null);
XmlConfigurationCategory category =
configuration.Categories[@"General", true];
Debug.Assert(category != null);
if (category.Options[OptionNames.ArrayList.ToString()] == null)
category.Options.Add(SnapInOptions.LocalUserOptions.ArrayListOption);
if (category.Options[OptionNames.Color.ToString()] == null)
category.Options.Add(SnapInOptions.LocalUserOptions.ColorOption);
if (category.Options[OptionNames.DateTime.ToString()] == null)
category.Options.Add(SnapInOptions.LocalUserOptions.DateTimeOption);
if (category.Options[OptionNames.Point.ToString()] == null)
category.Options.Add(SnapInOptions.LocalUserOptions.PointOption);
if (category.Options[OptionNames.Path.ToString()] == null)
category.Options.Add(SnapInOptions.LocalUserOptions.PathOption);
}
catch(Exception ex)
{
Debug.WriteLine(ex);
}
}
Reading options from a SnapIn
Once you have your options installed, the next logical step is to read them back out. This is something you'll want to do every time your SnapIn starts. But, in the interest of a framework, the hosting engine is polite enough to trigger the ReadXXXOptions
events before it goes about starting your SnapIn. This allows you to get your options loaded up before you hit the Start event. Yes, you can read or install your options anytime you want to, but in practice, having some standard places to do this, really makes life easier. I'm trying to guide you in good designs, maybe even hold your hand a bit, but trust me, when you've got deadlines and problems to solve, the last thing you want to screw with is remember just where or how you stored that extra special option. If you always follow my guidelines, it's so easy to remember when and where things happen. Do it the same way in every SnapIn, and you'll find that pretty soon, you've got a pretty well laid out bit of code.
Reading the options is just as easy as installing them, with one change. This time you'll just want to keep a copy of the option's value around for later use. I usually have member variables that I load up with the option's values. After all, this was the whole point of this mess, remember. To get your options:
protected override void ReadMyCommonOptions()
{
base.ReadMyCommonOptions ();
try
{
XmlConfiguration configuration =
SnapInHostingEngine.Instance.CommonConfiguration;
Debug.Assert(configuration != null);
XmlConfigurationCategory category =
configuration.Categories[@"General", true];
Debug.Assert(category != null);
_int =
(int)category.Options[CommonOptionNames.Int.ToString()].Value;
_string =
(string)category.Options[CommonOptionNames.String.ToString()].Value;
_float =
(float)category.Options[CommonOptionNames.Float.ToString()].Value;
_byteArray =
(byte[])category.Options[CommonOptionNames.ByteArray.ToString()].Value;
_bool = (bool)category.Options[CommonOptionNames.Bool.ToString()].Value;
}
catch(Exception ex)
{
Debug.WriteLine(ex);
}
}
protected override void ReadMyLocalUserOptions()
{
base.ReadMyLocalUserOptions ();
try
{
XmlConfiguration configuration =
SnapInHostingEngine.Instance.LocalUserConfiguration;
Debug.Assert(configuration != null);
XmlConfigurationCategory category =
configuration.Categories[@"General", true];
Debug.Assert(category != null);
_arrayList =
(ArrayList)category.Options[LocalUserOptionNames.ArrayList.ToString()].Value;
_color =
(Color)category.Options[LocalUserOptionNames.Color.ToString()].Value;
_dateTime =
(DateTime)category.Options[LocalUserOptionNames.DateTime.ToString()].Value;
_point =
(Point)category.Options[LocalUserOptionNames.Point.ToString()].Value;
_path =
(string)category.Options[LocalUserOptionNames.Path.ToString()].Value;
}
catch(Exception ex)
{
Debug.WriteLine(ex);
}
}
Writing options from a SnapIn
After your SnapIn stops, every time it stops, the hosting engine will trigger the WriteXXXOptions
events. This is in case you have modified any options manually in code, that the configurations might not know about. This is where you will update the configurations with your local variables. You only need to do this if you've changed them. Changes made through the Options dialog are already made to the configurations, so again this is only there in case you've changed something manually.
Here's how you can do it though, just to drive the point home:
protected override void WriteMyCommonOptions()
{
base.WriteMyCommonOptions ();
try
{
XmlConfiguration configuration =
SnapInHostingEngine.Instance.CommonConfiguration;
Debug.Assert(configuration != null);
XmlConfigurationCategory category =
configuration.Categories[@"General", true];
Debug.Assert(category != null);
category.Options[CommonOptionNames.Int.ToString()].Value = _int;
category.Options[CommonOptionNames.String.ToString()].Value = _string;
category.Options[CommonOptionNames.Float.ToString()].Value = _float;
category.Options[CommonOptionNames.ByteArray.ToString()].Value = _byteArray;
category.Options[CommonOptionNames.Bool.ToString()].Value = _bool;
}
catch(Exception ex)
{
Debug.WriteLine(ex);
}
}
protected override void WriteMyLocalUserOptions()
{
base.WriteMyLocalUserOptions ();
try
{
XmlConfiguration configuration =
SnapInHostingEngine.Instance.LocalUserConfiguration;
Debug.Assert(configuration != null);
XmlConfigurationCategory category =
configuration.Categories[@"General", true];
Debug.Assert(category != null);
category.Options[LocalUserOptionNames.ArrayList.ToString()].Value = _arrayList;
category.Options[LocalUserOptionNames.Color.ToString()].Value = _color;
category.Options[LocalUserOptionNames.DateTime.ToString()].Value = _dateTime;
category.Options[LocalUserOptionNames.Point.ToString()].Value = _point;
category.Options[LocalUserOptionNames.Path.ToString()].Value = _path;
}
catch(Exception ex)
{
Debug.WriteLine(ex);
}
}
Upgrading options from a SnapIn
OK, so you've released a SnapIn, and several months go by and you now need to add some features. You end up rolling your SnapIn's version up, and need to move an option from one category to another. Maybe you need to delete an obsolete option. This is where you go. The UpgradeXXXOptions
are there in case you need to fix up or change things as you migrate to newer versions. This will only fire the first time a new version is loaded, before the InstallXXXOptions
event. I'm not going to cover this real hard, because chances are you won't ever need it. But if you do, read on. Otherwise skip ahead.
The hosting engine tracks some information about each SnapIn in the InstallationConfiguration.xml file, in the common data path. If you need to mess with this, you can delete that file, set the SnapIn version to the old version, and run it. Then rebuild the SnapIn after incrementing its version. The upgrade events will fire. Rinse and repeat until you can upgrade your stuff properly. Feel free to give me a yell if you end up needing more help than this. Chances are that you'll understand this better if you actually get around to incrementing SnapIn versions, because you'll have deployed some already and be pretty familiar with things by then. At least I hope so. :P
How can I tell when an option changes?
Alright, we've got our options installed, we know how to read them and write them. But what about change notification? Didn't you say that Razor would notify me when the changes were made? Yes, it will. Remember the three classes that I told you you would be working with, the configuration
, category
, and option
classes? They each inherit a common base class. This allows one single event on the configuration to fire events for any change. If an option five categories deep changes, it'll trickle up to the configuration that holds, it. The event will fire up through each nested category until it reaches the top. So for each change notification, you can look at the element that is changing and figure out if it's an option
, category
, or configuration
. Most times, you're just going to be interested in the options.
Something else that is nice about the options, is the ability to place any of the elements into edit mode. Once an element is in edit mode, you can make changes and cancel them if you aren't happy and fall back on the original values, or accept them and overwrite the originals with the new. This is how the Options dialog can allow OK, Cancel, Apply functionality. You gotta remember to look at the element's state when the Changed
event fires. If it's being edited, just ignore the event. You only want to concern yourself with elements that are not being edited.
Normally, I just wire up to the configuration, and let the events come to me. You could however wire up to the individual options, but why waste space with 20 event handlers, when one would suffice? I also wire up for changes when the SnapIn starts, and unwire when it stops. Here's the sample code for this:
protected override void StartMyServices()
{
base.StartMyServices ();
SnapInHostingEngine.Instance.CommonConfiguration.Changed +=
new XmlConfigurationElementEventHandler(CommonConfiguration_Changed);
SnapInHostingEngine.Instance.LocalUserConfiguration.Changed +=
new XmlConfigurationElementEventHandler(LocalUserConfiguration_Changed);
}
protected override void StopMyServices()
{
base.StopMyServices ();
SnapInHostingEngine.Instance.CommonConfiguration.Changed -=
new XmlConfigurationElementEventHandler(CommonConfiguration_Changed);
SnapInHostingEngine.Instance.LocalUserConfiguration.Changed -=
new XmlConfigurationElementEventHandler(LocalUserConfiguration_Changed);
}
Notice I've separated the events into the common and local user events. You could do this all in one event handler, but I'm really trying to drive the point home about how you have access to two different configurations. Here's the code I use to respond to changes:
private void CommonConfiguration_Changed(object sender,
XmlConfigurationElementEventArgs e)
{
if (e.Element.IsBeingEdited)
return;
if (e.Element.GetElementType() ==
XmlConfigurationElementTypes.XmlConfigurationOption)
{
string[] optionNames = Enum.GetNames(typeof(CommonOptionNames));
foreach(string optionName in optionNames)
if (string.Compare(e.Element.Fullpath,
SnapInHostingEngine.DefaultCommonConfigurationName +
@"\General\" + optionName, true) == 0)
{
this.ReadMyCommonOptions();
break;
}
}
}
private void LocalUserConfiguration_Changed(object sender,
XmlConfigurationElementEventArgs e)
{
if (e.Element.IsBeingEdited)
return;
if (e.Element.GetElementType() ==
XmlConfigurationElementTypes.XmlConfigurationOption)
{
string[] optionNames = Enum.GetNames(typeof(LocalUserOptionNames));
foreach(string optionName in optionNames)
if (string.Compare(e.Element.Fullpath,
SnapInHostingEngine.DefaultLocalUserConfigurationName +
@"\General\" + optionName, true) == 0)
{
this.ReadMyLocalUserOptions();
break;
}
}
}
When I write SnapIns, I'll usually make one SnapIn that just handles a group of options. Normally options come in packs, not just by themselves, so it's kind of handy to write some code that can handle a bunch of them. In my sample, I just used an enum
for the option names, so that I could build and check the names of changing elements on the fly. You don't have to do this, but it keeps my head from hurting and we don't have to stare at a massive switch
statement. However, if this was a mission critical SnapIn, and I wanted speed, I'd go by that route. This is just for show and to teach you all the concepts at work.
Notice that the options appear to have a path based naming scheme. Each element in a configuration can be referenced using "X\Y\Z" path notation. Just like a file has a full path, so does each element. They can be retrieved or added using this notation to help you save time. If you wanted to add a bunch of categories, or make sure they are there, this is the quickest way. What if you wanted to access an option that is 10 categories deep, yup, this is the quickest way. When I say quickest, I mean for you, the coder. Saves the typing. Internally, the navigation is no faster or slower however you retrieve the categories or options. I use the same methods internally regardless, but this stuff will save you time as you reference the elements in a configuration.
Well, that's about it for Part 2.You should be good to go on how to use the Configuration
namespace of the Razor Framework. Dig into the code. Checkout how you can tweak out the options. Definitely run the sample, and check out the custom UITypeEditors
and other cool things you can do with this.
I'm sure I've misspelled things left and right, I'm pretty tired right now. I'll be back tomorrow to make corrections. Give me a shout if you want. I'll try and update the article and clean it up a bit. Just really been getting a lot of requests for part 2, so I felt like I had to get this out. Even if it's rough, it's better than nothing! I think. :P No, I'm sure it is.
As always, give me a vote to let me know if you like this stuff or not. The votes and comments are the only thing that keep me posting and sharing! If this stuff blows, tell me in the comments, I'll find a way to fix whatever mistake I've made. Hopefully some of the guys in the Razor workspace will come in and tear this up so I can improve it! :P
By the by, if you want to join up and contribute, here's the link to the workspace on GotDotNet. Hope to see you in there.
Cheers!