Working with xml transformations in SharePoint: Part 2

This is the second part of this two part series about working with xml transformations and especially how to benefit from them when using app.config files as a source for application settings in SharePoint.
In the first part of the series I we finished off with having two different kinds of transformed app.config files. The first one was “pushed” to SharePoint as a root file (TEMPLATE/CONFIG), and the second one as an element file inside a feature.

So in this, the last part of the series, I´m going to show you how to make use (aka read from) of those two app.config files as a source for settings. I will make it a bit simple by just reading the settings and bind them to two different repeater controls and “printing” them out like html on an application page. I am, though, going to accomplish this in the same manner as I would in real world scenario. The difference is that in a real world application you read just one setting (or many) in a webpart or some other place.

The sample application and the source code can be found in my github repo.

Let´s get started

Since these files are app.config files you might wonder why I simply just “merge” them with the web.config file in SharePoint and use ConfigurationManager.AppSettings to read them?
The answer is that I am, kind of, but I want to avoid writing to web.config via the SharePoint API as much as possible since it´s really a pain and I wanted to show you an alternative. I could maybe use a separte web.config file in a subdirectory but that would only be useful for application pages in that same subdirectory however I might do that in a future post.

This time though I´m going to use a DDD (Domain-Driven design) approach with an entity and a service so first of all I´ll create an entity called a setting, pretty straight forward:

Then, I´ll create a contract (an interface) for communicating with service that can give me a setting based on its key or all the settings.

 

The ServiceLocator

In this example I have all classes and interfaces in just one project, but in a real world scenario you might want to put these into different projects (layers) and use an IOC container for resolving instances of the ISettingService. But this is not the scope of this post though.

So the service locator (my little IOC) is just a simple one allowing me to get instances of the ISettingService, not by all means a perfect one but that´s not the point of this post as I said.

 

The MappedFolderSettingService

I have two implementations of the ISettingService, they are pretty much the same but they read have differences. The code for the the service that will read its settings from the root file in TEMPLATE/CONFIG looks like this:

<br />class MappedFolderSettingService : ISettingService<br />{<br /><%%KEEPWHITESPACE%%> Dictionary&lt;string, Setting&gt; settings;<br /><br /><%%KEEPWHITESPACE%%> public MappedFolderSettingService()<br /><%%KEEPWHITESPACE%%> {<br /><%%KEEPWHITESPACE%%>   InitSettings();<br /><%%KEEPWHITESPACE%%> }<br /><br /><%%KEEPWHITESPACE%%> private void InitSettings()<br /><%%KEEPWHITESPACE%%> {<br /><%%KEEPWHITESPACE%%>   FileInfo file = new FileInfo(Path.Combine(SPUtility.GetGenericSetupPath(@"CONFIG\MyExample"), "app1.config"));<br /><br /><%%KEEPWHITESPACE%%> if (file.Exists == false)<br /><%%KEEPWHITESPACE%%> {<br /><%%KEEPWHITESPACE%%>   throw new FileNotFoundException("No config file", "app1.config");<br /><%%KEEPWHITESPACE%%> }<br /><br /><%%KEEPWHITESPACE%%> settings = (from KeyValueConfigurationElement appSetting in file.AppSettings()<br /><%%KEEPWHITESPACE%%>             select appSetting)<br /><%%KEEPWHITESPACE%%>              .ToDictionary(kvce =&gt; kvce.Key, kvce =&gt; new Setting { Key = kvce.Key, Value = kvce.Value });<br /><%%KEEPWHITESPACE%%> }<br /><br /><%%KEEPWHITESPACE%%> public bool TryLocateByKey(string key, out Model.Setting value)<br /><%%KEEPWHITESPACE%%> {<br /><%%KEEPWHITESPACE%%>   return settings.TryGetValue(key, out value);<br /><%%KEEPWHITESPACE%%> }<br /><br /><%%KEEPWHITESPACE%%> public IEnumerable&lt;Model.Setting&gt; FindAll()<br /><%%KEEPWHITESPACE%%> {<br /><%%KEEPWHITESPACE%%>   return settings.Values;<br /><%%KEEPWHITESPACE%%> }<br /><%%KEEPWHITESPACE%%> }<br />

The service has an internal dictionary that uses the key of the setting as an identifier. The dictionary is initialized by reading KeyValueConfigurationElements from the app.config file that in turn are retrieved by an extension method on the FileInfo class. That code looks like this:

<br /><%%KEEPWHITESPACE%%> public static KeyValueConfigurationCollection AppSettings(this FileInfo instance)<br /><%%KEEPWHITESPACE%%> {<br /><%%KEEPWHITESPACE%%> Configuration configuration = null;<br /><br /><%%KEEPWHITESPACE%%> try<br /><%%KEEPWHITESPACE%%> {<br /><%%KEEPWHITESPACE%%> var map = new ExeConfigurationFileMap { ExeConfigFilename = instance.FullName };<br /><%%KEEPWHITESPACE%%> configuration = ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationUserLevel.None);<br /><br /><%%KEEPWHITESPACE%%> if (configuration.HasFile == false)<br /><%%KEEPWHITESPACE%%> {<br /><%%KEEPWHITESPACE%%>   throw new Exception("FileInfoExtensions: The Configuration instance has no file associated. ExeConfigurationFileMap was constructed with an invalid filepath.");<br /><%%KEEPWHITESPACE%%> }<br /><%%KEEPWHITESPACE%%> }<br /><%%KEEPWHITESPACE%%> catch (Exception e)<br /><%%KEEPWHITESPACE%%> {<br /><%%KEEPWHITESPACE%%>   throw;<br /><%%KEEPWHITESPACE%%> }<br /><br /><%%KEEPWHITESPACE%%> return configuration.AppSettings.Settings;<br /><%%KEEPWHITESPACE%%> }<br />

This extension method used the static OpenMappedExeConfiguration method on the ConfigurationManager class with and instance of the ExeConfigurationFileMap class which is initialized with the full path of the FileInfo class (the path to the app.config file).

That´s pretty much how this service works so let´s move on the the other one.

 

The SPWebSettingService

This implementation instead will use the propertybag on the current SPWeb instance for storing the settings. So first of all we need to get the settings in there since we are not going to read directly from the app.config file that was actually an element file inside a feature.

A feature reciever

<br /><%%KEEPWHITESPACE%%> [Guid("acbfb733-e375-4aa8-88d9-b9edda66603d")]<br /><%%KEEPWHITESPACE%%> public class MyExampleEventReceiver : SPFeatureReceiver<br /><%%KEEPWHITESPACE%%> {<br /><br /><%%KEEPWHITESPACE%%> public override void FeatureActivated(SPFeatureReceiverProperties properties)<br /><%%KEEPWHITESPACE%%> {<br /><br /><%%KEEPWHITESPACE%%> SPWeb website = properties.Feature.Parent as SPWeb;<br /><br /><%%KEEPWHITESPACE%%> FileInfo file;<br /><%%KEEPWHITESPACE%%> if (properties.Feature.TryLocateElementFile("app2.config", out file) == false)<br /><%%KEEPWHITESPACE%%> {<br /><%%KEEPWHITESPACE%%>   throw new FileNotFoundException("element file file not found", "app2.config");<br /><%%KEEPWHITESPACE%%> }<br /><br /><%%KEEPWHITESPACE%%> foreach (KeyValueConfigurationElement appSetting in file.AppSettings())<br /><%%KEEPWHITESPACE%%> {<br /><%%KEEPWHITESPACE%%>   string fullkey = Setting.BuildWebSitePropertyKey(appSetting.Key);<br /><br /><%%KEEPWHITESPACE%%> if (website.AllProperties.Contains(fullkey))<br /><%%KEEPWHITESPACE%%> {<br /><%%KEEPWHITESPACE%%>   website.SetProperty(fullkey, appSetting.Value);<br /><%%KEEPWHITESPACE%%> }<br /><%%KEEPWHITESPACE%%> else<br /><%%KEEPWHITESPACE%%> {<br /><%%KEEPWHITESPACE%%>   website.AddProperty(fullkey, appSetting.Value);<br /><%%KEEPWHITESPACE%%> }<br /><br /><%%KEEPWHITESPACE%%>  website.Update();<br /><%%KEEPWHITESPACE%%> }<br /><br /><%%KEEPWHITESPACE%%> }<br /><br /><%%KEEPWHITESPACE%%> public override void FeatureDeactivating(SPFeatureReceiverProperties properties)<br /><%%KEEPWHITESPACE%%> {<br /><%%KEEPWHITESPACE%%> SPWeb website = properties.Feature.Parent as SPWeb;<br /><br /><%%KEEPWHITESPACE%%> var query = from DictionaryEntry property in website.AllProperties<br /><%%KEEPWHITESPACE%%>             where property.Key.ToString().StartsWith(Setting.Prefix)<br /><%%KEEPWHITESPACE%%>             select property.Key.ToString();<br /><br /><%%KEEPWHITESPACE%%> foreach (var result in query)<br /><%%KEEPWHITESPACE%%> {<br /><%%KEEPWHITESPACE%%>   website.DeleteProperty(result);<br /><%%KEEPWHITESPACE%%>   website.Update();<br /><%%KEEPWHITESPACE%%> }<br /><%%KEEPWHITESPACE%%> }<br /><br /><%%KEEPWHITESPACE%%> }<br />

You can see that we will use the same approach for reading the app.config file as in the previous service implementation but again we have an extension method called TryLocateElementFile

<br /><%%KEEPWHITESPACE%%> public static DirectoryInfo Directory(this SPFeature instance)<br /><%%KEEPWHITESPACE%%> {<br /><%%KEEPWHITESPACE%%>   return new DirectoryInfo(instance.Definition.RootDirectory);<br /><%%KEEPWHITESPACE%%> }<br /><br /><%%KEEPWHITESPACE%%> public static bool TryLocateElementFile(this SPFeature instance, string fileName, out FileInfo value)<br /><%%KEEPWHITESPACE%%> {<br /><br /><%%KEEPWHITESPACE%%> value = instance.Directory()<br /><%%KEEPWHITESPACE%%>         .GetFiles(fileName, SearchOption.AllDirectories)<br /><%%KEEPWHITESPACE%%>         .FirstOrDefault();<br /><br /><%%KEEPWHITESPACE%%> return value != null;<br /><%%KEEPWHITESPACE%%> }<br />

The extension method searches the files in the feature directory for the filename (app.config in the example) that was passed in.

The settings are then stored with a prefix in the propertybag so we can find them. The setting class has to be altered to accomplish this:

OK, now we are ready to read the settings from the current SPWeb in our service, the code looks like this:

<br /><%%KEEPWHITESPACE%%> class SPWebSettingService : ISettingService<br /><%%KEEPWHITESPACE%%> {<br /><%%KEEPWHITESPACE%%> Dictionary&lt;string, Setting&gt; settings;<br /><br /><%%KEEPWHITESPACE%%> public SPWebSettingService()<br /><%%KEEPWHITESPACE%%> {<br /><%%KEEPWHITESPACE%%>   InitSettings();<br /><%%KEEPWHITESPACE%%> }<br /><br /><%%KEEPWHITESPACE%%> private void InitSettings()<br /><%%KEEPWHITESPACE%%> {<br /><%%KEEPWHITESPACE%%> SPWeb website = SPContext.Current.Web;<br /><br /><%%KEEPWHITESPACE%%> settings = (from DictionaryEntry property in website.AllProperties<br /><%%KEEPWHITESPACE%%>             where property.Key.ToString().StartsWith(Setting.Prefix)<br /><%%KEEPWHITESPACE%%>             select property)<br /><%%KEEPWHITESPACE%%>              .ToDictionary(de =&gt; de.Key.ToString(), de =&gt; new Setting { Key = de.Key.ToString(), Value = de.Value.ToString() });<br /><br /><%%KEEPWHITESPACE%%> }<br /><br /><%%KEEPWHITESPACE%%> public bool TryLocateByKey(string key, out Model.Setting value)<br /><%%KEEPWHITESPACE%%> {<br /><%%KEEPWHITESPACE%%>   return settings.TryGetValue(key, out value);<br /><%%KEEPWHITESPACE%%> }<br /><br /><%%KEEPWHITESPACE%%> public IEnumerable&lt;Model.Setting&gt; FindAll()<br /><%%KEEPWHITESPACE%%> {<br /><%%KEEPWHITESPACE%%>   return settings.Values;<br /><%%KEEPWHITESPACE%%> }<br /><%%KEEPWHITESPACE%%> }<br />

 

 

Putting it all together

<br /><%%KEEPWHITESPACE%%> public partial class TestSettings : LayoutsPageBase<br /><%%KEEPWHITESPACE%%> {<br /><%%KEEPWHITESPACE%%> ISettingService mappedfolderSVC;<br /><%%KEEPWHITESPACE%%> ISettingService spwebSVC;<br /><br /><%%KEEPWHITESPACE%%> protected void Page_Load(object sender, EventArgs e)<br /><%%KEEPWHITESPACE%%> {<br /><%%KEEPWHITESPACE%%>   Repeater1.DataSource = mappedfolderSVC.FindAll();<br /><%%KEEPWHITESPACE%%>   Repeater1.DataBind();<br /><br /><%%KEEPWHITESPACE%%>   Repeater2.DataSource = spwebSVC.FindAll();<br /><%%KEEPWHITESPACE%%>   Repeater2.DataBind();<br /><%%KEEPWHITESPACE%%> }<br /><br /><%%KEEPWHITESPACE%%> public TestSettings()<br /><%%KEEPWHITESPACE%%> {<br /><%%KEEPWHITESPACE%%>   mappedfolderSVC = ServiceLocators.SettingServiceLocator.SettingService&lt;MappedFolderSettingService&gt;();<br /><%%KEEPWHITESPACE%%>   spwebSVC = ServiceLocators.SettingServiceLocator.SettingService&lt;SPWebSettingService&gt;();<br /><%%KEEPWHITESPACE%%> }<br /><%%KEEPWHITESPACE%%> }<br />

Finally, in the code for the application page we will use these two instances of the ISettingService given to us by the little IOC container (again, note that in a real world scenario I would use unity or some other IOC for this).

The settings are the retrieved and bound to two different repeaters and the final result :

This is just a little example but I hope you get the idea and use it in your own implementations…

, , , ,

  1. Working with xml transformations in SharePoint: Part 2
  2. Making use of configuration file transformations in SharePoint 2010 « Johan Leino

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: