Hiding the out-of-the-box Document Library in SharePoint 2013

Recently I was tasked with investigating the possibility of removing the out of the box Document Library from the ‘add an app’ page so that users wouldn’t accidentally select that option instead of a specific list template containing the organisation’s meta data they want included in the library. It’s been refreshing exploring Intranet-style scenarios after having focussed on public facing web sites for so long and as such this is the type of problem I was eager to get my head around and solve.

In days gone by one would generally turn to creating a custom site definition to achieve such a goal – or god forbid modify one of the out of the box site definitions. We’ve all known for some time that the latter is a terrible idea, but the former isn’t particularly better when considering future migrations. It was no surprise however that it was still one of the first options I came across when researching solutions, such as on Salaudeen Rajack’s post Hide Site templates & List Templates in SharePoint. Back to the drawing board.

Another more promising option I pursued was using PowerShell to disable the feature which specifically provisions the Document Library as per Renan Rocha’s Hide “list template” disabling its feature or this StackOverflow question: Hide Listtemplate from Create Dialog in PowerShell. This worked, however it also removed a custom document library template which I had created for testing purposes to mirror the desired result I’d want to end up with. It also only worked at the web layer, which means that we’d first need to run it recursively across the whole site, then would also need to run it each time a new sub site was created – not exactly practical even if it did suit our purpose.

There were also a few hacky options I discovered along the way which I was obviously keen to avoid, such as that listed on the MSDN question: Is it possible to delete the default document library template?. I figured there had to be a better way.

I decided to change tack a little bit and investigate adding our custom templates as ‘Noteworthy’ apps to at least give them some prominence on the screen. Unfortunately it turned out that while you can set Apps as noteworthy by making them featured apps, you can’t set list templates as featured or noteworthy. There are some good posts on this topic including Stephen Brown’s SharePoint 2013 and the Noteworthy section and Shereen Qumsieh’s Making Changes to the Noteworthy Section of a SharePoint Site. It was these posts that gave me all the information I needed to solve my problem.

I ended up creating a Delegate control targeting AdditionalPageHead (yes this was a full trust solution – I’m sure someone can come up with an alternative way of getting this to work in Office 365!) to run the following JavaScript:

<script type="text/javascript">
    function AlterStorefront() {
        if (SP.Storefront != undefined) {
            var listedApps = SP.Storefront.StorefrontApp.get_currentView();
            if (listedApps == undefined || listedApps.$2i_3 == null || listedApps.$L_3 == null) { setTimeout(AlterStorefront, 750); return; }
			
            listedApps.$2i_3.length = 0;

            for (i = 0; i < listedApps.$L_3.length; i++) {
                if (listedApps.$L_3[i].$2Q_0.Title == "Document Library") {
                    listedApps.$L_3.splice(i, 1);
                }
                if (listedApps.$L_3[i].$2Q_0.Title.indexOf("Insite") > -1 || listedApps.$L_3[i].$2Q_0.Title == "Icon Links Grid") {
                    listedApps.$2i_3.push(listedApps.$L_3[i]);
                }
            }

            SP.Storefront.StorefrontApp.get_currentView().updateUI();
        }
    }

    SP.SOD.executeFunc("sp.js", "SP.Storefront", AlterStorefront);
</script>

While AdditionalPageHead exists on all pages, checking that SP.Storefront exists means that it won’t run on every page.

So the first point of note is that we wait until SP.js has loaded before calling our function given it relies on it. Once we’ve confirmed the Storefront does exist on the current page, we attempt to get the current view of the Storefront. Because this loads asynchronously itself, it is highly probable that it hasn’t loaded before this code executes. As such I performed a few checks to ensure the properties of note had completely loaded and re-called the function after sleeping for a period of time (0.75 seconds I found to be roughly the most optimal on my development server).

The next step was clearing out the existing Noteworthy apps by setting the array’s length to 0. This perhaps isn’t ideal given your ‘Featured’ apps would also be wiped rather than just the default ones that populate that area, however if this is a concern you can more specifically target their removal similar to how I did it in the next step.

That next step is looping through the array which stores all the app items. We look for the one corresponding with ‘Document Library’ and remove it using splice. We then look for a particular string (or multiples) to identify the templates we want to promote to Noteworthy, and add them to that array. The final step is to update the UI to reflect those changes.

One important thing to note is that the indexOf function is not compatible with IE7 and IE8 – if this is a problem then you may want to ‘find’ the apps for promotion another way.

So there you have it, a semi (not-so?) elegant way of achieving the desired result – removing the Document Library list template from the ‘add an app’ page and replacing the noteworthy items with your custom templates. The solution isn’t perfect – unfortunately there is a visual delay between loading the initial page (which you see for a moment) and when the updateUI kicks in – but overall it does the job.

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