Why wouldn’t you use the App model for On-Premises SharePoint solutions?

It’s been a while since my last post, and due to a mini-hiatus I decided to take over the Christmas/New Years period in relation to certification studying, I figured it was time for a little something different on this blog. Truth be known, this is a conversation I’ve had with a number of pro-App model colleagues as far back as October last year. I tend not to be able to help myself when it comes to playing the devil’s advocate role (in fact I’ve been given the ‘black hat‘ on too many occasions to think it a coincidence) so this position did come perhaps a little too naturally for me, even with my bias towards heavily branded public facing solutions.

I was actually inspired to write about this from a recent #CollabTalk thread via a question posed by Christian Buckley (@buckleyplanet)

buckley

My favourite responses came from a cloud-focused Microsoft employee on the SharePoint team, Mark Kashman (@mkashman)

kashman

And a SharePoint Certified Master, Chris Beckett (@teknirvana)

beckett

I found the honesty of the responses refreshing.

It’s no secret Microsoft’s position on this one. You only have to read the official documentation on MSDN such as Deciding between apps for SharePoint and SharePoint solutions or Apps for SharePoint compared with SharePoint solutions to realise they’re pushing the App model in a big way. A couple of ‘tell us what you really think’ quotes that spring to mind from the aforementioned articles include ‘With these considerations in mind, apps should be looked at as the primary choice, and full-trust solutions should be looked at when the capability of the app model does not meet the business requirement.’ and ‘The most important guidance we can give you is to develop an app for SharePoint instead of a farm solution or NCSS whenever you can’.

The cynic in me however still believes there may be an ulterior motive to this position. Microsoft has been ‘all-in‘ for the cloud for a long time and there are a number of very good reasons why they would prefer clients be on Office 365 rather than managing their own on-premises farms. It therefore makes sense that they would want to remove as many barriers to entry to that transition as possible, full trust solutions being a significant one.

So as I always do, I set out to read as many decent articles as I could on the topic. I had remembered some time ago reading the SP-king-of-controversy, Bjørn Furuknap’s SharePoint 2013 App Model Solves Non-Problems Only? article and even though it was one of the only ones that would potentially back up my argument, it seemed to be arguing against the necessity of the App model rather than why it perhaps shouldn’t be used. That article introduced me to Doug Ware’s posts SharePoint 2013 Preview – Apps or Crapps?The SharePoint 2013 App Model is better than Farm Solutions and An Architecture for Provider SharePoint 2013 Hosted Apps on Azure which definitely argue the counter-point well.

Alaa Mostafa’s SharePoint 2013 Development (Apps versus Solutions) was a decent read which pushed the App model as was Jeremy Thake’s SharePoint Apps Playbook Series: Part 1 – SharePoint Apps vs SharePoint Solutions (was looking forward to the series, lets hope for more!). There was also a logged discussion SPChat transcript: App Model vs Solution Model with Jeremy Thake which helped frame my thoughts.

So what are those thoughts? Well, in a perfect world it’s clear that Apps would be the way to go. There are a number of things that concern me though looking from a wider consultancy view rather than just my own (where did I put that ‘these thoughts are my own’ disclaimer..):

  • The learning curve of developing with the App model against sticking to what most already know. The MSDN article I referenced before actually counters this point, arguing that it is not the only factor to consider and that it may be outweighed by the extensive lifecycle management process required for full-trust solutions. Not everyone is a senior consultant capable of making this transition on a client site however.
  • The ‘new’ nature of the beast. Anyone who has been around SharePoint long enough (and this has been particularly relevant with SP2013) would know that things rarely ‘just work’ in the first iteration. I can definitely see this causing a lot of unnecessary headaches on projects.
  • The added development time the above points could lead to. Increased complexity + less familiarity is sure to equal more $$. When you’re competing for work it’s not always best to ‘theoretically’ offer the best-MS-approved solution if you don’t get to implement it for being cost noncompetitive. It also wouldn’t paint the most rosy of pictures of any existing consultant banging their heads against the wall for a seemingly easy requirement.
  • Getting the App model up and running on-premises isn’t a trivial thing, which again adds added complexity and cost to any project which hasn’t already set up their environment for it.

I also wonder if its just me that thinks that this shouldn’t be a purely technical decision and should be one that involves the business. I believe that as consultants we should advise the client on the best way forward by giving them all the facts (and perhaps even some opinions) but allowing them to make a business decision with that information in front of them. Why should we push the App model if the client has no desire to ever move to the cloud? My argument is if you’re on-premises and you have no short-to-medium aspirations to head to the cloud, then where’s the justification for the steep learning curve, increased complexity and increased cost of building Apps.

Now that I feel I’ve made my point, i’m going to do a complete 180. The App model in my opinion should be on any consultant-worth-their-salt’s priority learning list. The direction is clear, and it will become THE way to develop for SharePoint sometime in the future. I’ve put my money where my mouth is in this case, registering for SPC14 with the aim to attend a number of cloud and App related sessions as well as Sonya Koptyev, Kirk Evans, and Richard diZerega‘s pre-conference sessions on Migrating Traditional SharePoint Solutions to the App Model and Refactoring Business Solutions into Apps for Office. One more excellent blog that should also be on the must-read list I feel I need to point out is Vesa Juvonen‘s – definitely worth following for those wanting to transition to the App model.

I’m curious to see if my view changes over time, particularly after SPC14 and hopefully after a number of healthy debates in regards to whether the App model should be chosen over full-trust solutions for on-premises SharePoint installations. If you care to add to the debate i’d be more than happy to hear from you through any medium you wish – i’m definitely prepared to be convinced either way.

How I passed 70-331

Last week I sat and passed the exam for 70-331 – Core Solutions of Microsoft SharePoint Server 2013. After having sat a couple of the prerequisite exams for the SharePoint certification streams it was good to get back to reading and learning about SharePoint and the content for this exam didn’t disappoint – it’s always valuable when the study material for a given exam allows you to learn new things and the content for this one opened my eyes up to some pieces of information I wasn’t previously familiar with. For all of the detractors out there regarding Microsoft certifications, this is probably the most valuable argument for why becoming certified has merit (of course you could read this material without sitting an exam, but for me it really helps to focus areas of study – otherwise there’s just so much information out there to get through!).

My preparation for this exam was quite significant. I had a bit of time up my sleeve so I went down the route of both reading as many relevant TechNet articles as possible and also reading Exam Ref 70-331: Core Solutions of Microsoft SharePoint Server 2013. The book started off fairly dry and I was wondering if I’d get any benefit from it but i’m glad I stuck with it, by the end I was very happy with the content I learnt from reading it and it was actually one of the better books I’ve read for learning about SharePoint recently which was somewhat surprising for what I expected would be just a manual to assist with the exam preparation. In the end I probably dedicated about 50h of study time for this one.

Overall I found the exam quite difficult even though I went in fairly confident. I found Alex Dean’s post 70-331 Insights to be a fairly accurate account of my experience also so I figured I’d link to that rather than duplicate it all here.

I mentioned that I read a number of TechNet articles – I primarily used the Born to Learn site’s Server Certification Study Group wiki to determine what to read, however a number of other options exist including Vlad Catrinescu’s Study Guide for Exam 70-331 Core Solutions of Microsoft SharePoint Server 2013, Gavin McKay’s Studying for Exam 70-331: Core Solutions of Microsoft SharePoint 2013 and Becky Bertram’s currently under construction Exam 70-331 Study Guide. To be honest the first 3 I’ve listed are very similar (even to the point where the mistakes are the same!) so I don’t know who should get the ultimate credit but otherwise all will likely serve the purpose. Finding out about the Born to Learn site was a great positive of this process – I feel it’s going to be a heavily visited site for me in the future!

While I didn’t watch any videos this time around there are a number of options out there for those that wish to pursue that way of learning. Some of the options include Pluralsite’s SharePoint Server 2013 Core Solutions (70-331), CBT Nugget’s Microsoft SharePoint Server 2013 70-331, Channel 9’s Exam Prep: 70-331 and 70-332 – MCSE: SharePoint (Microsoft SharePoint Server 2013) and TechNet’s SharePoint 2013 training for IT pros.

Another valuable resource you should be aware of is the Microsoft Virtual Academy site. I did find a page for Core Solutions of Microsoft SharePoint Server 2013: exame 70-331 however it is in another language so perhaps not so useful for those of us that only speak English! Plus it wouldn’t actually let me access the content. Regardless – I think this is going to be an ever improving and valuable site to keep an eye on for future certification efforts.

So that about covers everything. I enjoyed the content of this exam and the process of studying for it which is always a plus when setting off on the path to gain the overall SharePoint certifications. Next on the list will be 70-332 so I’m hoping that one is similarly well created content wise.

As always, best of luck for passing this exam!

Harnessing SignalR in SharePoint 2013 (Office 365)

Over a year ago I wrote a post on Harnessing SignalR in SharePoint. Back then, SignalR was fairly new and the amount of information out there was limited. Getting SignalR to work with SharePoint posed some challenges, all of which are outlined in that post, however eventually we got it working. Since then I’ve had little to do with the technology however the colleague I worked with on that proof of concept, Elliot Wood, has continued to keep on top of the changes to the library and the implications that may have for SharePoint integration. While i’m posting this to my blog, i’m doing so on behalf of Elliot who is responsible for all of the magic soon to be displayed.

Even though there was a fair amount of excitement in regards to SignalR, and particularly the work we did around getting it working in SharePoint, the number of subsequent posts along the same lines was fairly limited. One person who carried the torch was Maximilian Melcher – his post SignalR in SharePoint 2013 – the real-time web is coming! outlined that integrating SignalR in SharePoint 2013 wasn’t as easy as one might expect – even though we were now on the .NET 4.0 framework which should have made life much easier. Max ended up getting his solution working and subsequently posted a codeplex solution, however the post was enough to pose the challenge to Elliot to get it working as easily as possible.

The solution presented uses an autohosted App for SharePoint 2013 hosted on Office 365. With Microsoft’s direction seemingly steering away from full-trust and sandboxed solutions and straight towards the App model, this can be considered the most viable and future-proof solution to the ‘SharePointR’ dream.

To ensure the focus of this post remains focussed on integrating SignalR with SharePoint i’m going to avoid the specifics about SignalR and how to program with it. I’m also going to avoid specifics on how to create and deploy SharePoint Apps. We’ve come a long way with SignalR and the information out there is much better – I’d recommend reading ASP.NET SignalR Hubs API Guide – Server (C#) if you need an overview of programming with SignalR. For those that need a primer on SharePoint Apps, particularly in relation to provider-hosted Apps on Office 365 (the more ‘production-safe’ way of hosting Apps on Office 365), then head over to Chris O’Brien’s post Deploying SP2013 provider-hosted apps/Remote Event Receivers to Azure Websites (for Office 365 apps) for a quick read.

Now that you’re comfortable with the theory, it’s on to the solution. There are 2 pieces to the autohosted App puzzle – the SharePoint App and the SharePoint App Web.

SharePoint App

The purpose of this project is to define the Remote Event Receiver (RER) and also configure the Installed Event Endpoint for the App. The former essentially sets the types of events that will trigger the remote service and points the App to that service, the latter points to the service which will trigger when the App is installed.

Adding the RER is as simple as right-clicking the project and adding a new Remote Event Receiver and selecting the events you want to listen out for. Everything else will be taken care of for you including creating the Package, Feature and tying the RER to its class which will be hosted in the SharePoint App Web.

Configuring the Installed Event Endpoint requires you to edit the properties of the SharePoint App project and set the Handle App Installed property to true. Once again Visual Studio takes care of the rest.

The final step is to access the Permissions tab in the AppManifest.xml editor and add the necessary permissions to the Web and List to ensure your App has the required levels of access to your site.

That’s all there is to it.

SharePoint App Web

Setting up the SharePoint App Web project is a little more involved than the above however is still relatively simple. The first step is to add a SignalR Hub Class to the project – having this native in Visual Studio is fantastic and greatly simplifies the process of getting up and running with SignalR ensuring all necessary references are added to the solution (note that you must have installed the ASP.NET and Web Tools 2012.2 update before you can add this class natively). Alternatively you can add SignalR via the Package Manager Console.

Elliot has also decided to implement the hub using a Singleton instance for optimum performance. For this you’ll need to add another class and insert the following code:

    public class SharePointR
    {
        // Singleton instance
        private readonly static Lazy<SharePointR> _instance = new Lazy<SharePointR>(() =>
            new SharePointR(GlobalHost.ConnectionManager.GetHubContext<SharePointRHub>().Clients));

        public SharePointR(IHubConnectionContext clients)
        {
            Clients = clients;
        }

        public static SharePointR Instance
        {
            get
            {
                return _instance.Value;
            }
        }

        private IHubConnectionContext Clients
        {
            get;
            set;
        }

        public void NotifyDataChanged(string ListName, string Event)
        {
            Clients.Group(ListName).dataChanged(Event);
        }
    }

Once that’s been created we can edit the Hub we added earlier and insert:

    [HubName("SharePointRHub")]
    public class SharePointRHub : Hub
    {
        private readonly SharePointR _sharePointR;

        public SharePointRHub() : this(SharePointR.Instance) { }

        public SharePointRHub(SharePointR sharePointR)
        {
            _sharePointR = sharePointR;
        }

        public void Subscribe(string ListName)
        {
            Groups.Add(Context.ConnectionId, ListName);
        }

        public void UnSubscribe(string ListName)
        {
            Groups.Remove(Context.ConnectionId, ListName);
        }

        public void NotifyDataChanged(string ListName, string Event)
        {
            _sharePointR.NotifyDataChanged(ListName, Event);
        }
    }

One final piece to the SignalR puzzle is to add a Global.asax file which should include the following:

        protected void Application_Start(object sender, EventArgs e)
        {
            var hubConfiguration = new HubConfiguration();
            hubConfiguration.EnableCrossDomain = true;
            hubConfiguration.EnableDetailedErrors = true;
            RouteTable.Routes.MapHubs("/signalr", hubConfiguration);
        }

Now we have our SignalR plumbing we can set up our event receivers. The first cab off the rank is the AppEventReceiver which will fire when the App is installed. What we want to achieve here is to manually hook up the item event receivers to a list in the host web, for instance an Announcements list.

    public class AppEventReceiver : IRemoteEventService
    {
        private string ReceiverName = "RemoteEventReceiver";
        private List<EventReceiverType> EventType = new List<EventReceiverType>()
        {
            EventReceiverType.ItemAdded,
            EventReceiverType.ItemAdding,
            EventReceiverType.ItemUpdated,
            EventReceiverType.ItemUpdating,
            EventReceiverType.ItemDeleted,
            EventReceiverType.ItemDeleting
        };
        private string ListTitle = "Announcements";

        public SPRemoteEventResult ProcessEvent(SPRemoteEventProperties properties)
        {
            SPRemoteEventResult result = new SPRemoteEventResult();

            using (ClientContext clientContext = TokenHelper.CreateAppEventClientContext(properties, false))
            {
                Web hostWeb = clientContext.Web;
                clientContext.Load(hostWeb);

                List docLib = clientContext.Web.Lists.GetByTitle(ListTitle);

                string opContext = OperationContext.Current.Channel.LocalAddress.Uri.AbsoluteUri.Substring(0,
                   OperationContext.Current.Channel.LocalAddress.Uri.AbsoluteUri.LastIndexOf("/"));
                string remoteUrl = string.Format("{0}/RemoteEventReceiver.svc", opContext);

                if (properties.EventType == SPRemoteEventType.AppInstalled)
                {
                    foreach (var eventType in EventType)
                    {
                        EventReceiverDefinitionCreationInformation newEventReceiver = new EventReceiverDefinitionCreationInformation()
                        {
                            EventType = eventType,
                            ReceiverAssembly = Assembly.GetExecutingAssembly().FullName,
                            ReceiverClass = "SignalRProxyService.Services.RemoteEventReceiver",
                            ReceiverName = ReceiverName + eventType.ToString(),
                            ReceiverUrl = remoteUrl,
                            SequenceNumber = 1000
                        };
                        docLib.EventReceivers.Add(newEventReceiver);
                    }
                    clientContext.ExecuteQuery();
                }
                else if (properties.EventType == SPRemoteEventType.AppUninstalling)
                {
                    IEnumerable<EventReceiverDefinition> receivers = clientContext.LoadQuery(docLib.EventReceivers
                        .Where(e => e.ReceiverName == ReceiverName));

                    foreach (var rec in receivers)
                    {
                        rec.DeleteObject();
                    }
                    clientContext.ExecuteQuery();
                }
            }
            return result;
        }

        public void ProcessOneWayEvent(SPRemoteEventProperties properties)
        {
            // This method is not used by app events
        }
    }

We then want to code up the Remote Event Receiver to handle the events triggered on the site and push a message via SignalR to our page. This is a very simplistic example that shows that the item affected in the list we’ve subscribed to is what is causing the update on the page.

    public class RemoteEventReceiver : IRemoteEventService
    {
        private SharePointRHub client = new SharePointRHub();

        public SPRemoteEventResult ProcessEvent(SPRemoteEventProperties properties)
        {
            client.NotifyDataChanged(properties.ItemEventProperties.ListTitle, properties.EventType.ToString());
            return new SPRemoteEventResult();;
        }

        public void ProcessOneWayEvent(SPRemoteEventProperties properties)
        {
        }
    }

The final step is to code up the page. There are a number of aspects here which should be completed to get the branding right for the App – these are covered in greater detail in the how-to guide listed at the end of this post. For now i’ll highlight the main code needed to hook up SignalR on the page (note that references to scripts not yet mentioned in this post are also covered in that guide).

    <!--Script references. -->
    <script src="//ajax.aspnetcdn.com/ajax/4.0/1/MicrosoftAjax.js"></script>
    <script src="/Scripts/jquery-1.8.2.min.js"></script>
    <script src="/Scripts/json2.js"></script>
    <script src="/Scripts/chromeLoader.js"></script>
    <script src="/Scripts/jquery.signalR-1.0.0.js"></script>
    <script src="/signalr/hubs"></script>

    <!--Add script to update the page and send messages.-->
    <script type="text/javascript">
        $(function () {
            var connection = $.hubConnection();
            proxy = connection.createHubProxy('SharePointRHub')
            proxy.on('dataChanged', function (eventType) {
                $('#MainContent').append('<li>Data changed - ' + eventType + '</li>');
            });
            connection.start()
                .done(function () {
                    //Subscribe to Announcements list
                    proxy.invoke('Subscribe', "Announcements");
                    $('#MainContent').append('<span>Now connected, connection ID=' + connection.id + '</span>');
                })
                .fail(function () {
                    $('#MainContent').append('<span>Could not Connect!</span>');
                });
        });
    </script>

And that’s about it! The image below gives a sneak peak into how an event triggered via a SharePoint list can be reflected on a separate page in real time. It is far more impressive however seeing it in person, so I’d encourage you to follow the guide provided below and get ‘SharePointR’ up and running for yourself – the possibilities this technology opens up is limitless and it will be exciting to see the kinds of solutions designed which leverage SignalR within SharePoint in the near future.

PreviewDownload: Walkthrough-Deploying a SignalR autohosted App to Office 365

Using SPWebConfigModification to Update the Web.config in SharePoint 2013

Seven months ago I wrote an article on Jumping the Hurdles of using SPWebConfigModification to Update the Web.config – that article was based on my experiences in SharePoint 2010 and having researched the topic thoroughly at the time I was interested to see how things fared in SharePoint 2013. Thankfully I had the opportunity to architect a solution from the ground up in 2013 which gave me the opportunity to once again adhere to the vow I made in Application Settings in SharePoint to never manually modify the web.config file.

While this post has not been as thoroughly researched at the time of writing as I usually would like, a quick search on SPWebConfigModification in SharePoint 2013 brought up few results and when I was looking into it for the project mentioned above, little had been written about it. A recent question on the OZMOSS mailing list showed that there were still some questions around how SPWebConfigModification did (or didn’t) work so I thought it would be useful to document my findings here for anyone wanting to ‘do the right thing’ and keep their hands off the web.config file.

For a bit of background for this post I’d thoroughly recommend you read the article I link to above – the purpose of this post is to compare the behaviour of the functionality between versions of the platform and it will make far more sense if you understand my (and others) previous findings.

However for those of you who care little about the past and just want to know what the deal is in 2013 – here is the quick summary:

SPWebConfigModification in SP2010 came with a number of issues however none which were completely insurmountable with a little work. The majority were already documented by others (and I link to them in that post) however one phenomenon had little written about it and even less in terms of a solution.

This problem was that even though the removal code ran successfully and even removed the entry from the modification collection, the element in the web.config file still physically existed. I came up with a workaround where  in the removal code I first applied another modification reverting the entry back to its original state, then ran the code to remove all modifications.

So how did this fair in SP2013? There was good news and bad news. On the plus side, this issue had been fixed! Removing the entry from the modification collection successfully removed the entry from the web.config file. On the negative side it came with a nasty side effect – if the modification you were removing was a change to an attribute on a pre-existing element then that whole element was removed from the web.config, not just your change.

This was clearly unacceptable behaviour particularly if the entry being removed was essential to successfully loading the website.

Once again however I managed to jump the hurdles that always seem to exist with this tricky class. The workaround this time was to remove all the customisations by default at the beginning of both the FeatureActivated and FeatureDeactivating functions and then add the customisation required. In FeatureActivated this would be your customised entry. In FeatureDeactivating this would be the original entry (if it existed in the web.config before your modifications). Again this solution isn’t full-proof and is prone to falling over in future iterations of the platform, however it is a solid workaround as things stand now. I’ve provided a code snippet to achieve this below:

public class WebConfigModificationsFeatureEventReceiver : SPFeatureReceiver
{
	// Due to what appears to be a bug or just and unfortunate side-effect in SP2013, using SPConfigModification on an existing element
	// replaces that element with a new one. When removing the customisation, that element is removed, hence meaning that the original
	// entry no longer exists. To counter this we will re-add the original values in the deactivating feature.

	public override void FeatureActivated(SPFeatureReceiverProperties properties)
	{
		SPWebApplication webApp = properties.Feature.Parent as SPWebApplication;
		RemoveAllCustomisations(webApp);

		#region Enable session state

		httpRuntimeModification = new SPWebConfigModification();
		httpRuntimeModification.Path = "configuration/system.web/pages";
		httpRuntimeModification.Name = "enableSessionState";
		httpRuntimeModification.Sequence = 0;
		httpRuntimeModification.Owner = "WebConfigModifications";
		httpRuntimeModification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureAttribute;
		httpRuntimeModification.Value = "true";
		webApp.WebConfigModifications.Add(httpRuntimeModification);

		httpRuntimeModification = new SPWebConfigModification();
		httpRuntimeModification.Path = "configuration/system.webServer/modules";
		httpRuntimeModification.Name = "add[@name='Session']";
		httpRuntimeModification.Sequence = 0;
		httpRuntimeModification.Owner = "WebConfigModifications";
		httpRuntimeModification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
		httpRuntimeModification.Value = "<add name='Session' type='System.Web.SessionState.SessionStateModule' preCondition='' />";
		webApp.WebConfigModifications.Add(httpRuntimeModification);

		#endregion

		/*Call Update and ApplyWebConfigModifications to save changes*/
		webApp.Update();
		webApp.Farm.Services.GetValue<SPWebService>().ApplyWebConfigModifications();
	}

	public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
	{
		SPWebApplication webApp = properties.Feature.Parent as SPWebApplication;
		RemoveAllCustomisations(webApp);

		#region Revert session state

		httpRuntimeModification = new SPWebConfigModification();
		httpRuntimeModification.Path = "configuration/system.web/pages";
		httpRuntimeModification.Name = "enableSessionState";
		httpRuntimeModification.Sequence = 0;
		httpRuntimeModification.Owner = "WebConfigModifications";
		httpRuntimeModification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureAttribute;
		httpRuntimeModification.Value = "false";
		webApp.WebConfigModifications.Add(httpRuntimeModification);

		#endregion

		/*Call Update and ApplyWebConfigModifications to save changes*/
		webApp.Update();
		webApp.Farm.Services.GetValue<SPWebService>().ApplyWebConfigModifications();
	}

	private void RemoveAllCustomisations(SPWebApplication webApp)
	{
		if (webApp != null)
		{
			Collection<SPWebConfigModification> collection = webApp.WebConfigModifications;
			int iStartCount = collection.Count;

			// Remove any modifications that were originally created by the owner.
			for (int c = iStartCount - 1; c >= 0; c--)
			{
				SPWebConfigModification configMod = collection[c];

				if (configMod.Owner == "WebConfigModifications")
				{
					collection.Remove(configMod);
				}
			}

			// Apply changes only if any items were removed.
			if (iStartCount > collection.Count)
			{
				webApp.Update();
				webApp.Farm.Services.GetValue<SPWebService>().ApplyWebConfigModifications();
			}
		}
	}
}

So once again we’re left with an option to do things right which doesn’t quite act the way you’d expect, but as you can see, can still be used to achieve the end result desired. For all its ills in both versions of the products i’d still strongly recommend taking this approach to making changes to the web.config file over making manual modifications any day. I hope this post has made doing so a little easier moving forward.

UPDATES

We shouldn’t be surprised, but over time new hurdles have presented themselves which need documenting (and I apologise, the first one I mention I should have covered a long time ago when I encountered it!).

The approach documented above worked fine in my single-server development environment. As soon as we tried activating the feature in a multi-server farm test environment all sorts of issues occurred. This has been documented however and you can read about it in Jeremy Jameson’s post Waiting for SharePoint Web.config Modifications to Finish and Simon Doy’s post PowerShell to Detect Web Configuration Modification jobs.

The code changes required include a few functions to be added and a slight modification to the ApplyWebConfigModifications call as indicated in the below code.

        private static bool IsJobDefined(SPFarm farm)
        {
            SPServiceCollection services = farm.Services;

            foreach (SPService service in services)
            {
                foreach (SPJobDefinition job in service.JobDefinitions)
                {
                    if (string.Compare(job.Name, jobTitle, StringComparison.OrdinalIgnoreCase) == 0)
                        return true;
                }
            }

            return false;
        }

        private bool IsJobRunning(SPFarm farm)
        {
            SPServiceCollection services = farm.Services;

            foreach (SPService service in services)
            {
                foreach (SPRunningJob job in service.RunningJobs)
                {
                    if (string.Compare(job.JobDefinition.Name, jobTitle, StringComparison.OrdinalIgnoreCase) == 0)
                        return true;
                }
            }

            return false;
        }

        private void WaitForOneTimeJobToFinish(SPFarm farm)
        {
            float waitTime = 0;

            do
            {
                if (!IsJobDefined(farm) && !IsJobRunning(farm))
                    break;

                const int sleepTime = 500; // milliseconds

                Thread.Sleep(sleepTime);
                waitTime += (sleepTime / 1000.0F); // seconds

            } while (waitTime < 20);
        }

        /*Call Update and ApplyWebConfigModifications to save changes*/
        webApp.Update();
        WaitForOneTimeJobToFinish(webApp.Farm);
        webApp.Farm.Services.GetValue<SPWebService>().ApplyWebConfigModifications();

The second issue is one we’ve encountered more recently. The issue was in regards to the inability to successfully extend a web application which already had the web.config modifications applied. There’s 2 ways to approach this one – if you know before the fact, then make sure you extend your web application before applying any of the web.config modifications. Otherwise, hope that you’ve implemented decent deactivation code and be sure to deactivate the features before extending, and then reapply after the fact.