Using SharePoint Documents in LightSwitch HTML Client (Prem Ramanathan)

Using SharePoint Documents in LightSwitch HTML Client (Prem Ramanathan)

Rate This
  • Comments 20

Visual Studio LightSwitch is a great development environment for writing line of business applications that provide a consistent mobile and desktop friendly user interface that talk to various data sources including ODATA, SharePoint and SQL. SharePoint has evolved as a great platform for sharing documents and files, but LightSwitch doesn’t provide any built-in way to interact with SharePoint documents. In this article, I will give an overview of how to create a LightSwitch application that can talk to SharePoint to store the documents and make use of collaboration features of SharePoint. I will be building a simple Cookbook application that stores the recipes. Each recipe can contain files (images, documents etc.) associated with it which will be stored in SharePoint document library.

This article assumes a basic level of familiarity with LightSwitch and JavaScript. If you are new to LightSwitch, I suggest reading some of the kick starter blogs. This article assumes that you are using Visual Studio 2013 preview or later version.


Creating the Cookbook App

  • First step to creating Cookbook app is to create the data and the screens needed for the Application. Follow the steps here:
  • Create a new LightSwitch HTML application, name it Cookbook.
  • Create a new table called Recipe and add the following fields:
    • Name of type String
    • Ingredients of type String
    • Instructions of Type String


Now, let’s create the default screens for the table.  The following steps will guide you to create Browse/AddEdit/View Recipe screens.

  1. Create a browse screen for the Recipe table by right clicking on the Screens node in solution explorer, and click Add Screen.
  2. Select Recipe as the data and click OK.
  3. This will create a Browse Screen for the Recipe.
  4. In the content tree of the BrowseRecipe screen, create a new command bar button to “Add Edit Recipe” screen.
  5. This will popup a Add new screen dialog for “AddEditRecipes” screen. Click OK to create the screen.
  6. This will create a new screen named AddEditRecipe.
  7. Open the BrowseRecipes screen again.
  8. Select the List control, and in the properties tab, click Tap action.
  9. Select ViewRecipes, and click OK. This will open a create new screen dialog. Click OK.
  10. Here is a sample screenshot of how the add/edit screen wiring is done.



Now that we have completed the basic screens, let’s go ahead and launch the application by pressing F5. You will be greeted with Browse Recipe Screen. This is the basic Cookbook app that can store recipes. Close the app, and come back to designer.

Adding Documents to Recipe

Now that we have created the recipe app, let’s add attachments (file attachments) to recipe. By adding attachments, users will be able to see the related images and other documents for each recipe. In this article, we will be showing the attachments in the View Recipe screen  as a separate Tab. The documents will be stored in a SharePoint document library.

  1. First let’s enable SharePoint. The following steps will guide you to enable SharePoint.
  2. Open the Project Properties window by double clicking on the Properties node in the root project in solution Explorer.
  3. Click on the SharePoint tab, and you will see Enable SharePoint Button.
  4. Click the Enable SharePoint button, provide the SharePoint site URL and then click validate.
  5. When prompted for credentials, enter SharePoint credentials for the site.
  6. Once validated, click OK. If you have successfully enabled SharePoint, you will see a new project added your solution which is named Cookbook.SharePoint.

If you are not familiar with building LightSwitch apps for SharePoint, refer to this blog for more details.

Creating Document Library

We will be storing the files associated with the recipe in a document library. SharePoint supports two types of Storage, one is App Web which is specific to the App, and the Host Web which is shared by all Apps. In this demo, we will be using the App Web. SharePoint project provides an easy way to create Document Library. To create, right click on the SharePoint project in solution explorer, select Add New Item, select List, name it RecipeDocuments.


Click Add, and In the next screen, choose Document Library and click Finish.



This will add a new List of type Document Library to the SharePoint project. This list will be created when the App is deployed.

Adding documentViewer.css

Add a new style sheet named documentViewer.css under Content directory (by right clicking on Contents directory in HTMLClient project and click Add New Item, select StyleSheet) . Copy paste the following into the file which provides the styles for the document viewer control.

.sp-docview .sp-docview-manage,
.sp-docview .sp-docview-init,
.sp-docview .sp-docview-error,
.sp-docview .sp-docview-list,
.sp-docview .sp-docview-refresh {
    display: none;

.sp-docview.sp-docview-state-loading .sp-docview-spinner {
    display: block;
    background: url(Images/msls-loader-light.gif) no-repeat transparent;
    background-size: 18px 18px;
    background-position: center;
    height: 18px;
    width: 18px;
    margin: 2px;

.sp-docview.sp-docview-state-error .sp-docview-error {
    display: block;
    color: red;

.sp-docview.sp-docview-state-no-folder .sp-docview-init {
    display: inline-block;

.sp-docview.sp-docview-state-valid .sp-docview-manage {
    display: inline-block;
    text-align: right;

.sp-docview.sp-docview-state-valid .sp-docview-list {
    display: block;

.sp-docview.sp-docview-state-valid .sp-docview-refresh {
    display: inline-block;
    margin-left: 30px;

.sp-docview-list .ui-li.ui-btn {
    margin-bottom: 10px;
    border-bottom-width: 1px;

.sp-docview-list li .sp-docview-no-items {
    font-size: 14px;
    opacity: 0.7;

.sp-docview-list li .sp-docview-li-icon {
    float: left;
    padding-right: 8px;
    padding-top: 6px;

.sp-docview-list li .sp-docview-second-row {
    font-weight: normal;
    font-size: 12px;
    opacity: 0.7;
    padding: 4px 0px;

.sp-docview-list li .sp-docview-name {
    overflow: hidden;
    text-overflow: ellipsis;

.sp-docview-list li .sp-docview-author {
    padding-left: 4px;

Adding JavaScript Code

Add a new script file named documentViewer.js under Scripts directory (by right clicking on Scripts directory in HtmlClient project and click Add New Item, select JavaScript) and paste the following contents which contains the logic for Document viewer control:

/// <reference path="jquery-1.9.1.js" />
/// <reference path="msls-2.0.0.js" />

(function () {
    function getUrlParameter(parameterName) {
        var pattern = "[\\?&]" + parameterName + "=([^&#]*)",
            regularExpression = new RegExp(pattern),
            results = regularExpression.exec(window.location.href);
        return results ? results[1] : null;

    var initPromise,
        // Icon mapping. Static mapping provides some of most commonly used icons.
        gifIconExtensions = {
            doc: true, ppt: true, xls: true, eml: true, dot: true, txt: true,
            htm: true, jpg: true, png: true, gif: true, zip: true, xps: true
        pngIconExtensions = {
            docx: true, pptx: true, xlsx: true, one: true,
            dotx: true, pdf: true

    var sp_helper = {
        hostUrl: decodeURIComponent(getUrlParameter("SPHostUrl")),
        appWebUrl: decodeURIComponent(getUrlParameter("SPAppWebUrl")),
        load: function () {
            // Loads zero or more SharePoint objects, like a SP.User or SP.File instance.
            // Returns jQuery promise object that is resolved when the objects
            // have been loaded.
            var me = this, context = me.context, deferred = $.Deferred(),
                args =, 0);
            args.forEach(function (o) {
                function success() {
                function failure(unused, e) {
            return deferred.promise();
        getIconUrl: function (extension, large) {
            // Gets the URL for the icon associated with the file extension.
            var prefix = this.serverUrl + "/_layouts/15/images/" +
                        (large ? "lg_ic" : "ic");
            if (extension === "html") {
                extension = "htm";
            if (gifIconExtensions[extension]) {
                return prefix + extension + ".gif";
            } else if (pngIconExtensions[extension]) {
                return prefix + extension + ".png";
            } else {
                return prefix + "gen.gif";
        ready: function () {
            function getScript(url) {
                return $.ajax({ url: url, cache: true, dataType: "script" });

            if (!initPromise) {
                var scriptBase = sp_helper.appWebUrl + "/_layouts/15/";

                // Check if the SharePoint libraries are already loaded.
                //If someone has already loaded them, skip loading.
                if (!window.SP || !SP.ProxyWebRequestExecutorFactory ||
                    !SP.ClientObject || !SP.ClientContext) {

                    initPromise = getScript(scriptBase + "SP.RequestExecutor.js")
                    .then(function () {
                        return getScript(scriptBase + "SP.Runtime.js");
                    }).then(function () {
                        return getScript(scriptBase + "SP.js");
                } else {
                    initPromise = $.Deferred().resolve();

                initPromise = initPromise.then(function () {
                    sp_helper.serverUrl = $.mobile.path.parseUrl(sp_helper.hostUrl).domain;
                    sp_helper.appWebBaseUrl = $.mobile.path.parseUrl(sp_helper.appWebUrl)
                    sp_helper.context = new SP.ClientContext(sp_helper.appWebUrl);
                    sp_helper.factory = new SP.ProxyWebRequestExecutorFactory(
                    sp_helper.hostWeb = new SP.AppContextSite(
                        sp_helper.context, sp_helper.hostUrl).get_web();
                    sp_helper.appWeb = new SP.AppContextSite(
                        sp_helper.context, sp_helper.appWebUrl).get_web();
            return initPromise;

    // Represents the state of the control. At any given time,
    // the control will be one of the following states.
    var ControlState = {
        // Document library exists, but the folder hasn't been created yet.
        noFolder: "sp-docview-state-no-folder",
        // A server request is in progress.
        loading: "sp-docview-state-loading",
        // The _state is completely normal. Document library, folder both exists,
        // and the document list is being displayed.
        valid: "sp-docview-state-valid",
        // Error connecting to sharepoint or the document library.
        error: "sp-docview-state-error",
        // Either the document library is not configured correctly or 
        // entity doesn't have single primary key.
        invalid: "sp-docview-invalid"

    function DocumentViewer(element, contentItem, documentLibraryName, isAppWeb) {
        var _initButtonElement,

        // Gets the folder name from the entity name. Looks up for the key property
        // and returns the value.
        function _getFolderName(entity) {
            if (!entity || !entity.details ||
                entity.details.entityState === msls.EntityState.added) {

                return null;

            var model = entity.details.getModel(),
                properties =,
                keyProperties = [],

            properties.forEach(function (p) {
                if (p.__isKeyProperty) {
            if (keyProperties.length !== 1) {
                // More than one primary key or no primary key is not supported.
                return null;
            value = entity && entity[keyProperties[0].name];
            return typeof value !== "undefined" &&
                value !== null ? value.toString() : null;

        function _setState(s) {
            _state = s;

        function _handleError(e) {

        function _createFolder() {
            // Create a folder under the document library if it doesn't already exists.
            var rootFolder = _spDocLib.get_rootFolder(),
                folder = rootFolder.get_folders().add(_folderName);

                .then(function () {
                    _spFolder = folder;
                }).fail(function (e) {

        function _refresh() {
            // Initialize sharepoint and extract the parameters from the contentItem value.
            _docInfoList = [];

            sp_helper.ready().then(function () {
                if (documentLibraryName) {
                    _spSite = isAppWeb ? sp_helper.appWeb : sp_helper.hostWeb;
                    _baseUrl = isAppWeb ? sp_helper.appWebBaseUrl : sp_helper.serverUrl;
                    _spDocLib = _spSite.get_lists().getByTitle(documentLibraryName);
                    _folderName = _getFolderName(contentItem.value);
                } else {
                    _spDocLib = _folderName = null;

        function _refreshInternal() {
            // Refresh the data by talking to sharepoint.
            var rootFolder,
                folderNameCached = _folderName;

            if (!_spDocLib || !_folderName) {

            rootFolder = _spDocLib.get_rootFolder();
            _docInfoList = [];

            sp_helper.load(_spDocLib, rootFolder).then(function () {
            }).then(function () {
                if (folderNameCached !== _folderName) {
                    // If the control was reset in between network request,
                    // ignore the current result.
                folder = rootFolder.get_folders().getByUrl(_folderName);
                files = folder.get_files();
                // Load the file collection explicitly to include specific fields.
  "Include(Name, Title, Author, TimeLastModified, ServerRelativeUrl, ListItemAllFields)");
                return sp_helper.load(folder);
            }).then(function () {
                var file,
                    enumerator = files.getEnumerator();

                if (folderNameCached !== _folderName) {

                // Go through the file list, and load the details.
                while (enumerator.moveNext()) {
                    file = enumerator.get_current();
                    fileName = file.get_name();
                    index = fileName.lastIndexOf(".");
                    extension = index ? fileName.substr(index + 1) : "";
                    docInfo = {
                        name: fileName,
                        title: file.get_title(),
                        author: file.get_author().get_title(),
                        timeLastModified: file.get_timeLastModified(),
                        iconUrl: extension ? sp_helper.getIconUrl(extension, true) : "",
                        viewUrl: _baseUrl + file.get_serverRelativeUrl() + "?Web=1"
                _spFolder = folder;
            }).fail(function (e) {
                if (folderNameCached !== _folderName) {
                if (!_spFolder) {
                    // Folder doesn't exist. Set the control state to noFolder.
                } else {

        function _refreshUI() {
            // Refreshes the UI based on control state.
            if (_state === ControlState.valid) {
                _docInfoList.forEach(function (docInfo) {
                    // Go through each document, and add it to the list.
                        "<li data-icon='false'><a href='" + docInfo.viewUrl +
                                "' target='_blank'>" +
                            "<div class='sp-docview-li-icon'>" +
                                "<img src='" + docInfo.iconUrl + "' />" +
                            "</div>" +
                            "<div class='sp-docview-li-text'>" +
                                "<div class='sp-docview-name'>" + + "</div>" +
                                "<div class='sp-docview-second-row'>" +
                                    "<span class='sp-docview-last-modified'>" +
                                        docInfo.timeLastModified.toLocaleString() +
                                    "</span>," +
                                    "<span class='sp-docview-author'>" + +
                                    "</span>" +
                                "</div>" +
                            "</div>" +
                    _baseUrl + _spFolder.get_serverRelativeUrl());
                if (!_docInfoList.length) {
                        "<span class='sp-docview-no-items'>No Items</span>");

            if (_uiState !== _state) {
                if (_uiState) {
                _uiState = _state;

        _state = ControlState.invalid;
        // Create DOM elements.
        $(element).html("<div class='sp-docview-spinner'></div>" +
            "<a class='sp-docview-manage' href='' target='_blank'>Manage Documents</a>" +
            "<div class='sp-docview-init' data-role='button'>Initialize Library</div>" +
            "<a class='sp-docview-refresh' href=''>Refresh</a>" +
            "<ul class='sp-docview-list' data-role='listview' data-inset='true' ></ul>" +
            "<div class='sp-docview-error'></div>");
        _initButtonElement = $(".sp-docview-init", element);
        _manageButtonElement = $(".sp-docview-manage", element);
        _errorElement = $(".sp-docview-error", element);
        _listContainerElement = $(".sp-docview-list", element);
        _refreshButtonElement = $(".sp-docview-refresh", element);

        _initButtonElement.bind("click", _createFolder);
        _refreshButtonElement.bind("click", _refresh);
        contentItem.addChangeListener("value", _refresh);
    window.createDocumentViewer = DocumentViewer;

Update default.htm

Open default.htm file and make the following changes:


Add the following line after msls-2.0.0.min.css include.

    <link rel="stylesheet" type="text/css" href="Content/documentViewer.css" />

 Add the following line before globalize.min.js (if not already included) (SharePoint requires Microsoft Ajax)

<script type="text/javascript" src="//"></script>

Add the following line after msls-2.0.0.min.js file.

    <script type="text/javascript" src="Scripts/documentViewer.js"></script>


Add Custom Control

Now it is time to add the control to the ViewRecipe screen. To do this, open the ViewRecipe screen, and do the following steps:

  • Add a new Tab under the Tabs node.
  • Rename the newly added Tab to Images.
  • Select the new tab node, click “Add Custom Control” and enter “Recipe” as the binding path, click OK.
    • Screenshot below



  • Select the newly added custom control, click “Edit Render Code” link. This will take you to the ViewRecipe.lsml.js file.
  • Add the following line inside the body of Recipe_render function.

createDocumentViewer(element, contentItem, "RecipeDocuments", true);


Seeing it all Together

Now we are ready to launch the application. Press F5, and you will be greeted with the following screen.



Add a recipe, and click the recipe in the list, and click Images tab.



Now, you can see the document control is created. You can click initialize library button, which will create a new folder in the document library. The control now displays the link to the document library.



Click the link and it will launch the SharePoint document library and open the folder appropriate for the entity. The folder is named based on entity key property (which is ID in this case). You can change the logic to use different name here if you want to by updating getFolderName() function in documentViewer.js. You can add documents here by clicking the new item button.


Once you finish adding the documents, go back to the screen, and click refresh button, which will now display the documents (files) you have added.



Bingo! There is your document viewer!. You can also store documents and use SharePoint collaboration tools to edit and collaborate documents. This control provides basic functionality to interact and operate with SharePoint documents, but can be extended to do much more.


I hope you find this useful, and let me know your comments in the comments section.

- Prem Ramanathan, Senior Developer, LightSwitch Team

Leave a Comment
  • Please add 7 and 7 and type the answer here:
  • Post
  • All that code for something so simple? MS really need to improve their SharePoint development platform

  • Until now I resisted the urge to post a similar sentiment as PassionateProgrammer.  We've been waiting for a nice sample about uploading files to sharepoint from HTML client using javascript and I hoped this was the one.  Unfortunately, this blog is one big mess of a workaround to a limitation.  I don't know if this is a limitation of LS design time features, sharepoint 2013 app model or both, but it's a definitely show stopper for LS & SharePoint, in my view.

    The results of this blog can be done in seconds without any code by using a sharepoint data source in lightswitch but no-can-do for a list in the same sharepoint AppWeb (one that was deployed with the LS app like in this article) Now that's silly. We're told it's because the list doesn't yet exist at design time... I suppose that's fair but its surely a limitation. A limitation that could be fixed by allowing the change of connection strings at runtime.  I understand if this doesn't make the features list, but can someone do a blog about how to change conn strings at runtime in code (like configuration manager class or something(?))

    As a result of this limitation, we have to write a ton of js just to show data from such a sp list.  

    When do you think we'll see a blog about using sharepoint rest service to upload files from HTML Client?  I mean pure javascript rather than the webapi controller or other methods seen in earlier samples.

    LS team needs more input from perspective of sharepoint developers and less from MVC.

  • Hi @PassionateProgrammer & @JoshBooker,

    Thanks for taking the time to voice your feedback on the post--it’s a healthy reminder to each of us writing blog content to focus on simplicity and known pain points first and foremost.  Many of the more advanced entries recently published are rooted in a desire to socialize workarounds for known limitations/issues reported commonly in the forums.  We all recognize, however, that those posts and workarounds are no substitute for simplicity in the tools and platform.

    The challenges of interacting with the document library--specifically uploading documents--are top of mind for us right now: we recognize some of the seemingly simplest list/document library operations are very difficult to enable, and the capability trade-offs between attached and app web lists seem somewhat arbitrary.  Minimizing the developer experience and capability differences between host web, attached host web, and app web lists is a big step toward a deeply integrated SharePoint experience and we’re actively working in this space.  In the meantime, we can put together a utility for upload specifically--you’re one of many to report this need.

    We have the dynamic connection string request on our backlog--thank you for raising awareness of its importance.

    Again, thanks for passing the feedback along.  We are all listening and benefiting from it.

    Joe Binder

    Program Manager, Visual Studio LightSwitch

  • Joe,

    I really appreciate your detailed response.    A JavaScript upload utility sample would be really nice to have.  Scot Hillier has a nice blog on using REST Service and FileReader() object to do this:

    If at all possible, a blog about how to change connection strings at runtime would be huge!  I can list many, many scenarios where this could be useful right now for both the HTML and SL clients.

    Thanks for listening and keep up the great work!


    Lover of LightSwitch

  • Hi Josh,

    The challenge with Connection strings is whether you're looking to change them for everyone, which there is an ConfigurationManager API. Or, you're looking to change dynamically at runtime.

    We're always interested in the scenarios, so please feel free to post, or just contact me directly at Steve dot Lasker at Microsoft



  • Hi Joe, I hope you are still listening :-)

    I tried a simple scenario, in a default LS Html app, wanted to add an existing Office 365 list as a data source. (without first 'enabling' SharePoint) Since Add Datasource has SharePoint, I expected to be able to use a rest connection without having to deploy the app to a SharePoint site. However the discovery process comes up with a cryptic message.

    Is that by design? I don't like it ;-) Is there a simple way to achieve this?


    Ed Richard

  • @joebinder

    ..../_vti_bin/listdata.svc does not appear to be a valid OData service. is the error when trying to connect as oData service.

    Why is that?


  • Ed,

    You must 'enable' sharepoint and deploy to sharepoint online in order for LS to authenticate with O365.  I think this has to do with certificates and security context required by O365, but it probably has something to do with the auth implementation in LS as well.  For what it's worth, as long as sharepoint is enabled and the app is deployed to O365, you are able to connect to hostWeb lists.



  • Thanks @joshbooker, I understand. It is very misleading though, when you go through the wizard it gives the option to choose SharePoint and even supply a username and password. It would be good if this scenario is supported because we do want to have apps that are installed locally and need to interact with (at least read) SharePoint lists.


  • Ed, my prior comment was not completely accurate.  You can enable sharepoint and deploy your LS app to a local server.  See the options for provider-hosted in this article

    The trade off is deployment certs and registration with sharepoint is manual.  Also, all sp sites that have your app will use the same LS app so you will need to handle multi tenancy permissions in LS if applicable.

  • @Joshbooker and @EdRichard - you can't attach to O365 SharePoint site collections using specified credentials unfortunately under any configuration.  This is something we are aware of.  I am definitely looking into the issue to see what we can do.

    You can attach with your current user to an O365 sharepoint site list (see more details here -

  • Matt,

    Understood.  But that is not to say you cannot use client secret and connect to O365 lists from a provider-hosted LS app,  Correct?  

    In other words, once I enable sharepoint and validate O365 site, then I can use Add Datasource | SharePoint to access lists on the same O365 site.  Is that supported for provider-hosted apps?

  • @josh booker - you can definitely attach to Lists using your current user (not specified user). Of course after you have enabled SharePoint.  Provider Hosted will work then as well, certainly.  Keep in mind then that the list you are attached to has to be in the site collection where your app is deployed.

    HTH - Matt Sampson

  • Thanks everyone for chipping in on the conversation. I fully understand the capabilities -when SharePoint Enabled-

    What is missing clearly is when it is not, I also understand Authentication is the issue (I actually have been working on the issue of authentication to Office 365 from a XAML app running on Windows RT with Microsoft support engineers) . To me the whole point of Rest / oData is that you should be able to use it in very loosely coupled scenarios without any dependencies around where your app is running or it's context. At the moment I don't have a grip on how that's possible.

    About to install 2013 RC btw, looks like there's been some good progress on the Lightswitch html client front made!

    Thanks for clarifying Matt Sampson, your comments are very helpful.

    Is it mainly a cross-site security issue you guys are worried about? Certainly in the world of connected apps and services, this is always going to a challenge?



  • Thanks for those last links, I think I see the issue/solution clearer now;

    My scenario needs to be, SharePoint Enabled - Provider Hosted with etc.

    Will this work for Office 365?



Page 1 of 2 (20 items) 12