Building an MVC application for SharePoint

Building an MVC application for SharePoint

Learn how to write code to perform basic operations with the SharePoint 2013 .NET Framework client-side object model (CSOM), and build an ASP.NET MVC application that retrieves information from a SharePoint server.

 

The Right API

Before we enter into the details of implementation of the ASP.NET MVC application that connects to SharePoint and retrieves data using the CSOM API, it is worth making a step back and understand what CSOM is and what the APIs that SharePoint makes available to programmers are.

SharePoint 2013 provides several sets of APIs, including the server object model, the various client object models, and the REST/OData web services. There are a few factors to consider for which API set to use, depending on:

·         The type of application: Whether you are developing a SharePoint add-in, a Web Part on a SharePoint page, an ASP.NET application exposed in SharePoint by an iframe, a .NET application running on a client computer, a Windows PowerShell script, or a mobile app.

·         The programming language of your choice: Programming SharePoint is not exclusive of .NET developers. The JavaScript Client Side Object Model opens to any HTML-based applications to connect to a SharePoint backend, and the REST/OData web services allow any other platform to communicate with a SharePoint server. It is easily possible to create applications in SharePoint 2013 without needing to learn a lot about SharePoint programming.

·         The device on which the code runs: The possibilities include a server in the SharePoint farm, an external server such as a server in the cloud, a client computer, and a mobile device.

For more information about the SharePoint programming model, please refer to this article on MSDN: Choose the right API set in SharePoint 2013.

Venn diagram of API sets and SharePoint app types

SharePoint sets of APIs, from MSDN – Choose the right API set in SharePoint 2013.

 

MSDN provides plenty of information on which SharePoint API set is more suitable to use

 

The Client Side Object Model

The SharePoint Client Side Object Model (CSOM) provides a programming interface for retrieving, updating, and managing data in SharePoint 2013. SharePoint 2013 makes the CSOM available in several forms. Specifically to .NET, almost every class in the core site and list server object model has a corresponding class in the .NET Framework client object model. This means that most of the functionalities of SharePoint Server are accessible to external applications using CSOM.

To improve performance, lines of code written against the .NET Framework client object model are sent to the SharePoint server in batches, where they are converted to server-side code and executed. The results, and the new state of all variables, are then returned to the client. We as the developer determine whether a batch runs synchronously or asynchronously. In a synchronous batch, the .NET Framework application waits for the returned results from the server before continuing; in an asynchronous batch, client-side processing continues immediately and the client user interface remains responsive.

Additionally, it is possible to use LINQ query syntax in our client code to query any IEnumerable object, including SharePoint 2013 objects that implement the IEnumerable interface. However, when doing this, we are using LINQ to Objects and not the LINQ to SharePoint provider, which I present in my other article.

Programming model for apps for SharePoint

Client applications and APIs in SharePoint.

 

The SharePoint Client Object Model is available for .NET and JavaScript

 

The MVC Project

Let’s get started and create an ASP.NET MVC project in Visual Studio. As we are planning to use the SharePoint CSOM, we need to add a reference to the latest libraries. The assemblies for the .NET Framework client object model must be installed on the client. They are included in a redistributable package that you can obtain on the SharePoint Server 2013 Client Components SDK.

Another option is to add a NuGet reference directly into the project; we need two packages:

·         Microsoft.SharePoint.Client.Latest

·         Microsoft.SharePoint.Client.Runtime

 

Microsoft.SharePoint.Client package in NuGet.

 

The MVC application that we want to build connects to a SharePoint site and displays a discussion list by category. For the purpose, I am using a SharePoint site created with the Community template, so I have the following lists already available:

·         Categories

·         Discussion List

 

Site Contents for a Community site in SharePoint 2013.

 

A Category is identified by the following properties, which match to the equivalent model in the MVC application:

·         Id

·         Name

·         Description

 

public class Category

{

    public int Id { get; set; }

 

    public string Name { get; set; }

 

    public string Description { get; set; }

}

The Category model.

 

When creating a discussion, we are presented with the following form:

Adding a new item to the Discussion List in SharePoint.

 

So our model reflects this structure:

public class Discussion

{

    public string Subject { get; set; }

 

    public string Body { get; set; }

 

    [DisplayName("Question")]

    public bool IsQuestion { get; set; }

 

    public Category Category { get; set; }

}

The Discussion model.

 

Now we need a controller with two actions:

·         Index: Retrieves the list of categories ordered by name.

·         Discussion: Retrieves the list of discussions available for a specific category.

 

For each of these actions, there is a view that displays the information obtained from SharePoint. More about the views later in this article.

Our controller, which for simplicity of design I call DefaultController, is implemented as follows:

public class DefaultController : Controller

{

    // GET: Default

    public ActionResult Index()

    {

        using (var sp = new SharePointContext())

        {

            var categories = sp.Categories.OrderBy(c => c.Name);

            return View(categories);

        }

    }

 

    // GET: /discussion/{category}

    [Route("discussion/{category}")]

    public ActionResult Discussion(string category)

    {

        using (var sp = new SharePointContext())

        {

            var discussionList = sp.DiscussionList.Where(d => d.Category.Name == category);

            return View(discussionList);

        }

    }

}

The Default controller.

 

A few things worth noticing here:

1.     The default action Index uses a disposable SharePoint context for obtaining a list of available categories, sorts them by name and then passes this collection to the view.

 

2.     The Discussion action has a custom route that responds to the URL /discussion/{category}, where {category} is the name of the category.
When enabling attribute routing, which is available in MVC 5, it is necessary to activate these custom routes by calling the MapMvcAttributeRoutes method on the route collection in the RegisterRoutes method of the RouteConfig class.

public class RouteConfig

{

    public static void RegisterRoutes(RouteCollection routes)

    {

        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

 

        routes.MapMvcAttributeRoutes();

 

        routes.MapRoute(...);

 

3.     From the SharePoint context, the actions retrieves a list of discussions in the given category, and passes this list to the view.

 

The result that we want to obtain is similar to what in the screenshot below, a list of categories, and by clicking on their name, display all discussions for that category.

List of categories in the MVC applications, directly from SharePoint.

 

That makes the Index view really straightforward:

@model IEnumerable<SharePointMvc.Models.Category>

 

<h2>Categories</h2>

<ul>

    @foreach (var category in Model)

    {

        <li>

            @Html.ActionLink(category.Name, "Discussion", routeValues: new { category = category.Name })<br />

            <p>@category.Description</p>

        </li>

    }

</ul>

The Index view.

 

The model for this view is declared as an enumerable of Category, and for each category in this collection, the view creates an action link to the Discussion action in the Default controller, passing the category name as route value.

Similarly, the Discussion view is as follows:

@model IEnumerable<SharePointMvc.Models.Discussion>

 

<h2>Discussion</h2>

 

<table class="table">

    <tr>

        <th>

            @Html.DisplayNameFor(model => model.Subject)

        </th>

        <th>

            @Html.DisplayNameFor(model => model.Body)

        </th>

        <th>

            @Html.DisplayNameFor(model => model.IsQuestion)

        </th>

    </tr>

@foreach (var item in Model) {

    <tr>

        <td>

            @Html.DisplayFor(modelItem => item.Subject)

        </td>

        <td>

            @Html.Raw(item.Body)

        </td>

        <td>

            @Html.DisplayFor(modelItem => item.IsQuestion)

        </td>

    </tr>

}

</table>

The Discussion view.

 

Objects in an MVC application can be modelled after a SharePoint list item

 

The SharePoint Context

If you noticed the pattern used in the previous actions to obtain data from SharePoint, it is very similar to what Entity Framework exposes: A data context where collections of entities are defined, and the possibility to access these collections with LINQ.

In a similar way, our implementation of the SharePoint context follows the same pattern, by exposing collections of the models that we have defined in our MVC application. Because in this case we are only reading information from SharePoint, and not writing back (although technically possible), an enumerable would suffice.

public class SharePointContext : IDisposable

{

    public IEnumerable<Category> Categories => LoadCategories();

 

    public IEnumerable<Discussion> DiscussionList => LoadDiscussionList();

Collection of models in the SharePoint context.

 

First of all, the SharePointContext class implements the IDisposable interface, so we can release the inner client context properly (more in a moment). Then, as anticipated, Categories and DiscussionList are read-only properties that expose an enumerable collection of the Category and Discussion models, respectively. Real time retrieval of data from SharePoint is implemented in two private methods.

The inner context is an instance of the ClientContext class defined in the SharePoint CSOM library. The ClientContext class extends ClientRuntimeContext, which is disposable, hence the need for disposing SharePointContext as well. All the ClientContext needs is the URL of the SharePoint site to which the MVC application is connecting; conveniently, this URL is stored in the Web.config and accessible via a static variable inside the SharePointContext class.

private ClientContext _context = new ClientContext(__sharepointUrl);

 

private static string __sharepointUrl => ConfigurationManager.AppSettings["SharePointUrl"];

Declaration of the ClientContext in the SharePointContext class.

 

Disposing the client context follows the implementation of the Disposable pattern:

private bool disposedValue = false; // To detect redundant calls

 

protected virtual void Dispose(bool disposing)

{

    if (!disposedValue)

    {

        if (disposing)

        {

            _context.Dispose();

        }

 

        disposedValue = true;

    }

}

 

public void Dispose()

{

    Dispose(true);

}

Implementation of the Disposable pattern for disposing of the SharePoint client context.

 

After building all this infrastructure around the SharePoint context, it is time to get to the juice of it. J We have two tasks to accomplish:

1.     Retrieve a list of categories defined in SharePoint.

2.     For a given category, retrieve a list of discussions.

 

The first task is implemented in the LoadCategories method:

private IEnumerable<Category> LoadCategories()

{

    Web web = _context.Web;

    _context.Load(web.Lists);

    _context.ExecuteQuery();

 

    List categoryList = web.Lists.GetByTitle("Categories");

    if (categoryList == null)

    {

        yield break;

    }

 

    CamlQuery query = CamlQuery.CreateAllItemsQuery();

    ListItemCollection categories = categoryList.GetItems(query);

 

    _context.Load(categories);

    _context.ExecuteQuery();

 

    foreach (var category in categories)

    {

        yield return new Category

        {

            Id = category.Id,

            Name = category["Title"].ToString(),

            Description = category["CategoryDescription"].ToString()

        };

    }

}

The LoadCategories method in the SharePointContext class.

 

The source code of LoadCategories is relatively simple to follow, even without a solid understating of how SharePoint works.

1.     First of all, we obtain a reference to the current site (Web) in the SharePoint context.

2.     The SharePoint CSOM data pattern always expects to load the desired object before accessing it; this is done by specifying the object to load in the Load method, and then actually executing a query to retrieve it, via the ExecuteQuery method on the SharePoint context instance.

3.     We need to retrieve the “Categories” list, so we first load all available lists in the specified site, and then we get the specific list by title.

4.     Once we have access to the Categories list, we need to read its list items; this is done by executing a CAML (Collaborative Application Markup Language) query. As we want to retrieve all existing categories, we can simply create a query that retrieves all items from the list.

5.     The list items are not available yet, we need to actually execute the query, and this is done, again, via the combination of Load and ExecuteQuery.

6.     Once obtained the list of categories, we can loop through them and read the necessary fields (Id, Title and Description) to create an instance of our MVC model Category for each list item.

 

In a very similar way, the implementation of LoadDiscussionList follows the same steps seen before for the categories, with the obvious difference that we now want to retrieve items from the “Discussions List” list:

private IEnumerable<Discussion> LoadDiscussionList()

{

    Web web = _context.Web;

    _context.Load(web.Lists);

    _context.ExecuteQuery();

 

    List discussionList = web.Lists.GetByTitle("Discussions List");

    if (discussionList == null)

    {

        yield break;

    }

 

    CamlQuery query = CamlQuery.CreateAllItemsQuery();

    ListItemCollection discussions = discussionList.GetItems(query);

 

    _context.Load(discussions);

    _context.ExecuteQuery();

 

    foreach (var discussion in discussions)

    {

        var category = discussion["CategoriesLookup"] as FieldLookupValue;

 

        yield return new Discussion

        {

            Subject = discussion["Title"].ToString(),

            Body = discussion["Body"].ToString(),

            IsQuestion = (bool)discussion["IsQuestion"],

            Category = new Category { Id = category.LookupId, Name = category.LookupValue }

        };

    }

}

The LoadDiscussionList method in the SharePointContext class.

 

There is a lot of repeated code here, so a good refactoring is much expected, for example for loading the current site and a specific list. The notable difference, though, is in the loop through the list items. Besides the obvious difference in the field names, it is worth noticing that the reference to a Category in a SharePoint list is made via a FieldLookupValue field. This object exposes two properties, the LookupId and the LookupValue, which can be used to create an instance of our Category model in the MVC application.

 

CSOM provides real-time CRUD operations on SharePoint items

 


  Comments

 

Ahmed Hashish
On 21 Oct 2019 at 04:08
Thank you very much for sharing such great article, I have a question here: What about authentication, what is the best approach to control login to the MVC app so that we have SSO between the MVC app and SharePoint?
 Source Code

Project Name: SharePointMvc


 Related Content
A flexible Default Value for your DateTime properties
When creating an MVC application with Entity Framework, it is possible to set default values for most properties in a model using the DefaultValue attribute. However, no much flexibility is offered for a DateTime property. This article presents a custom validation attribute for DateTime types that accepts different formats for defining the default value of the property.
Adding a Secured Geo-located Audit Trail
How I built a social sharing component for my own web site and added a secured geo-located audit trail. Step by step, let’s analyse technologies and source code for developing this component.
Adding Social Sharing to a Web Site
How I built a social sharing component for my own web site and added a secured geo-located audit trail. Step by step, let’s analyse technologies and source code for developing this component.
Best practices for mobile form design in SharePoint
Build effective SharePoint forms with Nintex that are accessible anywhere, at any time, and on any device. You built the workflows, you built the forms, now make them mobile.
Bring your “A” game to ESPC16
With just over 3 weeks to go to Europe's largest gathering of SharePoint & Office 365 professionals, take a look at these tips that will help you get the most out of ESPC16…
CIO vs CTO
What are the synergies and differences of the roles of a Chief Information Officer and a Chief Technology Officer? An open conversation about two roles with one mission…
Coded UI test automation of MVC applications with Visual Studio
Whether you are a software developer, tester, administrator or analyst, this article can help you master different types of UI testing of an MVC application, by using Visual Studio for creating coded UI test suites that can be automated for continuous execution.
Converting GIS spatial coordinates
Different formats and standards exist for describing geographical coordinates in GIS systems and applications. This article explains how to convert between the most used formats, presenting a working library written in C#.
Creating mobile accessible forms in SharePoint
With the release of the Nintex Mobile apps, SharePoint users can now optimise their experience across popular mobile devices and platforms.
Define your Performance Testing strategy with Visual Studio
Performance Testing is an essential part of software testing, with the specific goal of determining how a system performs in terms of responsiveness and stability under a particular workload. In this series of posts we’ll define and execute a good strategy for testing performance of an application using Visual Studio.
Disserting about colliding GUIDs and the Big Bang Theory
Can you generate two identical GUIDs? Would the world end if two GUIDs collide? How long does it take to generate a duplicate GUID and would we be still here when the result is found?
GIS Location Services in Dynamics CRM
A design paper about implementing GIS-based services for a local Council in Dynamics CRM, structuring address data, and delivering location services in the form of WebAPI endpoints via an Enterprise Service Bus.
Group or Team?
All teams are groups but not all groups are teams. What defines a group and what a team? When do we need one over the other one?
How to Give Constructive Feedback
Learning to give and receive constructive feedback is an essential part of learning, growing, improving and achieving our goals.
Mirroring an iPad on your laptop
Have you ever wanted to see your iPhone or iPad on a larger screen? Play games, watch movies, demo apps or present to your computer from your iPhone or iPad. Reflector mirrors iOS devices on big screens wirelessly using iOS built-in AirPlay mirroring.
Mobilize your SharePoint workflows
Build workflow applications in SharePoint that can be accessed on mobile devices using the Nintex solution for business process mobilization.
Natural String Sorting
Have you ever desired to have in your code a way to order a sequence of strings in the same way as Windows does for files whose name contains a mix of letters and numbers? Natural string sorting is not natively supported in .NET but can be easily implemented by specialising a string comparer and adding a few extensions to the enumerable string collection.
Sales Effectiveness in Dynamics CRM with Azure IoT and Machine Learning - Part 1
How can an organisation optimise its sales channels and product targeting by building a 365-degree view of its customers in Dynamics CRM? The answer, and topic of this article, is with the help of Azure IoT and Machine Learning services!
Scaling Applications with Azure Redis and Machine Learning - Part 1
This article presents design best practices and code examples for implementing the Azure Redis Cache and tuning the performance of ASP.NET MVC applications, optimising cache hit ratio and reducing “miss rate” with smart algorithms processed by Machine Learning.
Software Development Management in 11 steps
What it takes to be a great Software Development Manager? I have been building software for the last 15 years and have collected a few stories and experiences to share. Some I have used as questions when interviewing candidates. In 11 points, this is my story to date.
SOLID SharePoint apps with MVC
Practical code examples of ASP.NET MVC applications that connect to a SharePoint Server and comply with the SOLID principles.
The art of outsourcing projects
Outsourcing may look financially attractive, but working with companies in far-off lands introduces challenges that, if not considered properly, can drive any project to failure. Let’s explore some common pitfalls when working with offshore partners and a common-sense approach to work around them.
The value of hashtags
Customers expect a modern approach to advertising. Digital advertising can leverage evolving technology to provide just-in-time, just-at-the-right-place promotions.
We don't need no Yoda's syntax
There is an urban myth in the programmers’ community that the so called “Yoda’s syntax” performs better when checking an object for nullity. Let's demystify it...