How to download a file from SharePoint using Ajax from html


Recently I was able to help someone who was getting a corrupt file when trying to download from SharePoint Online through AJAX call.

He was able to download TEXT files successfully using the existing code, but it didn’t work for PDF or Word or Excel documents.

<script type="text/javascript">
	window.onload = () => {
		let code = '{!ACCESS_TOKEN}';   
		let extension = '{!extension}';

		//make a ajax callout
		$.ajax({
			url: "https://<YourSharePointSite>.sharepoint.com/_api/Web/GetFileByServerRelativePath(decodedurl='/DocLibrary/pdf_file.pdf')/$value", 
			type: 'get',  
			contentType: true, 
			processData: false, 
			headers: { accept: 'application/json',
					  "Authorization": "Bearer "+code,
					 },
			success: function(response){
				alert('success response'+response)
				var a = document.createElement('a');
				var binaryData = [];
				binaryData.push(response);
				var url = window.URL.createObjectURL(new Blob(binaryData),{type: "application/octet-stream"});
			   
				a.href = url;
				a.download = 'pdf_file.pdf';
				document.body.appendChild(a);
				a.click();
				a.remove();
				window.URL.revokeObjectURL(url);
			},
		});
	}
</script>

He did try to add responseType :’arraybuffer’ or responseType :’blob’, or use ‘binaryStringResponseBody : true’, but neither of them have helped to open the downloaded content as PDF successfully.

He was able to get the response back from SharePoint online, but it was not recognized as intended content type, i.e. PDF. In console it looked like above.

To fix this we had to force the stream to be treated and parsed as ‘text/plain’, and it worked beautifully!!

Here is the modified code, which has worked:

<script type="text/javascript">
window.onload = () => {
	let code = '{!ACCESS_TOKEN}';   

	$.ajax({
		url: "https://<YourSharePointSite>.sharepoint.com/_api/Web/GetFileByServerRelativePath(decodedurl='/DocLibrary/pdf_file.pdf')/$value", 
		type: 'get',  
		contentType: true, 
		processData: false,
		encoding: null,
		headers: { 
			accept: 'application/json',
			"Authorization": "Bearer " + code
			},
		beforeSend: function (request) {
			request.overrideMimeType('text/plain; charset=x-user-defined');
			},
		success: function(response){
			var binary = "";
			var responseTextLen = response.length;

			for ( i = 0; i < responseTextLen; i++ ) {
				binary += String.fromCharCode(response.charCodeAt(i) & 255)
			}

			var a = document.createElement('a');
			a.href = "data:application/pdf;base64," + btoa(binary);
			a.download = 'pdf_file.pdf';
			document.body.appendChild(a);
			a.click();
			a.remove();
		},
	});
}
</script>

Take a look at ‘beforeSend’, using this we are forcing the requested MIME type as ‘text/plain’, instead of the arraybuffer response.

Here is an alternate implementation of the ‘success’ method using JavaScript FileReader API.

success: function(response){                
                var i = 0,
                dataArray = new Uint8Array(response.length);
                for (; i < response.length; i++) {
                    dataArray[i] = response.charCodeAt(i)
                }

                var blob = new Blob([dataArray.buffer], {
                    type: "application/pdf"
                });
                let link = document.createElement('a');
                link.download = 'pdf_file.pdf';

                let reader = new FileReader();
                reader.readAsDataURL(blob); // converts the blob to base64 and calls onload

                reader.onload = function() {
                  link.href = reader.result;
                  link.click();
                };
            }

This code should work for any file types. We just have to change the file type (MIME type) properly.

Here is a list of Microsoft file types and MIME types:

File extensionFile typeMIME type
.docxMicrosoft Office Word 2007 documentapplication/vnd.openxmlformats-officedocument.wordprocessingml.document
.docmOffice Word 2007 macro-enabled documentapplication/vnd.ms-word.document.macroEnabled.12
.dotxOffice Word 2007 templateapplication/vnd.openxmlformats-officedocument.wordprocessingml.template
.dotmOffice Word 2007 macro-enabled document templateapplication/vnd.ms-word.template.macroEnabled.12
.xlsxMicrosoft Office Excel 2007 workbookapplication/vnd.openxmlformats-officedocument.spreadsheetml.sheet
.xlsmOffice Excel 2007 macro-enabled workbookapplication/vnd.ms-excel.sheet.macroEnabled.12
.xltxOffice Excel 2007 templateapplication/vnd.openxmlformats-officedocument.spreadsheetml.template
.xltmOffice Excel 2007 macro-enabled workbook templateapplication/vnd.ms-excel.template.macroEnabled.12
.xlsbOffice Excel 2007 binary workbookapplication/vnd.ms-excel.sheet.binary.macroEnabled.12
.xlamOffice Excel 2007 add-inapplication/vnd.ms-excel.addin.macroEnabled.12
.pptxMicrosoft Office PowerPoint 2007 presentationapplication/vnd.openxmlformats-officedocument.presentationml.presentation
.pptmOffice PowerPoint 2007 macro-enabled presentationapplication/vnd.ms-powerpoint.presentation.macroEnabled.12
.ppsxOffice PowerPoint 2007 slide showapplication/vnd.openxmlformats-officedocument.presentationml.slideshow
.ppsmOffice PowerPoint 2007 macro-enabled slide showapplication/vnd.ms-powerpoint.slideshow.macroEnabled.12
.potxOffice PowerPoint 2007 templateapplication/vnd.openxmlformats-officedocument.presentationml.template
.potmOffice PowerPoint 2007 macro-enabled presentation templateapplication/vnd.ms-powerpoint.template.macroEnabled.12
.ppamOffice PowerPoint 2007 add-inapplication/vnd.ms-powerpoint.addin.macroEnabled.12
.sldxOffice PowerPoint 2007 slideapplication/vnd.openxmlformats-officedocument.presentationml.slide
.sldmOffice PowerPoint 2007 macro-enabled slideapplication/vnd.ms-powerpoint.slide.macroEnabled.12
.oneMicrosoft Office OneNote 2007 sectionapplication/msonenote
.onetoc2Office OneNote 2007 TOCapplication/msonenote
.onetmpOffice OneNote 2007 temporary fileapplication/msonenote
.onepkgOffice OneNote 2007 packageapplication/msonenote
.thmx2007 Office system release themeapplication/vnd.ms-officetheme

You can also check the common MIME types list from Mozilla. https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types

Hope this will help someone in need.

Use Application Insights logging adapters with NLog C# Console application


I was trying to create a demo console application using C# to check how can I use NLog to send logging information to Microsoft Application insights.

As usual, I have started with the GitHub repo https://github.com/Microsoft/ApplicationInsights-dotnet-logging and started following the steps, but I have seen that the default steps have stopped my logging completely. Even the Console and File listeners are not working! Later I found the solution and thought to share.

Step 1: Create a Console application in Visual Studio

Create a new project in Visual Studio
I have selected Console App (.NET Framework) for my project type
I have named my project as ConsoleNLogAppInsightDemo
Solution opened with default template

Step 2: Add NLog package from Nuget. You can follow the steps here https://github.com/nlog/nlog/wiki/Tutorial as well.

Add Nlog Package

Step 3: Add NLog.Config package as well, unless you want to add it manually.

NLog.Config package in NuGet

Step 4: I have used following basic configuration to log to Console and File.

<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      autoReload="true"
      throwExceptions="false"
      internalLogLevel="Off" internalLogFile="c:\temp\nlog-internal.log">
  <targets>
    <target name="logfile" xsi:type="File" fileName="nlogfile.txt" />
    <target name="logconsole" xsi:type="Console" />
  </targets>

  <rules>
    <logger name="*" minlevel="Info" writeTo="logconsole" />
    <logger name="*" minlevel="Debug" writeTo="logfile" />
  </rules>
</nlog>

Step 5: Add basic codes in you Program.cs file to test NLog

using System;

namespace ConsoleNLogAppInsightDemo
{
    class Program
    {
        private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
        static void Main(string[] args)
        {
            try
            {
                Logger.Info("Hello world");
                System.Console.ReadKey();
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "Hmm! Something is not right.");
            }
        }
    }
}

Step 6: Run your application and see the output in Console and File.

Program output
Log output in local File

Step 7: Now the time is to add Application Insight. Open your Azure subscription, go to the ‘Application Insights’ resource that you have created and copy the ‘Instrumentation Key’.

Here are some screenshots I took while creating the Application Insight resource from scratch.

Search for Application Insights resource in Azure Portal
Click ‘Add’ to create a new resource
Create the resource

Open the resource once the resource creation is successful.

Deployment successful
Copy the instrumentation key

Step 8: Now, go back to your Console application and add the package named ‘Microsoft.ApplicationInsights.NLogTarget’. https://www.nuget.org/packages/Microsoft.ApplicationInsights.NLogTarget/

NLogTarget package from Microsoft

Step 9: Now, open your NLog.config file and add the ‘Extension’, ‘Target’ and ‘Rule’ related to Application Insights as mentioned in this GitHub repo https://github.com/Microsoft/ApplicationInsights-dotnet-logging.

<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      autoReload="true"
      throwExceptions="false"
      internalLogLevel="Off" internalLogFile="c:\temp\nlog-internal.log">
  <extensions>
    <add assembly="Microsoft.ApplicationInsights.NLogTarget" />
  </extensions>
  <targets>
    <target name="logfile" xsi:type="File" fileName="nlogfile.txt" />
    <target name="logconsole" xsi:type="Console" />
    <target name="aiTarget" xsi:type="ApplicationInsightsTarget" >
      <instrumentationKey>YOUR_INSTRUMENTAION_KEY</instrumentationKey>
      <!-- Only required if not using ApplicationInsights.config -->
      <contextproperty name="threadid" layout="${threadid}" />
      <!-- Can be repeated with more context -->
    </target>
  </targets>

  <rules>
    <logger name="*" minlevel="Info" writeTo="logconsole" />
    <logger name="*" minlevel="Debug" writeTo="logfile" />
    <logger name="*" minlevel="Trace" writeTo="aiTarget" />
  </rules>
</nlog>

Step 10: Now, add tracing code in your Program.cs file and run it.

Logger.Trace("This should go to App insight");

Here is my full code:

using System;

namespace ConsoleNLogAppInsightDemo
{
    class Program
    {
        private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
        static void Main(string[] args)
        {
            try
            {
                Logger.Info("Hello world");
                Logger.Trace("This should go to App insight");
                System.Console.ReadKey();
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "Hmm! Something is not right.");
            }
        }
    }
Completely blank output

Check the output window in Visual Studio after running the application. You would see a message saying that ‘Application Insights Telemetry (unconfigured)’.

Application Insights Telemetry (unconfigured)

At this point I was completely clueless why it would say unconfigured. After banging my head to the laptop screen few times, I have figured out the problem.

It seems, when you add the ‘Microsoft.ApplicationInsights.NLogTarget’ package from Nuget, it automatically adds some code in your App.config file.

App.config file content before adding Microsoft.ApplicationInsights.NLogTarget
App.config file content after adding Microsoft.ApplicationInsights.NLogTarget
<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <configSections>
        <section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog" />
    </configSections>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
    </startup>
    <nlog>
        <extensions>
            <add assembly="Microsoft.ApplicationInsights.NLogTarget" />
        </extensions>
        <targets>
            <target type="ApplicationInsightsTarget" name="aiTarget" />
        </targets>
        <rules>
            <logger name="*" minlevel="Trace" writeTo="aiTarget" />
        </rules>
    </nlog>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-4.0.1.1" newVersion="4.0.1.1" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>

So, it also adds the NLog configuration in App.config file, which is clearly not configured with the instrumentation key.

At this point, you need to either put the instrumentation key in App.config and use the NLog configuration from there (plus remove the NLog.config file) or remove the NLog configuration from App.config completely so that the NLog.config file can be taken into consideration. I would suggest the latter since your NLog.config file can have some existing configurations already, with additional targets and rules.

So I have decided to remove <nlog> block from my App.config and everything started to work as expected.

Console output came back
Application Insights Telemetry (unconfigured) message is now gone

You will see that the Application Insights Telemetry (unconfigured) message is now gone from output. Also, the data will show up in Application Insights search window.

Application insights search window showing data

So, you can say it was my stupidity that has caused this issue in the first place, but I thought to share the good news with you any way 😀