DEV Community

Cover image for Step-by-Step Guide: Downloading PDFs from SharePoint and Displaying Them in a Xamarin App
Jollen Moyani for Syncfusion, Inc.

Posted on • Originally published at syncfusion.com on

Step-by-Step Guide: Downloading PDFs from SharePoint and Displaying Them in a Xamarin App

As we all know, Microsoft SharePoint is one of the most popular and widely used applications for storing files in the cloud. In this blog, we will see how to implement SharePoint authentication, access PDF files, and view them easily in a Xamarin application using our Syncfusion Xamarin PDF Viewer.

Let’s get started!

Configuring the Xamarin application using the Azure Portal

To start with, you need to register your Xamarin application in Azure AD. Sign into the Azure Portal, search for and select Azure Active Directory , then go to the App registrations tab and select New registration.

Step 1: In the Register an application page:

  1. Enter a Name for your app registration.
  2. Select the type of accounts you want to grant access to your app.
  3. Select Register. Register an application window

Step 2: After the app registration is created, take note of the Application (client) ID for later use. You can find it in the Overview tab of the application registration we just created.

Note down the Application (client) ID

Step 3: Then, go to the Authentication tab, click Add a platform , and add both iOS and Android. Just for Android, we need to generate and provide a signature hash.

Go to the Authentication tab, click Add a platform, and add both iOS and Android

Creating and configuring a Xamarin application

Now, integrate the Microsoft SharePoint authentication into our Xamarin application to access the PDF documents and view them using our Syncfusion Xamarin PDF Viewer.

Step 1: Create a new Xamarin.Forms project.

Step 2: Install the following NuGet packages in the PCL and all platform projects:

Additionally, in iOS, to launch the application with Syncfusion controls, you need to call the SfPdfDocumentViewRenderer.Init(),SfTreeViewRenderer.Init(), and SfLinearProgressBarRenderer.Init() methods in the FinishedLaunching overridden method of the AppDelegate class after the Xamarin.Forms framework has been initialized and before the LoadApplication is called. Refer to the following code sample.

public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
    global::Xamarin.Forms.Forms.Init();
    Syncfusion.SfPdfViewer.XForms.iOS.SfPdfDocumentViewRenderer.Init();
    Syncfusion.XForms.iOS.TreeView.SfTreeViewRenderer.Init();
    Syncfusion.XForms.iOS.ProgressBar.SfLinearProgressBarRenderer.Init();
    LoadApplication(new App());
    App.ParentWindow = null;
    return base.FinishedLaunching(app, options);
}
Enter fullscreen mode Exit fullscreen mode

Note: Here, the SfTreeView and SfProgressBar have just been used to improve the usability of the application. You can replace these with the controls of your choice.

Step 3: Create model classes named FileManager and PDFFile to hold the data of the SharePoint documents and the PDF document, respectively. Refer to the following code example.

FileManager.cs

using System.ComponentModel;
using Xamarin.Forms;
using Microsoft.SharePoint.Client;

namespace SharePoint
{
    public class FileManager : INotifyPropertyChanged
    {
        private string itemName;
        private bool hasChildNodes;
        private Folder folder;
        private File file;
        private ImageSource imageIcon;

        public FileManager()
        {
        }

        public string ItemName
        {
            get { return itemName; }
            set
            {
                itemName = value;
                RaisedOnPropertyChanged("ItemName");
            }
        }

        public bool HasChildNodes
        {
            get { return hasChildNodes; }
            set
            {
                hasChildNodes = value;
                RaisedOnPropertyChanged("HasChildNodes");
            }
        }

        public Folder Folder
        {
            get { return folder; }
            set
            {
                folder = value;
                RaisedOnPropertyChanged("Folder");
            }
        }

        public File File
        {
            get { return file; }
            set
            {
                file = value;
                RaisedOnPropertyChanged("File");
            }
        }

        public ImageSource ImageIcon
        {
            get { return imageIcon; }
            set
            {
                imageIcon = value;
                RaisedOnPropertyChanged("ImageIcon");
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public void RaisedOnPropertyChanged(string _PropertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(_PropertyName));
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

PDFFile.cs

using System.IO;

namespace SharePoint
{
    public class PdfFile
    {
        public Stream DocumentStream { get; set; }
        public string Name { get; set; }
        public PdfFile(Stream pdfStream,string pdfName)
        {
            DocumentStream = pdfStream;
            Name = pdfName;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Create a ViewModel class named SharePointViewModel.cs. Implement the logic to authenticate and retrieve the SharePoint documents and return the document stream of the selected PDF file in the File Manager. Refer to the following code example.

SharePointViewModel.cs

using Microsoft.Identity.Client;
using Microsoft.SharePoint.Client;
using PnP.Framework;
using Syncfusion.TreeView.Engine;
using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows.Input;
using Xamarin.Essentials;
using Xamarin.Forms;

namespace SharePoint
{
    internal class SharePointViewModel
    {
        // Replace with your SharePoint tenant name.
        private const string Tenant = "{YOUR TENANT NAME}";

        // Replace with your targeted SharePoint site name.
        private const string SiteName = "{YOUR SITE NAME}";

        // Replace with your Azure AD Application's Client ID.
        private const string ClientId = "{YOUR CLIENT ID}";

        // Replace with your Azure AD Application's Package Name or Bundle ID.
        private const string AppId = "{YOUR APP ID}";

        // For Android, replace with your Authentication Signature Hash.
        private const string SignatureHash = "{YOUR SIGNATURE HASH}";

        public ObservableCollection<FileManager> Documents { get; set; }        
        public ClientContext Context { get; set; }        
        public ICommand DocumentsViewOnDemandCommand { get; set; }
        public SharePointViewModel()
        {
            DocumentsViewOnDemandCommand = new Command(ExecuteOnDemandLoading, CanExecuteOnDemandLoading);
        }

        private bool CanExecuteOnDemandLoading(object sender)
        {
            var hasChildNodes = ((sender as TreeViewNode).Content as FileManager).HasChildNodes;
            if (hasChildNodes)
                return true;
            else
                return false;
        }

        private void ExecuteOnDemandLoading(object obj)
        {
            var node = obj as TreeViewNode;

            // Skip the repeated population of child items when every time the node expands.
            if (node.ChildNodes.Count > 0)
            {
                node.IsExpanded = true;
                return;
            }

            //Animation starts for expander to show progress of load on demand.
            node.ShowExpanderAnimation = true;

            Microsoft.SharePoint.Client.Folder root = (node.Content as FileManager).Folder;

            var documents = GetDocuments(root);

            node.PopulateChildNodes(documents);

            if (documents.Count > 0)
            {
                //Expand the node after child items are added.              
                node.IsExpanded = true;
            }

            //Stop the animation after load on demand is executed. If animation not stopped, it remains still after execution of load on demand.
            node.ShowExpanderAnimation = false;
        }

        /// <summary>
        /// Perform SharePoint authentication and return the documents of the target site.
        /// </summary>
        /// <returns>Documents of the target site</returns>
        internal async Task AuthenticateAndAcquireDocuments()
        {
            string redirectURI = DeviceInfo.Platform == DevicePlatform.Android ?
                $"msauth://{AppId}/{SignatureHash}" : $"msauth.{AppId}://auth";

            IPublicClientApplication PublicClientApp = PublicClientApplicationBuilder.Create(ClientId)
                .WithIosKeychainSecurityGroup(AppId)
                .WithRedirectUri(redirectURI)
                .WithAuthority("https://login.microsoftonline.com/common")
                .Build();

            var _scopes = new String[] { $"https://{Tenant}.sharepoint.com/AllSites.Read" }; //Replace with your own permissions.

            try
            {
                AuthenticationResult authResult = await PublicClientApp.AcquireTokenInteractive(_scopes)
                    .WithParentActivityOrWindow(App.ParentWindow)
                    .WithUseEmbeddedWebView(true)
                    .ExecuteAsync();

                var authManager = new AuthenticationManager().GetAccessTokenContext( $"https://{Tenant}.sharepoint.com/sites/{SiteName}",
                    authResult.AccessToken);

                Context = authManager.GetSiteCollectionContext();

                var web = Context.Web;//Gets target site web details.
                var docs = web.Lists.GetByTitle("Documents");//Gets site's documents list.
                Var root = docs.RootFolder; //Root folder.
                Documents = GetDocuments(root);
            }
            catch (Exception e)
            {
            }
        }

         /// <summary>
        /// Returns the documents present in the passed root or parent folder.
        /// </summary>
        /// <param name="root">Root folder</param>
        /// <returns>Documents of the root or parent folder</returns>
        internal ObservableCollection<FileManager> GetDocuments(Microsoft.SharePoint.Client.Folder root)
        {
            ObservableCollection<FileManager> documents = new ObservableCollection<FileManager>();

            Assembly assembly = typeof(DocumentsPage).GetTypeInfo().Assembly;
            Context.Load(root, f => f.ItemCount, f => f.Folders);
            Context.Load(root, f => f.ItemCount, f => f.Files);
            Context.ExecuteQuery();

            foreach (Microsoft.SharePoint.Client.Folder folder in root.Folders)
            {
                bool hasChildNodes = folder.ItemCount > 0 ? true : false;
                var folderItem = new FileManager()
                {
                    ItemName = folder.Name,
                    Folder = folder,
                    HasChildNodes = hasChildNodes,
                    ImageIcon = ImageSource.FromResource("SharePoint.Icons.treeview_folder.png", assembly)
                };
                documents.Add(folderItem);
            }

            foreach (Microsoft.SharePoint.Client.File file in root.Files)
            {
                if (file.Name.EndsWith(".pdf"))
                {
                    var fileItem = new FileManager()
                    {
                        ItemName = file.Name,
                        File = file,
                        ImageIcon = ImageSource.FromResource("SharePoint.Icons.treeview_pdf.png", assembly)
                    };
                    documents.Add(fileItem);
                }
            }
            return documents;
        }

        /// <summary>
        /// Returns the chosen PDF document details.
        /// </summary>
        /// <param name="file">PDF file chosen from the File Explorer</param>
        /// <returns>PDF document details</returns>
        internal PdfFile GetPdfDocumentDetails(Microsoft.SharePoint.Client.File file)
        {
            MemoryStream PdfDocumentStream = new MemoryStream();
            var fileStream = file.OpenBinaryStream();
            Context.Load(file);
            Context.ExecuteQuery();
            fileStream.Value.CopyTo(PdfDocumentStream);
            PdfDocumentStream.Position = 0;
            return new PdfFile(PdfDocumentStream, file.Name);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Make sure to replace the following things in the previous code example:

  • Tenant —SharePoint tenant name.
  • SiteName —SharePoint site name.
  • ClientId —Application’s Client ID noted from the Azure AD.
  • AppId —Application’s package name [Android] or bundle ID [iOS] given during Azure AD authentication.
  • SignatureHash —Signature hash noted from Android’s authentication.

Step 5: Create a view named PdfViewerPage to view the selected PDF file from the SharePoint documents. Refer to the following code example.

PdfViewerPage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
             xmlns:syncfusion="clr-namespace:Syncfusion.SfPdfViewer.XForms;assembly=Syncfusion.SfPdfViewer.XForms" 
             x:Class="SharePoint.PdfViewerPage">
    <ContentPage.Content>
        <syncfusion:SfPdfViewer x:Name="PdfViewer"/>
    </ContentPage.Content>
</ContentPage>
Enter fullscreen mode Exit fullscreen mode

PdfViewerPage.xaml.cs

using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace SharePoint
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class PdfViewerPage : ContentPage
    {
        public PdfViewerPage(PdfFile pdfFile)
        {
            InitializeComponent();
            this.Title = pdfFile.Name;
            pdfFile.DocumentStream.Position = 0;
            PdfViewer.LoadDocument(pdfFile.DocumentStream);
        }

        protected override void OnDisappearing()
        {
            PdfViewer.Dispose();
            base.OnDisappearing();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 6: Create another view named DocumentsPage to login and display the SharePoint documents of the given site name in the File Explorer-like Treeview. Also, in the TreeView’s ItemTapped handler method, implement the logic to retrieve the selected PDF document details and navigate to the PdfViewerPage to view them.

Note: In this example, we have used TreeView’s ItemTemplate Selector to customize the appearance of each item with different templates to create the file explorer view.

Refer to the following code example.

DocumentsPage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
             xmlns:treeView="clr-namespace:Syncfusion.XForms.TreeView;assembly=Syncfusion.SfTreeView.XForms"
             xmlns:progressBar="clr-namespace:Syncfusion.XForms.ProgressBar;assembly=Syncfusion.SfProgressBar.XForms"
             xmlns:local="clr-namespace:SharePoint"
             NavigationPage.HasNavigationBar="False"
             x:Class="SharePoint.DocumentsPage">

    <ContentPage.BindingContext>
        <local:SharePointViewModel/>
    </ContentPage.BindingContext>

    <ContentPage.Resources>
        <ResourceDictionary>
            <local:ItemTemplateSelector x:Key="ItemTemplateSelector" />
        </ResourceDictionary>
    </ContentPage.Resources>

    <ContentPage.Content>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="auto"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <progressBar:SfLinearProgressBar x:Name="ProgressBar" Grid.Row="0" HeightRequest="5" IsIndeterminate="True" IsVisible="false" ProgressColor="#038387"/>
            <StackLayout Grid.Row="1" Orientation="Vertical" VerticalOptions="Center">
                <Image x:Name="SharepointImage" HeightRequest="175" WidthRequest="175" VerticalOptions="Center"></Image>
                <Button Text="SHAREPOINT LOGIN" x:Name="LoginButton" WidthRequest="250"
                        HorizontalOptions="Center"
                        Font="18"
                        Clicked="LoginButton_Clicked"/>
                <Label x:Name="DocumentsTitleLabel" HeightRequest="50" IsVisible="false" Text="Documents" HorizontalTextAlignment="Center" VerticalTextAlignment="Center" FontSize="20" TextColor="White" BackgroundColor="#038387"/>
                <treeView:SfTreeView x:Name="DocumentsView" ItemHeight="40"
                                     IsVisible="false"
                                     ChildPropertyName="SubFiles"
                                     LoadOnDemandCommand="{Binding DocumentsViewOnDemandCommand}"
                                     ItemTemplateContextType="Node"                                   
                                     ItemTapped="DocumentsView_ItemTapped"
                                     SelectionBackgroundColor="#038387"
                                     SelectionForegroundColor="White"
                                     Indentation="20" 
                                     ExpanderWidth="40" ItemTemplate="{StaticResource ItemTemplateSelector}">
                </treeView:SfTreeView>
            </StackLayout>
        </Grid>
    </ContentPage.Content>
</ContentPage>
Enter fullscreen mode Exit fullscreen mode

DocumentsPage.xaml.cs

using System;
using System.Reflection;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace SharePoint
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class DocumentsPage : ContentPage
    {
        public DocumentsPage()
        {
            InitializeComponent();
            SharepointImage.Source = ImageSource.FromResource("SharePoint.Icons.sharepoint.png",
                typeof(DocumentsPage).GetTypeInfo().Assembly);
        }

        private async void LoginButton_Clicked(object sender, EventArgs e)
        {
            ProgressBar.IsVisible = true;
            LoginButton.IsVisible = false;

            await (BindingContext as SharePointViewModel).AuthenticateAndAcquireDocuments();

            SharepointImage.IsVisible = false;
            DocumentsTitleLabel.IsVisible = true;
            DocumentsView.IsVisible = true;
            DocumentsView.ItemsSource = (BindingContext as SharePointViewModel).Documents;
            ProgressBar.IsVisible = false;
        }

        private void DocumentsView_ItemTapped(object sender, Syncfusion.XForms.TreeView.ItemTappedEventArgs e)
        {
            Microsoft.SharePoint.Client.File file = (e.Node.Content as FileManager).File;
            if (file != null)
            {
                Navigation.PushAsync(new PdfViewerPage((BindingContext as SharePointViewModel)
                    .GetPdfDocumentDetails(file)));
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

In this example, you can navigate back to the Documents page from PDF Viewer page to choose or load another PDF document by clicking the back icon in the navigation bar. Refer to the following image.

Select the back icon in the navigation bar

Step 7: Before running the application, there are some configurations that need to be done in both Android and iOS platforms to make the SharePoint authentication successful and to continue with our application.

iOS

In iOS, to reopen our application after the authentication flow is completed, override the OpenUrl method in the AppDelegate class. Refer to the following code example.

public override bool OpenUrl(UIApplication app, NSUrl url, NSDictionary options)
{                  
 AuthenticationContinuationHelper.SetAuthenticationContinuationEventArgs(url);
 return true;
}
Enter fullscreen mode Exit fullscreen mode

Next, open the Entitlements.plist file and check the Enable Keychain option. This will add an entry for our app to the keychain, which is where iOS securely stores our credentials.

Select the Keychain checkbox

To allow the application to use the keychain, set the Custom Entitlements setting to point to the Entitlements.plist file. This setting option can be found in the Properties -> iOS Bundle Signing -> Additional Resources.

Navigate to Properties , iOS Bundle Signing and then Additional Resources

Finally, configure the iOS application to respond to the callback URL by defining a URL type in the Info.plist file. Add or update the Identifier , URL Schemes, and Role properties in the URL type. Refer to the following screenshot.

Add or update the Identifier, URL Schemes, and Role properties in the URL type

Android

In Android, to reopen our application after the authentication is completed, override the OnActivityResult method in the MainActivity class. Refer to the following code example.

protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
 base.OnActivityResult(requestCode, resultCode, data);   
 AuthenticationContinuationHelper.SetAuthenticationContinuationEventArgs(requestCode, resultCode, data);
}
Enter fullscreen mode Exit fullscreen mode

Next, configure the Android application to respond to the callback URL by providing an activity and an intent-filter in the AndroidManifest.xml file. Refer to the following code example.

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.companyname.SharePoint" android:installLocation="preferExternal">
  <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30" />
  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

  <application android:label="SharePoint.Android">

    <activity android:name="microsoft.identity.client.BrowserTabActivity">
      <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="msauth" android:host="com.syncfusion.sharepointauth" android:path="{YOUR_SIGNATURE_HASH}" />
      </intent-filter>
    </activity>

  </application>
</manifest>
Enter fullscreen mode Exit fullscreen mode

Note: Please make sure to replace the text {YOUR_SIGNATURE_HASH} with your application’s signature hash in the AndroidManifest.xml file.

Step 8: Finally, you can deploy the application. The output of this example application will look as follows.

Downloading PDFs from SharePoint and Displaying Them using Xamarin.Forms PDF Viewer

Resource

For more details, check out the complete code example on GitHub.

Conclusion

Thanks for reading! We have seen in this blog how to access PDF files from SharePoint and load them using Syncfusion’s Xamarin.Forms PDF Viewer control. Try this in your application and share your feedback in the comment section below!

If you aren’t a customer, you can try our 30-day free trial to check out these features. Also, try our other Xamarin examples from this GitHub location.

If you have any questions, you can contact us through our support forum, support portal, or feedback portal. We are always happy to assist you!

Related blogs

Top comments (0)