diff --git a/Demos/ASP.NET Core/README.md b/Demos/ASP.NET Core/README.md
index ce30905dd..b663f8ef3 100644
--- a/Demos/ASP.NET Core/README.md
+++ b/Demos/ASP.NET Core/README.md
@@ -19,29 +19,15 @@ GroupDocs.Viewer supports over [170 file-formats](https://docs.groupdocs.com/vie
## Features
-
-- Clean, modern and intuitive design
-- Easily switchable colour theme (create your own colour theme in 5 minutes)
- Responsive design
-- Mobile support (open application on any mobile device)
-- Support over 170 document and image formats including **DOCX**, **PPTX**, and **XLSX**
-- HTML and image modes
-- Fully customizable navigation panel
-- Open password protected documents
+- Fully customizable navigation panel & thumbnails
- Text searching & highlighting
-- Download documents
-- Upload documents
-- Print document
-- Rotate pages
+- Download & upload documents
+- Print document as PDF
- Zoom in/out documents without quality loss in HTML mode
-- Thumbnails
-- Smooth page navigation
-- Smooth document scrolling
+- Smooth page navigation & scrolling
- Preload pages for faster document rendering
-- Multi-language support for displaying errors
-- Display two or more pages side by side (when zooming out)
-- Cross-browser support (Safari, Chrome, Opera, Firefox)
-- Cross-platform support (Windows, Linux, MacOS)
+- Multi-language support
## How to run
@@ -70,11 +56,15 @@ Open the app by navigating to in your favorite br
The MIT License (MIT).
-Please have a look at the LICENSE.md for more details
+Please have a look at the [LICENSE](LICENSE) for more details
-## GroupDocs Document Viewer on other platforms
+## More Demo Projects
-- [JAVA DropWizard Document Viewer](https://github.com/groupdocs-viewer/GroupDocs.Viewer-for-Java/tree/master/Demos/Dropwizard)
-- [JAVA Spring Document viewer](https://github.com/groupdocs-viewer/GroupDocs.Viewer-for-Java/tree/master/Demos/Spring)
+- [Java Dropwizard Demo](https://github.com/groupdocs-viewer/GroupDocs.Viewer-for-Java/tree/master/Demos/Dropwizard)
+- [Java Spring Demo](https://github.com/groupdocs-viewer/GroupDocs.Viewer-for-Java/tree/master/Demos/Spring)
+- [ASP.NET Web Forms Demo](https://github.com/groupdocs-viewer/GroupDocs.Viewer-for-.NET/tree/master/Demos/ASP.NET%20Web%20Forms)
+- [ASP.NET MVC Demo](https://github.com/groupdocs-viewer/GroupDocs.Viewer-for-.NET/tree/master/Demos/ASP.NET%20MVC)
+- [Windows Forms Demo](https://github.com/groupdocs-viewer/GroupDocs.Viewer-for-.NET/tree/master/Demos/Windows%20Forms)
+- [WPF Demo](https://github.com/groupdocs-viewer/GroupDocs.Viewer-for-.NET/tree/master/Demos/WPF)
-[Home](https://www.groupdocs.com/) | [Product Page](https://products.groupdocs.com/viewer/net) | [Documentation](https://docs.groupdocs.com/viewer/net/) | [Demo](https://products.groupdocs.app/viewer/total) | [API Reference](https://apireference.groupdocs.com/net/viewer) | [Examples](https://github.com/groupdocs-viewer/GroupDocs.Viewer-for-.NET) | [Blog](https://blog.groupdocs.com/category/viewer/) | [Free Support](https://forum.groupdocs.com/c/viewer) | [Temporary License](https://purchase.groupdocs.com/temporary-license)
+[Home](https://www.groupdocs.com/) | [Product Page](https://products.groupdocs.com/viewer/net) | [Documentation](https://docs.groupdocs.com/viewer/net/) | [Demo](https://products.groupdocs.app/viewer/total) | [API Reference](https://apireference.groupdocs.com/net/viewer) | [Examples](https://github.com/groupdocs-viewer/GroupDocs.Viewer-for-.NET/tree/master/Examples) | [Blog](https://blog.groupdocs.com/category/viewer/) | [Free Support](https://forum.groupdocs.com/c/viewer) | [Temporary License](https://purchase.groupdocs.com/temporary-license)
diff --git a/Demos/ASP.NET Core/src/Files/password.pdf b/Demos/ASP.NET Core/src/Files/password.pdf
deleted file mode 100644
index ea7e42868..000000000
Binary files a/Demos/ASP.NET Core/src/Files/password.pdf and /dev/null differ
diff --git a/Demos/ASP.NET Core/src/Files/sample.docx b/Demos/ASP.NET Core/src/Files/sample.docx
deleted file mode 100644
index 4f0190b8e..000000000
Binary files a/Demos/ASP.NET Core/src/Files/sample.docx and /dev/null differ
diff --git a/Demos/ASP.NET Core/src/Files/sample.pdf b/Demos/ASP.NET Core/src/Files/sample.pdf
deleted file mode 100644
index b290105ee..000000000
Binary files a/Demos/ASP.NET Core/src/Files/sample.pdf and /dev/null differ
diff --git a/Demos/ASP.NET Core/src/Files/sample.pptx b/Demos/ASP.NET Core/src/Files/sample.pptx
deleted file mode 100644
index 6a3fc4f71..000000000
Binary files a/Demos/ASP.NET Core/src/Files/sample.pptx and /dev/null differ
diff --git a/Demos/ASP.NET Core/src/Files/sample.xlsx b/Demos/ASP.NET Core/src/Files/sample.xlsx
deleted file mode 100644
index 21db220d8..000000000
Binary files a/Demos/ASP.NET Core/src/Files/sample.xlsx and /dev/null differ
diff --git a/Demos/ASP.NET Core/src/GroupDocs.Viewer.AspNetCore.csproj b/Demos/ASP.NET Core/src/GroupDocs.Viewer.AspNetCore.csproj
index 7f61ae328..5f53d4003 100644
--- a/Demos/ASP.NET Core/src/GroupDocs.Viewer.AspNetCore.csproj
+++ b/Demos/ASP.NET Core/src/GroupDocs.Viewer.AspNetCore.csproj
@@ -10,7 +10,7 @@
-
+
diff --git a/Demos/ASP.NET Core/src/Storage/Files/flowchart.vsdx b/Demos/ASP.NET Core/src/Storage/Files/flowchart.vsdx
new file mode 100644
index 000000000..12db7e009
Binary files /dev/null and b/Demos/ASP.NET Core/src/Storage/Files/flowchart.vsdx differ
diff --git a/Demos/ASP.NET Core/src/Storage/Files/groupdocs.pptx b/Demos/ASP.NET Core/src/Storage/Files/groupdocs.pptx
new file mode 100644
index 000000000..d94d696dd
Binary files /dev/null and b/Demos/ASP.NET Core/src/Storage/Files/groupdocs.pptx differ
diff --git a/Demos/ASP.NET Core/src/Storage/Files/house-plan.dwg b/Demos/ASP.NET Core/src/Storage/Files/house-plan.dwg
new file mode 100644
index 000000000..ff07697b5
Binary files /dev/null and b/Demos/ASP.NET Core/src/Storage/Files/house-plan.dwg differ
diff --git a/Demos/ASP.NET Core/src/Storage/Files/invoice.xlsx b/Demos/ASP.NET Core/src/Storage/Files/invoice.xlsx
new file mode 100644
index 000000000..f223175b2
Binary files /dev/null and b/Demos/ASP.NET Core/src/Storage/Files/invoice.xlsx differ
diff --git a/Demos/ASP.NET Core/src/Storage/Files/resume.docx b/Demos/ASP.NET Core/src/Storage/Files/resume.docx
new file mode 100644
index 000000000..1d9f67edb
Binary files /dev/null and b/Demos/ASP.NET Core/src/Storage/Files/resume.docx differ
diff --git a/Demos/ASP.NET Core/src/Storage/Files/resume.pdf b/Demos/ASP.NET Core/src/Storage/Files/resume.pdf
new file mode 100644
index 000000000..ef4cae4db
Binary files /dev/null and b/Demos/ASP.NET Core/src/Storage/Files/resume.pdf differ
diff --git a/Demos/ASP.NET Core/src/Storage/Files/software-development-plan.mpp b/Demos/ASP.NET Core/src/Storage/Files/software-development-plan.mpp
new file mode 100644
index 000000000..5e48341a2
Binary files /dev/null and b/Demos/ASP.NET Core/src/Storage/Files/software-development-plan.mpp differ
diff --git a/Demos/ASP.NET Core/src/Storage/Files/vector-image.svg b/Demos/ASP.NET Core/src/Storage/Files/vector-image.svg
new file mode 100644
index 000000000..6df8640de
--- /dev/null
+++ b/Demos/ASP.NET Core/src/Storage/Files/vector-image.svg
@@ -0,0 +1,425 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Demos/MVC/.gitattributes b/Demos/ASP.NET MVC/.gitattributes
similarity index 100%
rename from Demos/MVC/.gitattributes
rename to Demos/ASP.NET MVC/.gitattributes
diff --git a/Demos/MVC/.gitignore b/Demos/ASP.NET MVC/.gitignore
similarity index 100%
rename from Demos/MVC/.gitignore
rename to Demos/ASP.NET MVC/.gitignore
diff --git a/Demos/WebForms/LICENSE b/Demos/ASP.NET MVC/LICENSE
similarity index 100%
rename from Demos/WebForms/LICENSE
rename to Demos/ASP.NET MVC/LICENSE
diff --git a/Demos/ASP.NET MVC/README.md b/Demos/ASP.NET MVC/README.md
new file mode 100644
index 000000000..cca6d78b9
--- /dev/null
+++ b/Demos/ASP.NET MVC/README.md
@@ -0,0 +1,69 @@
+![Alt text](https://raw.githubusercontent.com/groupdocs-viewer/groupdocs-viewer.github.io/master/resources/image/banner.png "GroupDocs.Viewer")
+
+# GroupDocs.Viewer for .NET ASP.NET MVC Demo
+
+![GitHub](https://img.shields.io/github/license/groupdocs-viewer/GroupDocs.Viewer-for-.NET)
+
+In order to demonstrate [GroupDocs.Viewer for .NET](https://products.groupdocs.com/viewer/net) reach and powerful features we prepared **document viewer** demo. Which can be used as a standalone application or easily integrated into your project.
+
+## System Requirements
+- .NET Framework 4.8
+- Visual Studio 2022
+
+## Supported Document Formats
+
+GroupDocs.Viewer for .NET enables you to render Microsoft Word, Microsoft Excel, Microsoft PowerPoint, and many more file types in HTML, PDF, PNG, and JPEG formats. The complete list of the supported document and file formats can be found in the [Supported document formats](https://docs.groupdocs.com/viewer/net/supported-document-formats/) documentation article.
+
+## Demo Video
+
+
+
+
+
+
+
+## Features
+- Responsive design
+- Fully customizable navigation panel & thumbnails
+- Text searching & highlighting
+- Download & upload documents
+- Print document as PDF
+- Zoom in/out documents without quality loss in HTML mode
+- Smooth page navigation & scrolling
+- Preload pages for faster document rendering
+- Multi-language support
+
+## How To Run
+
+Download the [source code](https://github.com/groupdocs-viewer/GroupDocs.Viewer-for-.NET/archive/master.zip) from github or clone this repository:
+
+```bash
+git clone https://github.com/groupdocs-viewer/GroupDocs.Viewer-for-.NET
+```
+
+Navigate to `Demos\ASP.NET MVC\src` and open `GroupDocs.Viewer.AspNetMvc.sln` in Visual Studio. Build and run the project.
+
+Open the app by navigating to in your favorite browser.
+
+## Configuration
+
+You can configure this demo project in [Global.asax.cs](./src/Global.asax.cs) file in `ConfigureServices` method which includes configuration for GroupDocs.Viewer and UI.
+
+NOTE: without a license application will run in trial mode, [purchase a license](https://purchase.groupdocs.com/buy) or [request a temporary license](https://purchase.groupdocs.com/temporary-license).
+
+## License
+
+The MIT License (MIT).
+
+Please have a look at the [LICENSE](LICENSE) for more details
+
+## More Demo Projects
+
+- [Java Dropwizard Demo](https://github.com/groupdocs-viewer/GroupDocs.Viewer-for-Java/tree/master/Demos/Dropwizard)
+- [Java Spring Demo](https://github.com/groupdocs-viewer/GroupDocs.Viewer-for-Java/tree/master/Demos/Spring)
+- [ASP.NET Core Demo](https://github.com/groupdocs-viewer/GroupDocs.Viewer-for-.NET/tree/master/Demos/ASP.NET%20Core)
+- [ASP.NET Web Forms Demo](https://github.com/groupdocs-viewer/GroupDocs.Viewer-for-.NET/tree/master/Demos/ASP.NET%20Web%20Forms)
+- [Windows Forms Demo](https://github.com/groupdocs-viewer/GroupDocs.Viewer-for-.NET/tree/master/Demos/Windows%20Forms)
+- [WPF Demo](https://github.com/groupdocs-viewer/GroupDocs.Viewer-for-.NET/tree/master/Demos/WPF)
+
+[Home](https://www.groupdocs.com/) | [Product Page](https://products.groupdocs.com/viewer/net) | [Documentation](https://docs.groupdocs.com/viewer/net/) | [Demo](https://products.groupdocs.app/viewer/total) | [API Reference](https://apireference.groupdocs.com/net/viewer) | [Examples](https://github.com/groupdocs-viewer/GroupDocs.Viewer-for-.NET/tree/master/Examples) | [Blog](https://blog.groupdocs.com/category/viewer/) | [Free Support](https://forum.groupdocs.com/c/viewer) | [Temporary License](https://purchase.groupdocs.com/temporary-license)
diff --git a/Demos/ASP.NET MVC/src/ActionResults/FileActionResult.cs b/Demos/ASP.NET MVC/src/ActionResults/FileActionResult.cs
new file mode 100644
index 000000000..27f8cb376
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/ActionResults/FileActionResult.cs
@@ -0,0 +1,50 @@
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Web.Http;
+
+namespace GroupDocs.Viewer.AspNetMvc.ActionResults
+{
+ internal class FileActionResult : IHttpActionResult
+ {
+ private readonly byte[] _data;
+ private readonly string _fileName;
+ private readonly string _contentType;
+ readonly HttpRequestMessage _request;
+
+ public FileActionResult(byte[] data, string fileName, string contentType,
+ HttpRequestMessage request)
+ {
+ _data = data;
+ _fileName = fileName;
+ _contentType = contentType;
+ _request = request;
+ }
+
+ public Task ExecuteAsync(CancellationToken cancellationToken)
+ {
+ var response = new HttpResponseMessage
+ {
+ Content = new ByteArrayContent(_data),
+ StatusCode = HttpStatusCode.OK,
+ RequestMessage = _request
+ };
+
+ var contentType = string.IsNullOrEmpty(_contentType)
+ ? "application/octet-stream"
+ : _contentType;
+
+ response.Content.Headers.ContentType =
+ new MediaTypeHeaderValue(contentType);
+ response.Content.Headers.ContentDisposition =
+ new ContentDispositionHeaderValue("attachment")
+ {
+ FileName = _fileName
+ };
+
+ return Task.FromResult(response);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/ActionResults/JsonActionResult.cs b/Demos/ASP.NET MVC/src/ActionResults/JsonActionResult.cs
new file mode 100644
index 000000000..23a7920d2
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/ActionResults/JsonActionResult.cs
@@ -0,0 +1,39 @@
+using System.Net;
+using System.Net.Http;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Web.Http;
+using Newtonsoft.Json;
+
+namespace GroupDocs.Viewer.AspNetMvc.ActionResults
+{
+ internal class JsonActionResult : IHttpActionResult
+ {
+ readonly object _value;
+ public HttpStatusCode StatusCode { get; set; } = HttpStatusCode.OK;
+
+ readonly HttpRequestMessage _request;
+
+ public JsonActionResult(object value, HttpRequestMessage request)
+ {
+ _value = value;
+ _request = request;
+ }
+
+ public Task ExecuteAsync(CancellationToken cancellationToken)
+ {
+ var json = JsonConvert.SerializeObject(_value, Formatting.Indented);
+ var content = new StringContent(json, Encoding.UTF8, "application/json");
+
+ var response = new HttpResponseMessage
+ {
+ Content = content,
+ StatusCode = StatusCode,
+ RequestMessage = _request
+ };
+
+ return Task.FromResult(response);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/ActionResults/ResourceActionResult.cs b/Demos/ASP.NET MVC/src/ActionResults/ResourceActionResult.cs
new file mode 100644
index 000000000..09a67c8c8
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/ActionResults/ResourceActionResult.cs
@@ -0,0 +1,40 @@
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Web.Http;
+
+namespace GroupDocs.Viewer.AspNetMvc.ActionResults
+{
+ internal class ResourceActionResult : IHttpActionResult
+ {
+ private readonly byte[] _data;
+ private readonly string _contentType;
+ readonly HttpRequestMessage _request;
+
+ public ResourceActionResult(byte[] data, string contentType, HttpRequestMessage request)
+ {
+ _data = data;
+ _contentType = contentType;
+ _request = request;
+ }
+
+ public Task ExecuteAsync(CancellationToken cancellationToken)
+ {
+ var response = new HttpResponseMessage
+ {
+ Content = new ByteArrayContent(_data),
+ StatusCode = HttpStatusCode.OK,
+ RequestMessage = _request
+ };
+
+ response.Content.Headers.ContentType =
+ new MediaTypeHeaderValue(_contentType);
+ response.Content.Headers.ContentDisposition
+ = new ContentDispositionHeaderValue("inline");
+
+ return Task.FromResult(response);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/MVC/src/App_Start/RouteConfig.cs b/Demos/ASP.NET MVC/src/App_Start/RouteConfig.cs
similarity index 90%
rename from Demos/MVC/src/App_Start/RouteConfig.cs
rename to Demos/ASP.NET MVC/src/App_Start/RouteConfig.cs
index bf6418998..f9e325661 100644
--- a/Demos/MVC/src/App_Start/RouteConfig.cs
+++ b/Demos/ASP.NET MVC/src/App_Start/RouteConfig.cs
@@ -1,19 +1,19 @@
-using System.Web.Mvc;
-using System.Web.Routing;
-
-namespace GroupDocs.Viewer.MVC
-{
- public class RouteConfig
- {
- public static void RegisterRoutes(RouteCollection routes)
- {
- routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
-
- routes.MapRoute(
- name: "Default",
- url: "{controller}/{action}/{id}",
- defaults: new { controller = "Viewer", action = "Index", id = UrlParameter.Optional }
- );
- }
- }
-}
+using System.Web.Mvc;
+using System.Web.Routing;
+
+namespace GroupDocs.Viewer.AspNetMvc
+{
+ public class RouteConfig
+ {
+ public static void RegisterRoutes(RouteCollection routes)
+ {
+ routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
+
+ routes.MapRoute(
+ name: "Default",
+ url: "{controller}/{action}/{id}",
+ defaults: new { controller = "Viewer", action = "Index", id = UrlParameter.Optional }
+ );
+ }
+ }
+}
diff --git a/Demos/ASP.NET MVC/src/App_Start/WebApiConfig.cs b/Demos/ASP.NET MVC/src/App_Start/WebApiConfig.cs
new file mode 100644
index 000000000..846ce4cff
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/App_Start/WebApiConfig.cs
@@ -0,0 +1,13 @@
+using System.Web.Http;
+
+namespace GroupDocs.Viewer.AspNetMvc
+{
+ public static class WebApiConfig
+ {
+ public static void Register(HttpConfiguration config)
+ {
+ config.EnableCors();
+ config.MapHttpAttributeRoutes();
+ }
+ }
+}
diff --git a/Demos/ASP.NET MVC/src/ClientApp/.browserslistrc b/Demos/ASP.NET MVC/src/ClientApp/.browserslistrc
new file mode 100644
index 000000000..427441dc9
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/ClientApp/.browserslistrc
@@ -0,0 +1,17 @@
+# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
+# For additional information regarding the format and rule options, please see:
+# https://github.com/browserslist/browserslist#queries
+
+# For the full list of supported browsers by the Angular framework, please see:
+# https://angular.io/guide/browser-support
+
+# You can see what browsers were selected by your queries by running:
+# npx browserslist
+
+last 1 Chrome version
+last 1 Firefox version
+last 2 Edge major versions
+last 2 Safari major versions
+last 2 iOS major versions
+Firefox ESR
+not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line.
diff --git a/Demos/WebForms/src/client/.gitignore b/Demos/ASP.NET MVC/src/ClientApp/.gitignore
similarity index 79%
rename from Demos/WebForms/src/client/.gitignore
rename to Demos/ASP.NET MVC/src/ClientApp/.gitignore
index ee5c9d833..86d943a9b 100644
--- a/Demos/WebForms/src/client/.gitignore
+++ b/Demos/ASP.NET MVC/src/ClientApp/.gitignore
@@ -4,10 +4,16 @@
/dist
/tmp
/out-tsc
+# Only exists if Bazel was run
+/bazel-out
# dependencies
/node_modules
+# profiling files
+chrome-profiler-events*.json
+speed-measure-plugin*.json
+
# IDEs and editors
/.idea
.project
@@ -23,6 +29,7 @@
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
+.history/*
# misc
/.sass-cache
diff --git a/Demos/ASP.NET MVC/src/ClientApp/README.md b/Demos/ASP.NET MVC/src/ClientApp/README.md
new file mode 100644
index 000000000..daf9ae209
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/ClientApp/README.md
@@ -0,0 +1,19 @@
+# GroupdocsViewerUI
+
+This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 11.2.3.
+
+## Development server
+
+Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
+
+## Code scaffolding
+
+Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
+
+## Build
+
+Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.
+
+## Further help
+
+To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
diff --git a/Demos/ASP.NET MVC/src/ClientApp/angular.json b/Demos/ASP.NET MVC/src/ClientApp/angular.json
new file mode 100644
index 000000000..55575bfe2
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/ClientApp/angular.json
@@ -0,0 +1,116 @@
+{
+ "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
+ "version": 1,
+ "newProjectRoot": "projects",
+ "projects": {
+ "groupdocs-viewer-ui": {
+ "projectType": "application",
+ "schematics": {
+ "@schematics/angular:component": {
+ "inlineTemplate": true,
+ "inlineStyle": true,
+ "skipTests": true
+ },
+ "@schematics/angular:class": {
+ "skipTests": true
+ },
+ "@schematics/angular:directive": {
+ "skipTests": true
+ },
+ "@schematics/angular:guard": {
+ "skipTests": true
+ },
+ "@schematics/angular:interceptor": {
+ "skipTests": true
+ },
+ "@schematics/angular:module": {
+ "skipTests": true
+ },
+ "@schematics/angular:pipe": {
+ "skipTests": true
+ },
+ "@schematics/angular:service": {
+ "skipTests": true
+ },
+ "@schematics/angular:application": {
+ "strict": true
+ }
+ },
+ "root": "",
+ "sourceRoot": "src",
+ "prefix": "app",
+ "architect": {
+ "build": {
+ "builder": "@angular-devkit/build-angular:browser",
+ "options": {
+ "outputPath": "dist",
+ "index": "src/index.html",
+ "main": "src/main.ts",
+ "polyfills": "src/polyfills.ts",
+ "tsConfig": "tsconfig.app.json",
+ "aot": true,
+ "assets": [
+ "src/assets"
+ ],
+ "styles": [],
+ "scripts": [],
+ "allowedCommonJsDependencies": [
+ "hammerjs", "jquery"
+ ]
+ },
+ "configurations": {
+ "production": {
+ "fileReplacements": [
+ {
+ "replace": "src/environments/environment.ts",
+ "with": "src/environments/environment.prod.ts"
+ }
+ ],
+ "index": {
+ "input": "src/index.prod.html",
+ "output": "index.html"
+ },
+ "outputPath": "../assets",
+ "optimization": true,
+ "outputHashing": "none",
+ "sourceMap": false,
+ "namedChunks": false,
+ "extractLicenses": false,
+ "vendorChunk": false,
+ "buildOptimizer": true,
+ "budgets": [
+ {
+ "type": "initial",
+ "maximumWarning": "4000kb",
+ "maximumError": "5000kb"
+ },
+ {
+ "type": "anyComponentStyle",
+ "maximumWarning": "4kb",
+ "maximumError": "5kb"
+ }
+ ]
+ }
+ }
+ },
+ "serve": {
+ "builder": "@angular-devkit/build-angular:dev-server",
+ "options": {
+ "browserTarget": "groupdocs-viewer-ui:build"
+ },
+ "configurations": {
+ "production": {
+ "browserTarget": "groupdocs-viewer-ui:build:production"
+ }
+ }
+ },
+ "extract-i18n": {
+ "builder": "@angular-devkit/build-angular:extract-i18n",
+ "options": {
+ "browserTarget": "groupdocs-viewer-ui:build"
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Demos/ASP.NET MVC/src/ClientApp/package.json b/Demos/ASP.NET MVC/src/ClientApp/package.json
new file mode 100644
index 000000000..e6ff78140
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/ClientApp/package.json
@@ -0,0 +1,31 @@
+{
+ "name": "groupdocs-viewer-ui",
+ "version": "1.0.0",
+ "scripts": {
+ "ng": "ng",
+ "start": "ng serve",
+ "build": "ng build --configuration=production"
+ },
+ "private": true,
+ "dependencies": {
+ "@angular/animations": "^14.1.0",
+ "@angular/common": "^14.1.0",
+ "@angular/compiler": "^14.1.0",
+ "@angular/core": "^14.1.0",
+ "@angular/forms": "^14.1.0",
+ "@angular/platform-browser": "^14.1.0",
+ "@angular/platform-browser-dynamic": "^14.1.0",
+ "@angular/router": "^14.1.0",
+ "@groupdocs.examples.angular/viewer": "^0.8.92",
+ "rxjs": "^7.5.6",
+ "tslib": "^2.4.0",
+ "zone.js": "^0.11.7"
+ },
+ "devDependencies": {
+ "@angular-devkit/build-angular": "^14.1.0",
+ "@angular/cli": "^14.1.0",
+ "@angular/compiler-cli": "^14.1.0",
+ "@types/node": "^18.0.6",
+ "typescript": "^4.7.4"
+ }
+}
diff --git a/Demos/ASP.NET MVC/src/ClientApp/src/app/app.component.html b/Demos/ASP.NET MVC/src/ClientApp/src/app/app.component.html
new file mode 100644
index 000000000..be08b9bd6
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/ClientApp/src/app/app.component.html
@@ -0,0 +1,109 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{currentPage}}/{{countPages}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{'Present' | translate}}
+
+
+
+
+
+ Viewer
+ {{getFileName()}}
+
+
+
+
+ {{'Stop' | translate}}
+
+
+
+
+
+
+
+
+
+
+
+
+ = 10 ? 'seconds-remaining two-digits' : 'seconds-remaining'">{{secondsLeft}}
+
+
+
+
+
+
+
+ {{'Click' | translate}} {{'to open file' | translate}}
+ {{'Or drop file here' | translate}}
+
+
+
+
+
+
+
diff --git a/Demos/ASP.NET MVC/src/ClientApp/src/app/app.component.less b/Demos/ASP.NET MVC/src/ClientApp/src/app/app.component.less
new file mode 100644
index 000000000..5bf4280e9
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/ClientApp/src/app/app.component.less
@@ -0,0 +1,256 @@
+@import (css) url('https://fonts.googleapis.com/css?family=Open+Sans&display=swap');
+@import "./variables";
+
+:host * {
+ font-family: 'Open Sans', Arial, Helvetica, sans-serif;
+}
+
+.noselect {
+ -webkit-touch-callout: none; /* iOS Safari */
+ -webkit-user-select: none; /* Safari */
+ -khtml-user-select: none; /* Konqueror HTML */
+ -moz-user-select: none; /* Old versions of Firefox */
+ -ms-user-select: none; /* Internet Explorer/Edge */
+ user-select: none; /* Non-prefixed version, currently
+ supported by Chrome, Edge, Opera and Firefox */
+}
+
+.current-page-number {
+ margin-left: 7px;
+ font-size : 14px;
+ color : @regent-gray;
+ width : 37px;
+ height : 37px;
+ line-height: 37px;
+ text-align : center;
+
+ &.active {
+ color: #fff;
+ }
+}
+
+.wrapper {
+ align-items: stretch;
+ height : 100%;
+ width : 100%;
+ position : fixed;
+ top : 0;
+ bottom : 0;
+ left : 0;
+ right : 0;
+}
+
+.doc-panel {
+ display : flex;
+ height : calc(100vh - 60px);
+ flex-direction: row;
+}
+
+.top-panel {
+ display : flex;
+ align-items: center;
+ width : 100%;
+}
+
+.toolbar-panel {
+ background-color: @nav-main-background;
+ width : 100%;
+}
+
+.toolbar-panel-right {
+ display: flex;
+ flex: 1;
+ place-content: flex-end;
+}
+
+.btn-right {
+ margin-right: 7px;
+}
+
+.smp-start-stop {
+ ::ng-deep .button {
+ flex-direction: row;
+ border: 1px solid;
+ border-radius: 5px;
+ padding: 0px 10px !important;
+ }
+}
+
+.language-menu {
+ margin-right: 15px;
+}
+
+.select-language-menu {
+ ::ng-deep .select {
+ width: 100%;
+ ::ng-deep .dropdown-menu {
+ overflow-y: scroll;
+ max-height: 90%;
+ }
+ }
+
+ ::ng-deep .selected-value {
+ max-width: 100%;
+ }
+}
+
+.thumbnails-button {
+ ::ng-deep .button {
+ margin-left: 0 !important;
+ }
+}
+
+
+::ng-deep .tools {
+
+ .button,
+ .selected-value,
+ .nav-caret {
+ color: #fff !important;
+
+ &.inactive {
+ color: @regent-gray !important;
+ }
+ }
+
+ .button {
+ flex-flow: column;
+ }
+
+ .select-left {
+ .select {
+ position: relative;
+ }
+
+ .dropdown-menu {
+ top: 40px;
+ left: 0px;
+ }
+ }
+
+ .select-right {
+ .select {
+ position: relative;
+ }
+
+ .dropdown-menu {
+ top: 40px;
+ right: 0px;
+ }
+ }
+
+ .dropdown-menu .option {
+ color: @dove-gray !important;
+ }
+
+ .dropdown-menu .option:hover {
+ background-color: @folder !important;
+ }
+
+ .icon-button {
+ margin: 0px 0px 0px 15px !important;
+ }
+
+ .select {
+ width : 37px;
+ height : 37px;
+ margin-left: 7px;
+ line-height: 37px;
+ text-align : center;
+ }
+
+ .slides-title {
+ color: white;
+ padding-left: 12px;
+ font-size: 18px;
+ }
+
+ .slides-filename {
+ flex-grow: 1;
+ text-align: center;
+ color: white;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ padding-left: 20px;
+ overflow: hidden;
+ }
+
+ .slides-buttons {
+ display: flex;
+ ::ng-deep .select {
+ color: white;
+ cursor: pointer;
+ }
+ }
+
+ ::ng-deep .gd-nav-search-container
+ .icon-button {
+ margin: 0 0 0 7px !important;
+ }
+}
+
+.slides-nav {
+ position: absolute;
+ right: 30px;
+ bottom: 30px;
+ display: flex;
+ ::ng-deep .button {
+ font-size: 37px;
+ background-color: @porcelain;
+ border-radius: 3px;
+ }
+ ::ng-deep .timer {
+ font-size: 42px;
+ line-height: 6px;
+ color: @regent-gray;
+ position: relative;
+ .seconds-remaining {
+ position: absolute;
+ margin-left: 5px;
+ font-size: 16px;
+ top: 18px;
+ left: 12px;
+ &.two-digits {
+ left: 6px !important;
+ }
+ }
+ }
+}
+
+::ng-deep .page.presentation .gd-wrapper {
+ pointer-events: none;
+}
+
+@media (max-width: 1037px) {
+
+ .mobile-hide,
+ .current-page-number {
+ display: none;
+ }
+
+ ::ng-deep .tools {
+ gd-button:nth-child(1)>.icon-button {
+ margin: 0px 0px 0px 10px !important;
+ }
+
+ .icon-button {
+ height: 60px;
+ width : 60px;
+ }
+
+ .gd-nav-search-btn {
+ .icon-button {
+ height: 37px;
+ width : 37px;
+ }
+
+ .button {
+ font-size: 14px;
+ }
+ }
+
+ .gd-nav-search-container {
+ top: 59px !important;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/ClientApp/src/app/app.component.ts b/Demos/ASP.NET MVC/src/ClientApp/src/app/app.component.ts
new file mode 100644
index 000000000..54b9deeda
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/ClientApp/src/app/app.component.ts
@@ -0,0 +1,122 @@
+import { Component, ChangeDetectorRef } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+
+import { ViewerAppComponent, ViewerService, ViewerConfigService } from '@groupdocs.examples.angular/viewer';
+import { Api, ConfigService, ModalService, UploadFilesService, NavigateService, ZoomService, PagePreloadService, RenderPrintService, PasswordService, WindowService, LoadingMaskService, PageModel, TypedFileCredentials } from '@groupdocs.examples.angular/common-components';
+
+import { TranslateService } from '@ngx-translate/core';
+
+@Component({
+ selector: 'app-root',
+ templateUrl: './app.component.html',
+ styleUrls: ['./app.component.less', './variables.less']
+})
+export class AppComponent extends ViewerAppComponent {
+
+ configService: ConfigService;
+ viewerService: ViewerService;
+ pagesLoading: number[];
+ http: HttpClient;
+
+ constructor(viewerService: ViewerService,
+ modalService: ModalService,
+ viewerConfigService: ViewerConfigService,
+ uploadFilesService: UploadFilesService,
+ navigateService: NavigateService,
+ zoomService: ZoomService,
+ pagePreloadService: PagePreloadService,
+ renderPrintService: RenderPrintService,
+ passwordService: PasswordService,
+ windowService: WindowService,
+ loadingMaskService: LoadingMaskService,
+ http: HttpClient,
+ configService: ConfigService,
+ cdr: ChangeDetectorRef,
+ translate: TranslateService) {
+
+ super(viewerService,
+ modalService,
+ viewerConfigService,
+ uploadFilesService,
+ navigateService,
+ zoomService,
+ pagePreloadService,
+ renderPrintService,
+ passwordService,
+ windowService,
+ loadingMaskService,
+ cdr,
+ translate);
+
+ this.configService = configService;
+ this.viewerService = viewerService;
+ this.pagesLoading = [];
+ this.http = http;
+ }
+
+ preloadPages(start: number, end: number) {
+ const pagesToLoad = [];
+ const isInitialLoad = start === 1;
+ const minPagesToLoad = this.viewerConfig.preloadPageCount;
+ const countPages = this.file.pages.length;
+ this.selectedPageNumber = 1;
+
+ if (isInitialLoad) {
+ this.pagesLoading = [];
+ }
+
+ for (let i = start; i <= end; i++) {
+ const page = this.file.pages.find(p => p.number === i);
+ if(page && page.data) {
+ continue;
+ }
+
+ if (this.pagesLoading.indexOf(i) === -1) {
+ this.pagesLoading.push(i);
+ pagesToLoad.push(i);
+ }
+ }
+
+ if (pagesToLoad.length > 0) {
+ const last = pagesToLoad[pagesToLoad.length - 1];
+ if (!isInitialLoad && pagesToLoad.length < minPagesToLoad) {
+ const addPages = minPagesToLoad - pagesToLoad.length;
+ for (let i = last; i < last + addPages; i++) {
+ const pageNumber = i + 1;
+
+ if (pageNumber <= countPages && this.pagesLoading.indexOf(pageNumber) === -1) {
+ pagesToLoad.push(pageNumber);
+ this.pagesLoading.push(pageNumber);
+ }
+ }
+ }
+
+ this.loadPages(this.credentials, pagesToLoad).subscribe((
+ (pages: any) => {
+ pages.forEach((page: PageModel) => {
+ const pageIndex = page.number - 1;
+ const currPage = this.file.pages[pageIndex];
+
+ if (currPage) {
+ currPage.data = page.data;
+ if (this.file.thumbnails[pageIndex]) {
+ this.file.thumbnails[pageIndex].data = page.data;
+ this.file.thumbnails[pageIndex].width = currPage.width;
+ this.file.thumbnails[pageIndex].height = currPage.height;
+ }
+ }
+ });
+ }
+ ));
+ }
+ }
+
+ loadPages(credentials: TypedFileCredentials, pages: number[]) {
+ return this.http.post(this.configService.getViewerApiEndpoint() + Api.LOAD_DOCUMENT_PAGE + "s", {
+ 'guid': credentials.guid,
+ 'fileType': credentials.fileType,
+ 'password': credentials.password,
+ 'pages': pages
+ }, Api.httpOptionsJson);
+ }
+}
diff --git a/Demos/ASP.NET MVC/src/ClientApp/src/app/app.module.ts b/Demos/ASP.NET MVC/src/ClientApp/src/app/app.module.ts
new file mode 100644
index 000000000..645a436d3
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/ClientApp/src/app/app.module.ts
@@ -0,0 +1,89 @@
+import { NgModule } from '@angular/core';
+import { BrowserModule } from '@angular/platform-browser';
+import { APP_BASE_HREF } from '@angular/common';
+
+import { AppComponent } from './app.component';
+import { ConfigService } from '@groupdocs.examples.angular/common-components';
+import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
+import { ViewerConfigService, ViewerModule } from '@groupdocs.examples.angular/viewer';
+import { BehaviorSubject, Observable } from 'rxjs';
+
+import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
+import { ViewerTranslateLoader } from "@groupdocs.examples.angular/viewer";
+
+declare global {
+ interface Window {
+ apiEndpoint: string;
+ uiSettingsPath: string;
+ }
+}
+
+/*
+export class StaticViewerConfigService {
+ public updatedConfig: Observable = new BehaviorSubject({
+ pageSelector: true,
+ download: true,
+ upload: true,
+ print: true,
+ browse: true,
+ rewrite: true,
+ enableRightClick: true,
+ filesDirectory: "",
+ fontsDirectory: "",
+ defaultDocument: "",
+ watermarkText: "",
+ preloadPageCount: 3,
+ zoom: true,
+ search: true,
+ thumbnails: true,
+ rotate: false,
+ htmlMode: true,
+ cache: true,
+ saveRotateState: false,
+ printAllowed: true,
+ showGridLines: true,
+ showLanguageMenu: true,
+ defaultLanguage: 'en',
+ supportedLanguages: ['en', 'fr', 'de']
+ }).asObservable();
+
+ load(): Promise {
+ return Promise.resolve();
+ }
+}
+*/
+
+export function configServiceFactory() {
+ let config = new ConfigService();
+ config.apiEndpoint = window.apiEndpoint;
+ config.getViewerApiEndpoint = () => window.apiEndpoint;
+ config.getConfigEndpoint = () => window.uiSettingsPath;
+ return config;
+}
+
+@NgModule({
+ declarations: [
+ AppComponent
+ ],
+ imports: [
+ BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),
+ ViewerModule,
+ FontAwesomeModule,
+ TranslateModule.forRoot({
+ loader: {
+ provide: TranslateLoader,
+ useClass: ViewerTranslateLoader
+ }
+ })
+ ],
+ providers: [
+ { provide: APP_BASE_HREF, useValue: '/' },
+ { provide: ConfigService, useFactory: configServiceFactory },
+/*
+ { provide: ViewerConfigService, useClass: StaticViewerConfigService },
+*/
+ { provide: 'WINDOW', useValue: window },
+ ],
+ bootstrap: [AppComponent]
+})
+export class AppModule { }
diff --git a/Demos/ASP.NET MVC/src/ClientApp/src/app/variables.less b/Demos/ASP.NET MVC/src/ClientApp/src/app/variables.less
new file mode 100644
index 000000000..6a8bedf1d
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/ClientApp/src/app/variables.less
@@ -0,0 +1,31 @@
+@wide-screen-down: ~'(max-width: 1199px)';
+@desktop-down: ~'(max-width: 991px)';
+@tablet-down: ~'(max-width: 767px)';
+@phone-down: ~'(max-width: 1037px)';
+@small-phone-down: ~'(max-width: 320px)';
+
+@nav-logo-background: #25c2d4;
+@nav-main-background: #3e4e5a;
+@nav-accent-background: #E5E5E5;
+@nav-tabs-height: 30px;
+@editor-nav-height: 90px;
+@nav-tabs-height-m: 60px;
+@nav-height: 60px;
+@nav-icon-size: 20px;
+
+@primary: #3E4E5A;
+@brand: #25c2d4;
+@silver-chalice: #acacac;
+@dove-gray: #6e6e6e;
+@regent-gray: #959da5;
+@wild-sand: #f4f4f4;
+@mercury: #e7e7e7;
+@silver: #c4c4c4;
+@porcelain: #EDF0F2;
+
+@pdf: #e04e4e;
+@word: #539CF0;
+@powerpoint: #e29e1e;
+@excel: #7cbc46;
+@image: #c375ed;
+@folder: #4b566c;
diff --git a/Demos/MVC/src/DocumentSamples/Viewer/.gitkeep b/Demos/ASP.NET MVC/src/ClientApp/src/assets/.gitkeep
similarity index 100%
rename from Demos/MVC/src/DocumentSamples/Viewer/.gitkeep
rename to Demos/ASP.NET MVC/src/ClientApp/src/assets/.gitkeep
diff --git a/Demos/MVC/src/client/apps/viewer/src/environments/environment.prod.ts b/Demos/ASP.NET MVC/src/ClientApp/src/environments/environment.prod.ts
similarity index 100%
rename from Demos/MVC/src/client/apps/viewer/src/environments/environment.prod.ts
rename to Demos/ASP.NET MVC/src/ClientApp/src/environments/environment.prod.ts
diff --git a/Demos/MVC/src/client/apps/viewer/src/environments/environment.ts b/Demos/ASP.NET MVC/src/ClientApp/src/environments/environment.ts
similarity index 100%
rename from Demos/MVC/src/client/apps/viewer/src/environments/environment.ts
rename to Demos/ASP.NET MVC/src/ClientApp/src/environments/environment.ts
diff --git a/Demos/WinForms/src/GroupDocs.Viewer.WinForms/Resources/application.ico b/Demos/ASP.NET MVC/src/ClientApp/src/favicon.ico
similarity index 100%
rename from Demos/WinForms/src/GroupDocs.Viewer.WinForms/Resources/application.ico
rename to Demos/ASP.NET MVC/src/ClientApp/src/favicon.ico
diff --git a/Demos/ASP.NET MVC/src/ClientApp/src/index.html b/Demos/ASP.NET MVC/src/ClientApp/src/index.html
new file mode 100644
index 000000000..0b3e826b6
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/ClientApp/src/index.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+ GroupDocs.Viewer UI
+
+
+
+
+
+
+
diff --git a/Demos/ASP.NET MVC/src/ClientApp/src/index.prod.html b/Demos/ASP.NET MVC/src/ClientApp/src/index.prod.html
new file mode 100644
index 000000000..e42db0a95
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/ClientApp/src/index.prod.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+ GroupDocs.Viewer UI
+
+
+
+
+
+
+
+
diff --git a/Demos/MVC/src/client/apps/viewer/src/main.ts b/Demos/ASP.NET MVC/src/ClientApp/src/main.ts
similarity index 85%
rename from Demos/MVC/src/client/apps/viewer/src/main.ts
rename to Demos/ASP.NET MVC/src/ClientApp/src/main.ts
index fa4e0aef3..c7b673cf4 100644
--- a/Demos/MVC/src/client/apps/viewer/src/main.ts
+++ b/Demos/ASP.NET MVC/src/ClientApp/src/main.ts
@@ -8,6 +8,5 @@ if (environment.production) {
enableProdMode();
}
-platformBrowserDynamic()
- .bootstrapModule(AppModule)
+platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));
diff --git a/Demos/MVC/src/client/apps/viewer/src/polyfills.ts b/Demos/ASP.NET MVC/src/ClientApp/src/polyfills.ts
similarity index 94%
rename from Demos/MVC/src/client/apps/viewer/src/polyfills.ts
rename to Demos/ASP.NET MVC/src/ClientApp/src/polyfills.ts
index 2f258e56c..d5f67bd91 100644
--- a/Demos/MVC/src/client/apps/viewer/src/polyfills.ts
+++ b/Demos/ASP.NET MVC/src/ClientApp/src/polyfills.ts
@@ -18,7 +18,9 @@
* BROWSER POLYFILLS
*/
-/** IE10 and IE11 requires the following for NgClass support on SVG elements */
+/**
+ * IE11 requires the following for NgClass support on SVG elements
+ */
// import 'classlist.js'; // Run `npm install --save classlist.js`.
/**
@@ -35,7 +37,7 @@
* will put import in the top of bundle, so user need to create a separate file
* in this directory (for example: zone-flags.ts), and put the following flags
* into that file, and then add the following code before importing zone.js.
- * import './zone-flags.ts';
+ * import './zone-flags';
*
* The flags allowed in zone-flags.ts are listed here.
*
@@ -55,7 +57,8 @@
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
-import 'zone.js/dist/zone'; // Included with Angular CLI.
+import 'zone.js/dist/zone'; // Included with Angular CLI.
+
/***************************************************************************************************
* APPLICATION IMPORTS
diff --git a/Demos/MVC/src/client/apps/viewer/src/styles.less b/Demos/ASP.NET MVC/src/ClientApp/src/styles.css
similarity index 100%
rename from Demos/MVC/src/client/apps/viewer/src/styles.less
rename to Demos/ASP.NET MVC/src/ClientApp/src/styles.css
diff --git a/Demos/ASP.NET MVC/src/ClientApp/tsconfig.app.json b/Demos/ASP.NET MVC/src/ClientApp/tsconfig.app.json
new file mode 100644
index 000000000..82d91dc4a
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/ClientApp/tsconfig.app.json
@@ -0,0 +1,15 @@
+/* To learn more about this file see: https://angular.io/config/tsconfig. */
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./out-tsc/app",
+ "types": []
+ },
+ "files": [
+ "src/main.ts",
+ "src/polyfills.ts"
+ ],
+ "include": [
+ "src/**/*.d.ts"
+ ]
+}
diff --git a/Demos/ASP.NET MVC/src/ClientApp/tsconfig.json b/Demos/ASP.NET MVC/src/ClientApp/tsconfig.json
new file mode 100644
index 000000000..94a24fe1e
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/ClientApp/tsconfig.json
@@ -0,0 +1,30 @@
+/* To learn more about this file see: https://angular.io/config/tsconfig. */
+{
+ "compileOnSave": false,
+ "compilerOptions": {
+ "baseUrl": "./",
+ "outDir": "./dist/out-tsc",
+ "forceConsistentCasingInFileNames": true,
+ "strict": true,
+ "noImplicitReturns": true,
+ "noFallthroughCasesInSwitch": true,
+ "sourceMap": true,
+ "declaration": false,
+ "downlevelIteration": true,
+ "experimentalDecorators": true,
+ "moduleResolution": "node",
+ "importHelpers": true,
+ "target": "es2015",
+ "module": "es2020",
+ "lib": [
+ "es2018",
+ "dom"
+ ]
+ },
+ "angularCompilerOptions": {
+ "enableI18nLegacyMessageIdFormat": false,
+ "strictInjectionParameters": true,
+ "strictInputAccessModifiers": true,
+ "strictTemplates": true
+ }
+}
diff --git a/Demos/ASP.NET MVC/src/Controllers/ViewerApiController.cs b/Demos/ASP.NET MVC/src/Controllers/ViewerApiController.cs
new file mode 100644
index 000000000..d0cf3b042
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Controllers/ViewerApiController.cs
@@ -0,0 +1,385 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Threading.Tasks;
+using System.Web;
+using System.Web.Http;
+using System.Web.Http.Cors;
+using GroupDocs.Viewer.AspNetMvc.ActionResults;
+using GroupDocs.Viewer.AspNetMvc.Core;
+using GroupDocs.Viewer.AspNetMvc.Core.Configuration;
+using GroupDocs.Viewer.AspNetMvc.Core.Entities;
+using GroupDocs.Viewer.AspNetMvc.Core.Extensions;
+using GroupDocs.Viewer.AspNetMvc.Core.Utils;
+using GroupDocs.Viewer.AspNetMvc.Models;
+
+namespace GroupDocs.Viewer.AspNetMvc.Controllers
+{
+ [RoutePrefix(Constants.API_PATH)]
+ [EnableCors(origins: "*", headers: "*", methods: "*")]
+ public class ViewerApiController : ApiController
+ {
+ private readonly UIConfig _config;
+ private readonly IFileStorage _fileStorage;
+ private readonly IViewer _viewer;
+
+ public ViewerApiController(
+ UIConfig uiConfig,
+ IFileStorage fileStorage,
+ IViewer viewer)
+ {
+ _config = uiConfig;
+ _fileStorage = fileStorage;
+ _viewer = viewer;
+ }
+
+ [HttpGet]
+ [Route(Constants.LOAD_CONFIG_ACTION_NAME)]
+ public IHttpActionResult LoadConfig()
+ {
+ var config = new LoadConfigResponse
+ {
+ PageSelector = _config.PageSelector,
+ Download = _config.Download,
+ Upload = _config.Upload,
+ Print = _config.Print,
+ Browse = _config.Browse,
+ Rewrite = _config.Rewrite,
+ EnableRightClick = _config.EnableRightClick,
+ DefaultDocument = _config.DefaultDocument,
+ PreloadPageCount = _config.PreloadPageCount,
+ Zoom = _config.Zoom,
+ Search = _config.Search,
+ Thumbnails = _config.Thumbnails,
+ HtmlMode = _config.HtmlMode,
+ PrintAllowed = _config.PrintAllowed,
+ Rotate = _config.Rotate,
+ SaveRotateState = _config.SaveRotateState,
+ DefaultLanguage = _config.DefaultLanguage,
+ SupportedLanguages = _config.SupportedLanguages,
+ ShowLanguageMenu = _config.ShowLanguageMenu
+ };
+
+ return OkJsonResult(config);
+ }
+
+ [HttpPost]
+ [Route(Constants.LOAD_FILE_TREE_ACTION_NAME)]
+ public async Task GetFileTree(LoadFileTreeRequest request)
+ {
+ if (!_config.Browse)
+ return ErrorJsonResult("Browsing files is disabled.");
+
+ try
+ {
+ var files =
+ await _fileStorage.ListDirsAndFilesAsync(request.Path);
+
+ var result = files
+ .Select(entity => new FileDescription(entity.FilePath, entity.FilePath, entity.IsDirectory, entity.Size))
+ .ToList();
+
+ return OkJsonResult(result);
+ }
+ catch (Exception ex)
+ {
+ return ErrorJsonResult(ex.Message);
+ }
+ }
+
+ [HttpGet]
+ [Route(Constants.DOWNLOAD_DOCUMENT_ACTION_NAME)]
+ public async Task DownloadDocument(string path)
+ {
+ if (!_config.Download)
+ return ErrorJsonResult("Downloading files is disabled.");
+
+ try
+ {
+ var fileName = Path.GetFileName(path);
+ var bytes = await _fileStorage.ReadFileAsync(path);
+
+ return FileResult(bytes, fileName);
+ }
+ catch (Exception ex)
+ {
+ return ErrorJsonResult(ex.Message);
+ }
+ }
+
+ [HttpGet]
+ [Route(Constants.LOAD_DOCUMENT_PAGE_RESOURCE_ACTION_NAME)]
+ public async Task LoadDocumentPageResource(
+ [FromUri]string guid, [FromUri] int pageNumber, [FromUri] string resourceName)
+ {
+ if (!_config.HtmlMode)
+ return ErrorJsonResult("Loading page resources is disabled in image mode.");
+
+ try
+ {
+ var fileCredentials =
+ new FileCredentials(guid, "", "");
+ var bytes =
+ await _viewer.GetPageResourceAsync(fileCredentials, pageNumber, resourceName);
+
+ if (bytes.Length == 0)
+ return NotFoundJsonResult($"Resource {resourceName} was not found");
+
+ var contentType = resourceName.ContentTypeFromFileName();
+
+ return ResourceFileResult(bytes, contentType);
+ }
+ catch (Exception ex)
+ {
+ return ErrorJsonResult(ex.Message);
+ }
+ }
+
+ [HttpPost]
+ [Route(Constants.UPLOAD_DOCUMENT_ACTION_NAME)]
+ public async Task UploadDocument()
+ {
+ if (!_config.Upload)
+ return ErrorJsonResult("Uploading files is disabled.");
+
+ try
+ {
+ var url = HttpContext.Current.Request.Form["url"];
+ var (fileName, bytes) = await ReadOrDownloadFile(url);
+ bool.TryParse(HttpContext.Current.Request.Form["rewrite"], out var rewrite);
+
+ var filePath = await _fileStorage.WriteFileAsync(fileName, bytes, rewrite);
+ var result = new UploadFileResponse(filePath);
+
+ return OkJsonResult(result);
+ }
+ catch (Exception ex)
+ {
+ return ErrorJsonResult(ex.Message);
+ }
+ }
+
+ [HttpPost]
+ [Route(Constants.PRINT_PDF_ACTION_NAME)]
+ public async Task PrintPdf([FromBody] PrintPdfRequest request)
+ {
+ if (!_config.Print)
+ return ErrorJsonResult("Printing files is disabled.");
+
+ try
+ {
+ var fileCredentials =
+ new FileCredentials(request.Guid, request.FileType, request.Password);
+
+ var filename = Path.GetFileName(request.Guid);
+ var pdfFileName = Path.ChangeExtension(filename, ".pdf");
+ var pdfFileBytes = await _viewer.GetPdfAsync(fileCredentials);
+
+ return FileResult(pdfFileBytes, pdfFileName, "application/pdf");
+ }
+ catch (Exception ex)
+ {
+ if (ex.Message.Contains("password"))
+ {
+ var message = string.IsNullOrEmpty(request.Password)
+ ? "Password Required"
+ : "Incorrect Password";
+
+ return ForbiddenJsonResult(message);
+ }
+
+ return ErrorJsonResult(ex.Message);
+ }
+ }
+
+ [HttpPost]
+ [Route(Constants.LOAD_DOCUMENT_DESCRIPTION_ACTION_NAME)]
+ public async Task LoadDocumentDescription([FromBody] LoadDocumentDescriptionRequest request)
+ {
+ try
+ {
+ var fileCredentials =
+ new FileCredentials(request.Guid, request.FileType, request.Password);
+ var documentDescription =
+ await _viewer.GetDocumentInfoAsync(fileCredentials);
+
+ var pageNumbers = GetPageNumbers(documentDescription.Pages.Count());
+ var pagesData = await _viewer.GetPagesAsync(fileCredentials, pageNumbers);
+
+ var pages = new List();
+ foreach (PageInfo pageInfo in documentDescription.Pages)
+ {
+ var pageData = pagesData.FirstOrDefault(p => p.PageNumber == pageInfo.Number);
+ var pageDescription = new PageDescription
+ {
+ Width = pageInfo.Width,
+ Height = pageInfo.Height,
+ Number = pageInfo.Number,
+ SheetName = pageInfo.Name,
+ Data = pageData?.GetContent()
+ };
+
+ pages.Add(pageDescription);
+ }
+
+ var result = new LoadDocumentDescriptionResponse
+ {
+ Guid = request.Guid,
+ FileType = documentDescription.FileType,
+ PrintAllowed = documentDescription.PrintAllowed,
+ Pages = pages
+ };
+
+ return OkJsonResult(result);
+ }
+ catch (Exception ex)
+ {
+ if (ex.Message.Contains("password"))
+ {
+ var message = string.IsNullOrEmpty(request.Password)
+ ? "Password Required"
+ : "Incorrect Password";
+
+ return ForbiddenJsonResult(message);
+ }
+
+ return ErrorJsonResult(ex.Message);
+ }
+ }
+
+ private int[] GetPageNumbers(int totalPageCount)
+ {
+ if (_config.PreloadPageCount == 0)
+ return Enumerable.Range(1, totalPageCount).ToArray();
+
+ var pageCount =
+ Math.Min(totalPageCount, _config.PreloadPageCount);
+
+ return Enumerable.Range(1, pageCount).ToArray();
+ }
+
+ [HttpPost]
+ [Route(Constants.LOAD_DOCUMENT_PAGES_ACTION_NAME)]
+ public async Task LoadDocumentPages([FromBody] LoadDocumentPagesRequest request)
+ {
+ try
+ {
+ var fileCredentials =
+ new FileCredentials(request.Guid, request.FileType, request.Password);
+ var pages = await _viewer.GetPagesAsync(fileCredentials, request.Pages);
+ var pageContents = pages
+ .Select(page => new PageContent { Number = page.PageNumber, Data = page.GetContent() })
+ .ToList();
+
+ return OkJsonResult(pageContents);
+ }
+ catch (Exception ex)
+ {
+ if (ex.Message.Contains("password"))
+ {
+ var message = string.IsNullOrEmpty(request.Password)
+ ? "Password Required"
+ : "Incorrect Password";
+
+ return ForbiddenJsonResult(message);
+ }
+
+ return ErrorJsonResult(ex.Message);
+ }
+ }
+
+ [HttpPost]
+ [Route(Constants.LOAD_DOCUMENT_PAGE_ACTION_NAME)]
+ public async Task LoadDocumentPage([FromBody] LoadDocumentPageRequest request)
+ {
+ try
+ {
+ var fileCredentials =
+ new FileCredentials(request.Guid, request.FileType, request.Password);
+ var page = await _viewer.GetPageAsync(fileCredentials, request.Page);
+ var pageContent = new PageContent { Number = page.PageNumber, Data = page.GetContent() };
+
+ return OkJsonResult(pageContent);
+ }
+ catch (Exception ex)
+ {
+ if (ex.Message.Contains("password"))
+ {
+ var message = string.IsNullOrEmpty(request.Password)
+ ? "Password Required"
+ : "Incorrect Password";
+
+ return ForbiddenJsonResult(message);
+ }
+
+ return ErrorJsonResult(ex.Message);
+ }
+ }
+
+ private Task<(string, byte[])> ReadOrDownloadFile(string url)
+ {
+ return string.IsNullOrEmpty(url)
+ ? ReadFileFromRequest()
+ : DownloadFileAsync(url);
+ }
+
+ private async Task<(string, byte[])> ReadFileFromRequest()
+ {
+ var provider = await Request.Content.ReadAsMultipartAsync();
+ var firstFile = provider.Contents.First();
+
+ var bytes = await firstFile.ReadAsByteArrayAsync();
+ var fileName = PathUtils.RemoveInvalidFileNameChars(
+ firstFile.Headers.ContentDisposition.FileName);
+
+ return (fileName, bytes);
+ }
+
+ private async Task<(string, byte[])> DownloadFileAsync(string url)
+ {
+ using (HttpClient httpClient = new HttpClient())
+ {
+ httpClient.DefaultRequestHeaders.Add("User-Agent", "Other");
+
+ Uri uri = new Uri(url);
+ string fileName = Path.GetFileName(uri.LocalPath);
+ byte[] bytes = await httpClient.GetByteArrayAsync(uri);
+
+ return (fileName, bytes);
+ }
+ }
+
+ private IHttpActionResult ErrorJsonResult(string message) =>
+ new JsonActionResult(new ErrorResponse(message), Request)
+ {
+ StatusCode = HttpStatusCode.InternalServerError
+ };
+
+ private IHttpActionResult ForbiddenJsonResult(string message) =>
+ new JsonActionResult(new ErrorResponse(message), Request)
+ {
+ StatusCode = HttpStatusCode.Forbidden
+ };
+
+ private IHttpActionResult NotFoundJsonResult(string message) =>
+ new JsonActionResult(new ErrorResponse(message), Request)
+ {
+ StatusCode = HttpStatusCode.NotFound
+ };
+
+ private IHttpActionResult OkJsonResult(object result) =>
+ new JsonActionResult(result, Request);
+
+ private IHttpActionResult FileResult(byte[] data, string fileName) =>
+ new FileActionResult(data, fileName, "application/octet-stream", Request);
+
+ private IHttpActionResult FileResult(byte[] data, string fileName, string contentType) =>
+ new FileActionResult(data, fileName, contentType, Request);
+
+ private IHttpActionResult ResourceFileResult(byte[] data, string contentType) =>
+ new ResourceActionResult(data, contentType, Request);
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Controllers/ViewerController.cs b/Demos/ASP.NET MVC/src/Controllers/ViewerController.cs
new file mode 100644
index 000000000..fea470e07
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Controllers/ViewerController.cs
@@ -0,0 +1,12 @@
+using System.Web.Mvc;
+
+namespace GroupDocs.Viewer.AspNetMvc.Controllers
+{
+ public class ViewerController : Controller
+ {
+ public ActionResult Index()
+ {
+ return View();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Core/Caching/AsyncDuplicateLock.cs b/Demos/ASP.NET MVC/src/Core/Caching/AsyncDuplicateLock.cs
new file mode 100644
index 000000000..ffae062d6
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Core/Caching/AsyncDuplicateLock.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace GroupDocs.Viewer.AspNetMvc.Core.Caching
+{
+ public sealed class AsyncDuplicateLock : IAsyncLock
+ {
+ private sealed class RefCounted
+ {
+ public RefCounted(T value)
+ {
+ RefCount = 1;
+ Value = value;
+ }
+
+ public int RefCount { get; set; }
+ public T Value { get; private set; }
+ }
+
+ private static readonly Dictionary> SemaphoreSlims
+ = new Dictionary>();
+
+ private SemaphoreSlim GetOrCreate(object key)
+ {
+ RefCounted item;
+ lock (SemaphoreSlims)
+ {
+ if (SemaphoreSlims.TryGetValue(key, out item))
+ {
+ ++item.RefCount;
+ }
+ else
+ {
+ item = new RefCounted(new SemaphoreSlim(1, 1));
+ SemaphoreSlims[key] = item;
+ }
+ }
+ return item.Value;
+ }
+
+ public IDisposable Lock(object key)
+ {
+ GetOrCreate(key).Wait();
+ return new Releaser { Key = key };
+ }
+
+ public async Task LockAsync(object key)
+ {
+ await GetOrCreate(key).WaitAsync().ConfigureAwait(false);
+ return new Releaser { Key = key };
+ }
+
+ private sealed class Releaser : IDisposable
+ {
+ public object Key { get; set; }
+
+ public void Dispose()
+ {
+ RefCounted item;
+ lock (SemaphoreSlims)
+ {
+ item = SemaphoreSlims[Key];
+ --item.RefCount;
+ if (item.RefCount == 0)
+ SemaphoreSlims.Remove(Key);
+ }
+ item.Value.Release();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Core/Caching/CacheKeys.cs b/Demos/ASP.NET MVC/src/Core/Caching/CacheKeys.cs
new file mode 100644
index 000000000..e67263d97
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Core/Caching/CacheKeys.cs
@@ -0,0 +1,14 @@
+namespace GroupDocs.Viewer.AspNetMvc.Core.Caching
+{
+ public static class CacheKeys
+ {
+ public const string FILE_INFO_CACHE_KEY = "info.json";
+ public const string PDF_FILE_CACHE_KEY = "file.pdf";
+
+ public static string GetHtmlPageResourceCacheKey(int pageNumber, string resourceName)
+ => $"p{pageNumber}_{resourceName}";
+
+ public static string GetPageCacheKey(int pageNumber, string extension) =>
+ $"p{pageNumber}{extension}";
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Core/Caching/CachedPage.cs b/Demos/ASP.NET MVC/src/Core/Caching/CachedPage.cs
new file mode 100644
index 000000000..142ef385b
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Core/Caching/CachedPage.cs
@@ -0,0 +1,21 @@
+namespace GroupDocs.Viewer.AspNetMvc.Core.Caching
+{
+ internal class CachedPage
+ {
+ ///
+ /// The page number.
+ ///
+ public int PageNumber { get; }
+
+ ///
+ /// The data. Can be null.
+ ///
+ public byte[] Data { get; }
+
+ public CachedPage(int pageNumber, byte[] data)
+ {
+ PageNumber = pageNumber;
+ Data = data;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Core/Caching/CachingViewer.cs b/Demos/ASP.NET MVC/src/Core/Caching/CachingViewer.cs
new file mode 100644
index 000000000..e489decce
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Core/Caching/CachingViewer.cs
@@ -0,0 +1,204 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using GroupDocs.Viewer.AspNetMvc.Core.Entities;
+using GroupDocs.Viewer.AspNetMvc.Core.Extensions;
+
+namespace GroupDocs.Viewer.AspNetMvc.Core.Caching
+{
+ public class CachingViewer : IViewer
+ {
+ private readonly IViewer _viewer;
+ private readonly IFileCache _fileCache;
+ private readonly IAsyncLock _asyncLock;
+
+ public string PageExtension =>
+ _viewer.PageExtension;
+
+ public Page CreatePage(int pageNumber, byte[] data) =>
+ _viewer.CreatePage(pageNumber, data);
+
+ public CachingViewer(IViewer viewer, IFileCache fileCache, IAsyncLock asyncLock)
+ {
+ _viewer = viewer;
+ _fileCache = fileCache;
+ _asyncLock = asyncLock;
+ }
+
+ public async Task GetPagesAsync(FileCredentials fileCredentials, int[] pageNumbers)
+ {
+ var pagesOrNulls = GetPagesOrNullsFromCache(fileCredentials.FilePath, pageNumbers);
+ var missingPageNumbers = GetMissingPageNumbers(pagesOrNulls);
+
+ if (missingPageNumbers.Length == 0)
+ return ToPages(pagesOrNulls);
+
+ var createdPages = await CreatePages(fileCredentials, missingPageNumbers);
+
+ var pages = Combine(pagesOrNulls, createdPages);
+
+ return pages;
+ }
+
+ public async Task GetPageAsync(FileCredentials fileCredentials, int pageNumber)
+ {
+ var cacheKey = CacheKeys.GetPageCacheKey(pageNumber, PageExtension);
+ var bytes = await _fileCache.GetValueAsync(cacheKey, fileCredentials.FilePath, async () =>
+ {
+ using (await _asyncLock.LockAsync(fileCredentials.FilePath))
+ {
+ return await _fileCache.GetValueAsync(cacheKey, fileCredentials.FilePath, async () =>
+ {
+ var newPage = await _viewer.GetPageAsync(fileCredentials, pageNumber);
+
+ await SaveResourcesAsync(fileCredentials.FilePath, newPage.PageNumber, newPage.Resources);
+
+ return newPage.Data;
+ });
+ }
+ });
+
+ var page = CreatePage(pageNumber, bytes);
+ return page;
+ }
+
+ public Task GetDocumentInfoAsync(FileCredentials fileCredentials)
+ {
+ var cacheKey = CacheKeys.FILE_INFO_CACHE_KEY;
+ return _fileCache.GetValueAsync(cacheKey, fileCredentials.FilePath, async () =>
+ {
+ using (await _asyncLock.LockAsync(fileCredentials.FilePath))
+ {
+ return await _fileCache.GetValueAsync(cacheKey, fileCredentials.FilePath, () =>
+ _viewer.GetDocumentInfoAsync(fileCredentials));
+ }
+ });
+ }
+
+ public Task GetPdfAsync(FileCredentials fileCredentials)
+ {
+ var cacheKey = CacheKeys.PDF_FILE_CACHE_KEY;
+ return _fileCache.GetValueAsync(cacheKey, fileCredentials.FilePath, async () =>
+ {
+ using (await _asyncLock.LockAsync(fileCredentials.FilePath))
+ {
+ return await _fileCache.GetValueAsync(cacheKey, fileCredentials.FilePath, () =>
+ _viewer.GetPdfAsync(fileCredentials));
+ }
+ });
+ }
+
+ public Task GetPageResourceAsync(FileCredentials fileCredentials, int pageNumber,
+ string resourceName)
+ {
+ var cacheKey = CacheKeys.GetHtmlPageResourceCacheKey(pageNumber, resourceName);
+ return _fileCache.GetValueAsync(cacheKey, fileCredentials.FilePath,
+ async () =>
+ {
+ using (await _asyncLock.LockAsync(fileCredentials.FilePath))
+ {
+ return await _fileCache.GetValueAsync(cacheKey, fileCredentials.FilePath, () =>
+ _viewer.GetPageResourceAsync(fileCredentials, pageNumber, resourceName));
+ }
+ });
+ }
+
+ private async Task SaveResourcesAsync(string filePath, int pageNumber, IEnumerable pageResources)
+ {
+ var tasks = pageResources.Select(resource =>
+ {
+ var resourceCacheKey =
+ CacheKeys.GetHtmlPageResourceCacheKey(pageNumber, resource.ResourceName);
+
+ return _fileCache.SetAsync(resourceCacheKey, filePath, resource.Data);
+ });
+
+ await Task.WhenAll(tasks);
+ }
+
+ private async Task CreatePages(FileCredentials fileCredentials, int[] pageNumbers)
+ {
+ using (await _asyncLock.LockAsync(fileCredentials.FilePath))
+ {
+ var pagesOrNulls = GetPagesOrNullsFromCache(fileCredentials.FilePath, pageNumbers);
+ var missingPageNumbers = GetMissingPageNumbers(pagesOrNulls);
+
+ if (missingPageNumbers.Length == 0)
+ return ToPages(pagesOrNulls);
+
+ var createdPages = await _viewer.GetPagesAsync(fileCredentials, missingPageNumbers);
+
+ await SaveToCache(fileCredentials.FilePath, createdPages);
+
+ var pages = Combine(pagesOrNulls, createdPages);
+
+ return pages;
+ }
+ }
+
+ private Pages Combine(List dst, Pages missing)
+ {
+ var pages = dst
+ .Select(pageOrNull =>
+ {
+ if (pageOrNull.Data == null)
+ {
+ var page = missing
+ .Where(p => p.PageNumber == pageOrNull.PageNumber)
+ .Select(p => p)
+ .FirstOrDefault();
+
+ return page;
+ }
+
+ return CreatePage(pageOrNull.PageNumber, pageOrNull.Data);
+ }).ToList();
+
+ return new Pages(pages);
+ }
+
+ private Task SaveToCache(string filePath, Pages createdPages)
+ {
+ var tasks = createdPages
+ .SelectMany(page =>
+ {
+ var cacheKey = CacheKeys.GetPageCacheKey(page.PageNumber, _viewer.PageExtension);
+
+ var savePageTask = _fileCache.SetAsync(cacheKey, filePath, page.Data);
+ var saveResourcesTask = SaveResourcesAsync(filePath, page.PageNumber, page.Resources);
+
+ return new[] {savePageTask, saveResourcesTask};
+ });
+
+ return Task.WhenAll(tasks);
+ }
+
+ private Pages ToPages(List pagesOrNulls)
+ {
+ var pages = pagesOrNulls
+ .Select(p => CreatePage(p.PageNumber, p.Data))
+ .ToList();
+
+ var result = new Pages(pages);
+ return result;
+ }
+
+ private int[] GetMissingPageNumbers(List pagesOrNulls)
+ {
+ return pagesOrNulls
+ .Where(p => p.Data == null)
+ .Select(p => p.PageNumber)
+ .ToArray();
+ }
+
+ private List GetPagesOrNullsFromCache(string filePath, int[] pageNumbers)
+ {
+ return pageNumbers
+ .Select(pageNumber =>
+ (pageNumber, cacheKey: CacheKeys.GetPageCacheKey(pageNumber, PageExtension)))
+ .Select(page =>
+ new CachedPage(page.pageNumber, _fileCache.TryGetValue(page.cacheKey, filePath)))
+ .ToList();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Core/Caching/LocalFileCache.cs b/Demos/ASP.NET MVC/src/Core/Caching/LocalFileCache.cs
new file mode 100644
index 000000000..2ca1a9495
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Core/Caching/LocalFileCache.cs
@@ -0,0 +1,277 @@
+using System;
+using System.IO;
+using System.Runtime.Serialization;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Newtonsoft.Json;
+
+namespace GroupDocs.Viewer.AspNetMvc.Core.Caching
+{
+ public class LocalFileCache : IFileCache
+ {
+ ///
+ /// The Relative or absolute path to the cache folder.
+ ///
+ private string CachePath { get; }
+
+ private readonly TimeSpan _waitTimeout = TimeSpan.FromMilliseconds(100);
+
+ ///
+ /// Creates new instance of class.
+ ///
+ /// Relative or absolute path where document cache will be stored.
+ /// Thrown when is null.
+ public LocalFileCache(string cachePath)
+ {
+ if (cachePath == null)
+ throw new ArgumentNullException(nameof(cachePath));
+
+ CachePath = cachePath;
+ }
+
+ ///
+ /// Deserializes data associated with this key if present.
+ ///
+ /// A key identifying the requested entry.
+ /// The relative or absolute filepath.
+ /// True
if the key was found.
+ public TEntry TryGetValue(string cacheKey, string filePath)
+ {
+ string cacheFilePath = GetCacheFilePath(cacheKey, filePath);
+
+ if (File.Exists(cacheFilePath))
+ {
+ if (typeof(TEntry) == typeof(byte[]))
+ return (TEntry)ReadBytes(cacheFilePath);
+
+ if (typeof(TEntry) == typeof(Stream))
+ return (TEntry)ReadStream(cacheFilePath);
+
+ return Deserialize(cacheFilePath);
+ }
+
+ return default(TEntry);
+ }
+
+ ///
+ /// Deserializes data associated with this key if present.
+ ///
+ /// A key identifying the requested entry.
+ /// The relative or absolute filepath.
+ /// True
if the key was found.
+ public async Task TryGetValueAsync(string cacheKey, string filePath)
+ {
+ string cacheFilePath = GetCacheFilePath(cacheKey, filePath);
+
+ if (File.Exists(cacheFilePath))
+ {
+ if (typeof(TEntry) == typeof(byte[]))
+ return (TEntry)ReadBytes(cacheFilePath);
+
+ if (typeof(TEntry) == typeof(Stream))
+ return (TEntry)ReadStream(cacheFilePath);
+
+ return await DeserializeAsync(cacheFilePath);
+ }
+
+ return default(TEntry);
+ }
+
+ ///
+ /// Serializes data to the local disk.
+ ///
+ /// An unique identifier for the cache entry.
+ /// The relative or absolute filepath.
+ /// The object to serialize.
+ public void Set(string cacheKey, string filePath, TEntry value)
+ {
+ if (value == null)
+ return;
+
+ string cacheFilePath = GetCacheFilePath(cacheKey, filePath);
+
+ if (value is byte[] data)
+ {
+ using (FileStream dst = GetStream(cacheFilePath, FileMode.Create, FileAccess.Write, FileShare.None))
+ {
+ dst.Write(data, 0, data.Length);
+ }
+ }
+ else if (value is Stream src)
+ {
+ using (FileStream dst = GetStream(cacheFilePath, FileMode.Create, FileAccess.Write, FileShare.None))
+ {
+ if (src.CanSeek)
+ src.Position = 0;
+ src.CopyTo(dst);
+ }
+ }
+ else
+ {
+ var json = JsonConvert.SerializeObject(value, Formatting.Indented);
+ var bytes = Encoding.UTF8.GetBytes(json);
+
+ using (FileStream stream = GetStream(cacheFilePath, FileMode.Create, FileAccess.Write, FileShare.None))
+ {
+ stream.Write(bytes, 0, bytes.Length);
+ }
+ }
+ }
+
+ ///
+ /// Serializes data to the local disk.
+ ///
+ /// An unique identifier for the cache entry.
+ /// The relative or absolute filepath.
+ /// The object to serialize.
+ public async Task SetAsync(string cacheKey, string filePath, TEntry value)
+ {
+ if (value == null)
+ return;
+
+ string cacheFilePath = GetCacheFilePath(cacheKey, filePath);
+
+ if (value is byte[] data)
+ {
+ using (FileStream dst = GetStream(cacheFilePath, FileMode.Create, FileAccess.Write, FileShare.None))
+ {
+ await dst.WriteAsync(data, 0, data.Length);
+ }
+ }
+ else if (value is Stream src)
+ {
+ using (FileStream dst = GetStream(cacheFilePath, FileMode.Create, FileAccess.Write, FileShare.None))
+ {
+ if (src.CanSeek)
+ src.Position = 0;
+
+ await src.CopyToAsync(dst);
+ }
+ }
+ else
+ {
+ var json = JsonConvert.SerializeObject(value, Formatting.Indented);
+ var bytes = Encoding.UTF8.GetBytes(json);
+
+ using (FileStream stream = GetStream(cacheFilePath, FileMode.Create, FileAccess.Write, FileShare.None))
+ {
+ await stream.WriteAsync(bytes, 0, bytes.Length);
+ }
+ }
+ }
+
+ private object ReadStream(string cacheFilePath)
+ => GetStream(cacheFilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
+
+ private object ReadBytes(string cacheFilePath)
+ => GetBytes(cacheFilePath);
+
+ private TEntry Deserialize(string cachePath)
+ {
+ object data;
+ try
+ {
+ var bytes = GetBytes(cachePath);
+ var json = Encoding.UTF8.GetString(bytes);
+
+ data = JsonConvert.DeserializeObject(json);
+ }
+ catch (SerializationException)
+ {
+ data = default(TEntry);
+ }
+
+ return (TEntry)data;
+ }
+
+ private async Task DeserializeAsync(string cachePath)
+ {
+ object data;
+ try
+ {
+ using (var stream = GetStream(cachePath, FileMode.Open, FileAccess.Read, FileShare.Read))
+ {
+ var memory = new MemoryStream();
+ await stream.CopyToAsync(memory);
+ var json = Encoding.UTF8.GetString(memory.ToArray());
+
+ data = JsonConvert.DeserializeObject(json);
+ }
+ }
+ catch (SerializationException)
+ {
+ data = default(TEntry);
+ }
+
+ return (TEntry)data;
+ }
+
+ private string GetCacheFilePath(string cacheKey, string filePath)
+ {
+ string cacheSubFolder = string.Join("_", filePath.Split(Path.GetInvalidPathChars()))
+ .Replace(".", "_");
+ string cacheDirPath = Path.Combine(CachePath, cacheSubFolder);
+ string cacheFilePath = Path.Combine(cacheDirPath, cacheKey);
+
+ if (!Directory.Exists(cacheDirPath))
+ Directory.CreateDirectory(cacheDirPath);
+
+ return cacheFilePath;
+ }
+
+ private FileStream GetStream(string path, FileMode mode, FileAccess access, FileShare share)
+ {
+ FileStream stream = null;
+ TimeSpan interval = new TimeSpan(0, 0, 0, 0, 50);
+ TimeSpan totalTime = new TimeSpan();
+
+ while (stream == null)
+ {
+ try
+ {
+ stream = File.Open(path, mode, access, share);
+ }
+ catch (IOException)
+ {
+ Thread.Sleep(interval);
+ totalTime += interval;
+
+ if (_waitTimeout.Ticks != 0 && totalTime > _waitTimeout)
+ {
+ throw;
+ }
+ }
+ }
+
+ return stream;
+ }
+
+ private byte[] GetBytes(string path)
+ {
+ byte[] bytes = null;
+ TimeSpan interval = new TimeSpan(0, 0, 0, 0, 50);
+ TimeSpan totalTime = new TimeSpan();
+
+ while (bytes == null)
+ {
+ try
+ {
+ bytes = File.ReadAllBytes(path);
+ }
+ catch (IOException)
+ {
+ Thread.Sleep(interval);
+ totalTime += interval;
+
+ if (_waitTimeout.Ticks != 0 && totalTime > _waitTimeout)
+ {
+ throw;
+ }
+ }
+ }
+
+ return bytes;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Core/Caching/NoopFileCache.cs b/Demos/ASP.NET MVC/src/Core/Caching/NoopFileCache.cs
new file mode 100644
index 000000000..1a8817d66
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Core/Caching/NoopFileCache.cs
@@ -0,0 +1,18 @@
+using System.Threading.Tasks;
+
+namespace GroupDocs.Viewer.AspNetMvc.Core.Caching
+{
+ internal class NoopFileCache : IFileCache
+ {
+ public TEntry TryGetValue(string cacheKey, string filePath) =>
+ default(TEntry);
+
+ public Task TryGetValueAsync(string cacheKey, string filePath) =>
+ Task.FromResult(default(TEntry));
+
+ public void Set(string cacheKey, string filePath, TEntry entry) { }
+
+ public Task SetAsync(string cacheKey, string filePath, TEntry entry) =>
+ Task.CompletedTask;
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Core/Configuration/Language.cs b/Demos/ASP.NET MVC/src/Core/Configuration/Language.cs
new file mode 100644
index 000000000..caf05c40e
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Core/Configuration/Language.cs
@@ -0,0 +1,43 @@
+namespace GroupDocs.Viewer.AspNetMvc.Core.Configuration
+{
+ public class Language
+ {
+ public static readonly Language Arabic = new Language("ar");
+ public static readonly Language Catalan = new Language("ca");
+ public static readonly Language Czech = new Language("cs");
+ public static readonly Language Danish = new Language("da");
+ public static readonly Language German = new Language("de");
+ public static readonly Language Greek = new Language("el");
+ public static readonly Language English = new Language("en");
+ public static readonly Language Spanish = new Language("es");
+ public static readonly Language Filipino = new Language("fil");
+ public static readonly Language French = new Language("fr");
+ public static readonly Language Hebrew = new Language("he");
+ public static readonly Language Hindi = new Language("hi");
+ public static readonly Language Indonesian = new Language("id");
+ public static readonly Language Italian = new Language("it");
+ public static readonly Language Japanese = new Language("ja");
+ public static readonly Language Kazakh = new Language("kk");
+ public static readonly Language Korean = new Language("ko");
+ public static readonly Language Malay = new Language("ms");
+ public static readonly Language Dutch = new Language("nl");
+ public static readonly Language Polish = new Language("pl");
+ public static readonly Language Portuguese = new Language("pt");
+ public static readonly Language Romanian = new Language("ro");
+ public static readonly Language Russian = new Language("ru");
+ public static readonly Language Swedish = new Language("sv");
+ public static readonly Language Vietnamese = new Language("vi");
+ public static readonly Language Thai = new Language("th");
+ public static readonly Language Turkish = new Language("tr");
+ public static readonly Language Ukrainian = new Language("uk");
+ public static readonly Language ChineseSimplified = new Language("zh-hans");
+ public static readonly Language ChineseTraditional = new Language("zh-hant");
+
+ public string Code { get; }
+
+ public Language(string code)
+ {
+ Code = code;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Core/Configuration/UIConfig.cs b/Demos/ASP.NET MVC/src/Core/Configuration/UIConfig.cs
new file mode 100644
index 000000000..948c5fe18
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Core/Configuration/UIConfig.cs
@@ -0,0 +1,238 @@
+using System.Linq;
+
+namespace GroupDocs.Viewer.AspNetMvc.Core.Configuration
+{
+ public class UIConfig
+ {
+ public static readonly UIConfig Instance = new UIConfig();
+
+ internal string DefaultDocument { get; private set; } = string.Empty;
+ internal int PreloadPageCount { get; private set; } = 3;
+ internal bool PageSelector { get; private set; } = true;
+ internal bool Thumbnails { get; private set; } = true;
+ internal bool Zoom { get; private set; } = true;
+ internal bool Search { get; private set; } = true;
+ internal bool EnableRightClick { get; private set; } = true;
+ internal bool Download { get; private set; } = true;
+ internal bool Upload { get; private set; } = true;
+ internal bool Rewrite { get; private set; } = false;
+ internal bool Print { get; private set; } = true;
+ internal bool Browse { get; private set; } = true;
+ internal bool PrintAllowed { get; private set; } = true;
+ internal bool HtmlMode { get; private set; } = true;
+ internal bool ShowLanguageMenu { get; private set; } = true;
+ internal string DefaultLanguage { get; private set; } = "en";
+ internal string[] SupportedLanguages { get; private set; } = new string[]
+ {
+ "ar", // ar - العربية
+ "ca", // ca-ES - Català
+ "cs", // cs-CZ - Čeština
+ "da", // da-DK - Dansk
+ "de", // de-DE - Deutsch
+ "el", // el-GR - Ελληνικά
+ "en", // en-US - English
+ "es", // es-ES - Español
+ "fil", // fil-PH - Filipino
+ "fr", // fr-FR - Français
+ "he", // he-IL - עברית
+ "hi", // hi-IN - हिन्दी
+ "id", // id-ID - Indonesia
+ "it", // it-IT - Italiano
+ "ja", // ja-JP - 日本語
+ "kk", // kk-KZ - Қазақ Тілі
+ "ko", // ko-KR - 한국어
+ "ms", // ms-MY - Melayu
+ "nl", // nl-NL - Nederlands
+ "pl", // pl-PL - Polski
+ "pt", // pt-PT - Português
+ "ro", // ro-RO - Română
+ "ru", // ru-RU - Русский
+ "sv", // sv-SE - Svenska
+ "vi", // vi-VN - Tiếng Việt
+ "th", // th-TH - ไทย
+ "tr", // tr-TR - Türkçe
+ "uk", // uk-UA - Українська
+ "zh-hans", // zh-Hans - 中文(简体)
+ "zh-hant", // zh-Hant" - 中文(繁體)
+ };
+
+ internal bool Rotate { get; private set; } = false;
+ internal bool SaveRotateState { get; private set; } = false;
+ internal ViewerType ViewerType { get; private set; }
+
+ public UIConfig SetViewerType(ViewerType viewerType)
+ {
+ ViewerType = viewerType;
+ HtmlMode = viewerType == ViewerType.HtmlWithExternalResources ||
+ viewerType == ViewerType.HtmlWithEmbeddedResources;
+ return this;
+ }
+
+ public UIConfig SetPreloadPageCount(int countPages)
+ {
+ PreloadPageCount = countPages;
+ return this;
+ }
+
+ public UIConfig SetDefaultDocument(string filePath)
+ {
+ DefaultDocument = filePath;
+ return this;
+ }
+
+ public UIConfig HidePageSelectorControl()
+ {
+ PageSelector = false;
+ return this;
+ }
+
+ public UIConfig HideThumbnailsControl()
+ {
+ Thumbnails = false;
+ return this;
+ }
+
+ public UIConfig DisableFileDownload()
+ {
+ Download = false;
+ return this;
+ }
+
+ public UIConfig DisableFileUpload()
+ {
+ Upload = false;
+ return this;
+ }
+
+ public UIConfig RewriteFilesOnUpload()
+ {
+ Rewrite = true;
+ return this;
+ }
+
+ public UIConfig DisablePrint()
+ {
+ Print = false;
+ PrintAllowed = false;
+ return this;
+ }
+
+ public UIConfig DisableFileBrowsing()
+ {
+ Browse = false;
+ return this;
+ }
+
+ public UIConfig HideZoomButton()
+ {
+ Zoom = false;
+ return this;
+ }
+
+ public UIConfig HideSearchControl()
+ {
+ Search = false;
+ return this;
+ }
+
+ public UIConfig HidePageRotationControl()
+ {
+ Rotate = false;
+ return this;
+ }
+
+ public UIConfig DisableRightClick()
+ {
+ EnableRightClick = false;
+ return this;
+ }
+
+ public UIConfig HideLanguageMenu()
+ {
+ ShowLanguageMenu = false;
+ return this;
+ }
+
+ ///
+ /// Sets default language out of supported:
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ ///
+ ///
+ /// Default language e.g. .
+ /// This UIConfig instance.
+ public UIConfig SetDefaultLanguage(Language language)
+ {
+ DefaultLanguage = language.Code;
+ return this;
+ }
+
+ ///
+ /// Set supported UI languages. The following languages are supported:
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ ///
+ ///
+ /// Supported languages.
+ /// This UIConfig instance.
+ public UIConfig SetSupportedLanguages(params Language[] languages)
+ {
+ SupportedLanguages = languages.Select(l => l.Code).ToArray();
+ return this;
+ }
+ }
+}
diff --git a/Demos/ASP.NET MVC/src/Core/Configuration/ViewerConfig.cs b/Demos/ASP.NET MVC/src/Core/Configuration/ViewerConfig.cs
new file mode 100644
index 000000000..460e98a16
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Core/Configuration/ViewerConfig.cs
@@ -0,0 +1,48 @@
+using System;
+using GroupDocs.Viewer.Options;
+
+namespace GroupDocs.Viewer.AspNetMvc.Core.Configuration
+{
+ public class ViewerConfig
+ {
+ public static ViewerConfig Instance = new ViewerConfig();
+
+ internal string LicensePath = string.Empty;
+ internal readonly HtmlViewOptions HtmlViewOptions = HtmlViewOptions.ForEmbeddedResources();
+ internal readonly PngViewOptions PngViewOptions = new PngViewOptions();
+ internal readonly JpgViewOptions JpgViewOptions = new JpgViewOptions();
+ internal readonly PdfViewOptions PdfViewOptions = new PdfViewOptions();
+
+ private ViewerConfig() { }
+
+ public ViewerConfig SetLicensePath(string licensePath)
+ {
+ LicensePath = licensePath;
+ return this;
+ }
+
+ public ViewerConfig ConfigureHtmlViewOptions(Action setupOptions)
+ {
+ setupOptions?.Invoke(HtmlViewOptions);
+ return this;
+ }
+
+ public ViewerConfig ConfigurePngViewOptions(Action setupOptions)
+ {
+ setupOptions?.Invoke(PngViewOptions);
+ return this;
+ }
+
+ public ViewerConfig ConfigureJpgViewOptions(Action setupOptions)
+ {
+ setupOptions?.Invoke(JpgViewOptions);
+ return this;
+ }
+
+ public ViewerConfig ConfigurePdfViewOptions(Action setupOptions)
+ {
+ setupOptions?.Invoke(PdfViewOptions);
+ return this;
+ }
+ }
+}
diff --git a/Demos/ASP.NET MVC/src/Core/Constants.cs b/Demos/ASP.NET MVC/src/Core/Constants.cs
new file mode 100644
index 000000000..6ce879ed5
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Core/Constants.cs
@@ -0,0 +1,16 @@
+namespace GroupDocs.Viewer.AspNetMvc.Core
+{
+ public class Constants
+ {
+ public const string API_PATH = "viewer-api";
+ public const string LOAD_CONFIG_ACTION_NAME = "LoadConfig";
+ public const string LOAD_FILE_TREE_ACTION_NAME = "LoadFileTree";
+ public const string DOWNLOAD_DOCUMENT_ACTION_NAME = "DownloadDocument";
+ public const string LOAD_DOCUMENT_PAGE_RESOURCE_ACTION_NAME = "LoadDocumentPageResource";
+ public const string UPLOAD_DOCUMENT_ACTION_NAME = "UploadDocument";
+ public const string LOAD_DOCUMENT_DESCRIPTION_ACTION_NAME = "LoadDocumentDescription";
+ public const string LOAD_DOCUMENT_PAGES_ACTION_NAME = "LoadDocumentPages";
+ public const string LOAD_DOCUMENT_PAGE_ACTION_NAME = "LoadDocumentPage";
+ public const string PRINT_PDF_ACTION_NAME = "PrintPdf";
+ }
+}
diff --git a/Demos/ASP.NET MVC/src/Core/Entities/DocumentInfo.cs b/Demos/ASP.NET MVC/src/Core/Entities/DocumentInfo.cs
new file mode 100644
index 000000000..040443283
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Core/Entities/DocumentInfo.cs
@@ -0,0 +1,13 @@
+using System.Collections.Generic;
+
+namespace GroupDocs.Viewer.AspNetMvc.Core.Entities
+{
+ public class DocumentInfo
+ {
+ public string FileType { get; set; }
+
+ public bool PrintAllowed { get; set; }
+
+ public IEnumerable Pages { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Core/Entities/FileCredentials.cs b/Demos/ASP.NET MVC/src/Core/Entities/FileCredentials.cs
new file mode 100644
index 000000000..8adadac48
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Core/Entities/FileCredentials.cs
@@ -0,0 +1,16 @@
+namespace GroupDocs.Viewer.AspNetMvc.Core.Entities
+{
+ public class FileCredentials
+ {
+ public string FilePath { get; }
+ public string FileType { get; }
+ public string Password { get; }
+
+ public FileCredentials(string filePath, string fileType, string password)
+ {
+ FilePath = filePath;
+ FileType = fileType;
+ Password = password;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Core/Entities/FileSystemEntry.cs b/Demos/ASP.NET MVC/src/Core/Entities/FileSystemEntry.cs
new file mode 100644
index 000000000..45598d499
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Core/Entities/FileSystemEntry.cs
@@ -0,0 +1,33 @@
+namespace GroupDocs.Viewer.AspNetMvc.Core.Entities
+{
+ public class FileSystemEntry
+ {
+ public string FileName { get; private set; }
+
+ public string FilePath { get; private set; }
+
+ public bool IsDirectory { get; private set; }
+
+ public long Size { get; private set; }
+
+ private FileSystemEntry () { }
+
+ public static FileSystemEntry Directory(string name, string path, long size) =>
+ new FileSystemEntry
+ {
+ FileName = name,
+ FilePath = path,
+ IsDirectory = true,
+ Size = size
+ };
+
+ public static FileSystemEntry File(string name, string path, long size) =>
+ new FileSystemEntry
+ {
+ FileName = name,
+ FilePath = path,
+ IsDirectory = false,
+ Size = size
+ };
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Core/Entities/HtmlPage.cs b/Demos/ASP.NET MVC/src/Core/Entities/HtmlPage.cs
new file mode 100644
index 000000000..b448bbf4a
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Core/Entities/HtmlPage.cs
@@ -0,0 +1,22 @@
+using System.Text;
+
+namespace GroupDocs.Viewer.AspNetMvc.Core.Entities
+{
+ public class HtmlPage : Page
+ {
+ public static string Extension => ".html";
+
+ public override string GetContent() =>
+ Encoding.UTF8.GetString(Data);
+
+ public override void SetContent(string contents)
+ {
+ Data = Encoding.UTF8.GetBytes(contents);
+ }
+
+ public HtmlPage(int pageNumber, byte[] data)
+ : base(pageNumber, data)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Core/Entities/JpgPage.cs b/Demos/ASP.NET MVC/src/Core/Entities/JpgPage.cs
new file mode 100644
index 000000000..171882600
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Core/Entities/JpgPage.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Text;
+
+namespace GroupDocs.Viewer.AspNetMvc.Core.Entities
+{
+ public class JpgPage : Page
+ {
+ const string DATA_IMAGE = "data:image/jpeg;base64,";
+
+ public static string Extension => ".jpeg";
+
+ public override string GetContent()
+ {
+ return DATA_IMAGE + Convert.ToBase64String(Data);
+ }
+
+ public override void SetContent(string content)
+ {
+ this.Data = content.StartsWith(DATA_IMAGE)
+ ? Encoding.UTF8.GetBytes(content)
+ : Encoding.UTF8.GetBytes(content.Substring(DATA_IMAGE.Length - 1));
+ }
+
+ public JpgPage(int pageNumber, byte[] data)
+ : base(pageNumber, data)
+ {
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Core/Entities/Page.cs b/Demos/ASP.NET MVC/src/Core/Entities/Page.cs
new file mode 100644
index 000000000..265ca76ca
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Core/Entities/Page.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace GroupDocs.Viewer.AspNetMvc.Core.Entities
+{
+ public abstract class Page
+ {
+ private readonly List _resources = new List();
+
+ protected Page(int pageNumber, byte[] data)
+ {
+ PageNumber = pageNumber;
+ Data = data;
+ }
+
+ protected Page(int pageNumber, byte[] data, IEnumerable resources)
+ {
+ PageNumber = pageNumber;
+ Data = data;
+ _resources.AddRange(resources);
+ }
+
+ public IEnumerable Resources => _resources;
+
+ public int PageNumber { get; }
+
+ public byte[] Data { get; protected set; }
+
+ public abstract string GetContent();
+
+ public abstract void SetContent(string content);
+
+ public void AddResource(PageResource pageResource)
+ {
+ _resources.Add(pageResource);
+ }
+
+ public PageResource GetResource(string resourceName) =>
+ _resources.First(resource =>
+ resource.ResourceName.Equals(resourceName, StringComparison.InvariantCulture));
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Core/Entities/PageInfo.cs b/Demos/ASP.NET MVC/src/Core/Entities/PageInfo.cs
new file mode 100644
index 000000000..0cbf7d6dd
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Core/Entities/PageInfo.cs
@@ -0,0 +1,10 @@
+namespace GroupDocs.Viewer.AspNetMvc.Core.Entities
+{
+ public class PageInfo
+ {
+ public int Width { get; set; }
+ public int Height { get; set; }
+ public int Number { get; set; }
+ public string Name { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Core/Entities/PageResource.cs b/Demos/ASP.NET MVC/src/Core/Entities/PageResource.cs
new file mode 100644
index 000000000..382d4b6b1
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Core/Entities/PageResource.cs
@@ -0,0 +1,15 @@
+namespace GroupDocs.Viewer.AspNetMvc.Core.Entities
+{
+ public class PageResource
+ {
+ public PageResource(string resourceName, byte[] data)
+ {
+ ResourceName = resourceName;
+ Data = data;
+ }
+
+ public string ResourceName { get; }
+
+ public byte[] Data { get; }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Core/Entities/Pages.cs b/Demos/ASP.NET MVC/src/Core/Entities/Pages.cs
new file mode 100644
index 000000000..99d058707
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Core/Entities/Pages.cs
@@ -0,0 +1,35 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace GroupDocs.Viewer.AspNetMvc.Core.Entities
+{
+ public class Pages : IEnumerable
+ {
+ readonly List _pages;
+
+ public Pages()
+ {
+ _pages = new List();
+ }
+
+ public Pages(IEnumerable pages)
+ {
+ _pages = pages.ToList();
+ }
+
+ public void Add(Page page) => _pages.Add(page);
+
+ public Page this[int index]
+ {
+ get => _pages[index];
+ set => _pages.Insert(index, value);
+ }
+
+ public IEnumerator GetEnumerator()
+ => _pages.GetEnumerator();
+
+ IEnumerator IEnumerable.GetEnumerator()
+ => GetEnumerator();
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Core/Entities/PngPage.cs b/Demos/ASP.NET MVC/src/Core/Entities/PngPage.cs
new file mode 100644
index 000000000..85b4dd293
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Core/Entities/PngPage.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Text;
+
+namespace GroupDocs.Viewer.AspNetMvc.Core.Entities
+{
+ public class PngPage : Page
+ {
+ const string DATA_IMAGE = "data:image/png;base64,";
+
+ public static string Extension => ".png";
+
+ public override string GetContent() =>
+ DATA_IMAGE + Convert.ToBase64String(Data);
+
+ public override void SetContent(string content)
+ {
+ this.Data = content.StartsWith(DATA_IMAGE)
+ ? Encoding.UTF8.GetBytes(content)
+ : Encoding.UTF8.GetBytes(content.Substring(DATA_IMAGE.Length - 1));
+ }
+
+ public PngPage(int pageNumber, byte[] data)
+ : base(pageNumber, data)
+ {
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Core/Extensions/FileCacheExtensions.cs b/Demos/ASP.NET MVC/src/Core/Extensions/FileCacheExtensions.cs
new file mode 100644
index 000000000..dd5ec7499
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Core/Extensions/FileCacheExtensions.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Threading.Tasks;
+
+namespace GroupDocs.Viewer.AspNetMvc.Core.Extensions
+{
+ public static class FileCacheExtensions
+ {
+ ///
+ /// Gets the entry associated with this key if present or acquires and sets the entry if not present.
+ ///
+ /// Type of entry.
+ /// The cache.
+ /// A key identifying the requested entry.
+ /// The source file relative file path.
+ /// The method which returns entry.
+ /// The entry associated with this key if present or acquires and sets the entry if not present.
+ public static async Task GetValueAsync(this IFileCache cache, string cacheKey, string filePath, Func> acquire)
+ {
+ var entry = await cache.TryGetValueAsync(cacheKey, filePath);
+ if (entry == null)
+ {
+ entry = await acquire();
+ await cache.SetAsync(cacheKey, filePath, entry);
+ }
+
+ return entry;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Core/Extensions/MediaTypeExtensions.cs b/Demos/ASP.NET MVC/src/Core/Extensions/MediaTypeExtensions.cs
new file mode 100644
index 000000000..7183eb355
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Core/Extensions/MediaTypeExtensions.cs
@@ -0,0 +1,24 @@
+using System.IO;
+
+namespace GroupDocs.Viewer.AspNetMvc.Core.Extensions
+{
+ public static class ContentTypeExtensions
+ {
+ public static string ContentTypeFromFileName(this string filename)
+ {
+ var extension = Path.GetExtension(filename);
+
+ switch (extension)
+ {
+ case ".css": return "text/css";
+ case ".woff": return "font/woff";
+ case ".png": return "image/png";
+ case ".jpg":
+ case ".jpeg": return "image/jpeg";
+ case ".svg": return "image/svg+xml";
+ default:
+ return "application/octet-stream";
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Core/FileTypeResolution/FileExtensionFileTypeResolver.cs b/Demos/ASP.NET MVC/src/Core/FileTypeResolution/FileExtensionFileTypeResolver.cs
new file mode 100644
index 000000000..b78d8e66d
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Core/FileTypeResolution/FileExtensionFileTypeResolver.cs
@@ -0,0 +1,16 @@
+using System.IO;
+using System.Threading.Tasks;
+
+namespace GroupDocs.Viewer.AspNetMvc.Core.FileTypeResolution
+{
+ public class FileExtensionFileTypeResolver : IFileTypeResolver
+ {
+ public Task ResolveFileTypeAsync(string filePath)
+ {
+ string extension = Path.GetExtension(filePath);
+ FileType fileType = FileType.FromExtension(extension);
+
+ return Task.FromResult(fileType);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Core/FileTypeResolution/IFileTypeResolver.cs b/Demos/ASP.NET MVC/src/Core/FileTypeResolution/IFileTypeResolver.cs
new file mode 100644
index 000000000..7920a9255
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Core/FileTypeResolution/IFileTypeResolver.cs
@@ -0,0 +1,9 @@
+using System.Threading.Tasks;
+
+namespace GroupDocs.Viewer.AspNetMvc.Core.FileTypeResolution
+{
+ public interface IFileTypeResolver
+ {
+ Task ResolveFileTypeAsync(string filePath);
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Core/IAsyncLock.cs b/Demos/ASP.NET MVC/src/Core/IAsyncLock.cs
new file mode 100644
index 000000000..63df19206
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Core/IAsyncLock.cs
@@ -0,0 +1,10 @@
+using System;
+using System.Threading.Tasks;
+
+namespace GroupDocs.Viewer.AspNetMvc.Core
+{
+ public interface IAsyncLock
+ {
+ Task LockAsync(object key);
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Core/IFileCache.cs b/Demos/ASP.NET MVC/src/Core/IFileCache.cs
new file mode 100644
index 000000000..fa2d859f0
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Core/IFileCache.cs
@@ -0,0 +1,15 @@
+using System.Threading.Tasks;
+
+namespace GroupDocs.Viewer.AspNetMvc.Core
+{
+ public interface IFileCache
+ {
+ TEntry TryGetValue(string cacheKey, string filePath);
+
+ Task TryGetValueAsync(string cacheKey, string filePath);
+
+ void Set(string cacheKey, string filePath, TEntry entry);
+
+ Task SetAsync(string cacheKey, string filePath, TEntry entry);
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Core/IFileStorage.cs b/Demos/ASP.NET MVC/src/Core/IFileStorage.cs
new file mode 100644
index 000000000..560ba8c4c
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Core/IFileStorage.cs
@@ -0,0 +1,15 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using GroupDocs.Viewer.AspNetMvc.Core.Entities;
+
+namespace GroupDocs.Viewer.AspNetMvc.Core
+{
+ public interface IFileStorage
+ {
+ Task> ListDirsAndFilesAsync(string dirPath);
+
+ Task ReadFileAsync(string filePath);
+
+ Task WriteFileAsync(string fileName, byte[] bytes, bool rewrite);
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Core/IPageFormatter.cs b/Demos/ASP.NET MVC/src/Core/IPageFormatter.cs
new file mode 100644
index 000000000..6a625c762
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Core/IPageFormatter.cs
@@ -0,0 +1,10 @@
+using System.Threading.Tasks;
+using GroupDocs.Viewer.AspNetMvc.Core.Entities;
+
+namespace GroupDocs.Viewer.AspNetMvc.Core
+{
+ public interface IPageFormatter
+ {
+ Task FormatAsync(FileCredentials fileCredentials, Page page);
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Core/IViewer.cs b/Demos/ASP.NET MVC/src/Core/IViewer.cs
new file mode 100644
index 000000000..39ccd7db8
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Core/IViewer.cs
@@ -0,0 +1,16 @@
+using System.Threading.Tasks;
+using GroupDocs.Viewer.AspNetMvc.Core.Entities;
+
+namespace GroupDocs.Viewer.AspNetMvc.Core
+{
+ public interface IViewer
+ {
+ string PageExtension { get; }
+ Page CreatePage(int pageNumber, byte[] data);
+ Task GetDocumentInfoAsync(FileCredentials fileCredentials);
+ Task GetPageAsync(FileCredentials fileCredentials, int pageNumber);
+ Task GetPagesAsync(FileCredentials fileCredentials, int[] pageNumbers);
+ Task GetPdfAsync(FileCredentials fileCredentials);
+ Task GetPageResourceAsync(FileCredentials fileCredentials, int pageNumber, string resourceName);
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Core/Licensing/IViewerLicenser.cs b/Demos/ASP.NET MVC/src/Core/Licensing/IViewerLicenser.cs
new file mode 100644
index 000000000..1b35c295d
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Core/Licensing/IViewerLicenser.cs
@@ -0,0 +1,7 @@
+namespace GroupDocs.Viewer.AspNetMvc.Core.Licensing
+{
+ internal interface IViewerLicenser
+ {
+ void SetLicense();
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Core/Licensing/LicenseFileViewerLicenser.cs b/Demos/ASP.NET MVC/src/Core/Licensing/LicenseFileViewerLicenser.cs
new file mode 100644
index 000000000..7c9b13bf7
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Core/Licensing/LicenseFileViewerLicenser.cs
@@ -0,0 +1,45 @@
+using System;
+using System.IO;
+using GroupDocs.Viewer.AspNetMvc.Core.Configuration;
+
+namespace GroupDocs.Viewer.AspNetMvc.Core.Licensing
+{
+ internal class LicenseFileViewerLicenser : IViewerLicenser
+ {
+ private readonly ViewerConfig _config;
+ private readonly object _lock = new object();
+ private bool _licenseSet;
+
+ public LicenseFileViewerLicenser(ViewerConfig config)
+ {
+ _config = config;
+ }
+
+ public void SetLicense()
+ {
+ if (_licenseSet)
+ return;
+
+ if (File.Exists(_config.LicensePath))
+ SetLicense(_config.LicensePath);
+
+ string licensePath = Environment.GetEnvironmentVariable("GROUPDOCS_LIC_PATH");
+ if (!string.IsNullOrEmpty(licensePath))
+ SetLicense(licensePath);
+ }
+
+ private void SetLicense(string licensePath)
+ {
+ lock (_lock)
+ {
+ if (!_licenseSet)
+ {
+ License license = new License();
+ license.SetLicense(licensePath);
+
+ _licenseSet = true;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Core/PageFormatting/NoopPageFormatter.cs b/Demos/ASP.NET MVC/src/Core/PageFormatting/NoopPageFormatter.cs
new file mode 100644
index 000000000..135d9cf04
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Core/PageFormatting/NoopPageFormatter.cs
@@ -0,0 +1,11 @@
+using System.Threading.Tasks;
+using GroupDocs.Viewer.AspNetMvc.Core.Entities;
+
+namespace GroupDocs.Viewer.AspNetMvc.Core.PageFormatting
+{
+ public class NoopPageFormatter : IPageFormatter
+ {
+ public Task FormatAsync(FileCredentials fileCredentials, Page page) =>
+ Task.FromResult(page);
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Core/Storage/LocalFileStorage.cs b/Demos/ASP.NET MVC/src/Core/Storage/LocalFileStorage.cs
new file mode 100644
index 000000000..38cf9798c
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Core/Storage/LocalFileStorage.cs
@@ -0,0 +1,129 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using GroupDocs.Viewer.AspNetMvc.Core.Entities;
+using GroupDocs.Viewer.AspNetMvc.Core.Utils;
+
+namespace GroupDocs.Viewer.AspNetMvc.Core.Storage
+{
+ public class LocalFileStorage : IFileStorage
+ {
+ private readonly string _storagePath;
+ private readonly TimeSpan _waitTimeout = TimeSpan.FromMilliseconds(100);
+
+ public LocalFileStorage(string storagePath)
+ {
+ _storagePath = storagePath;
+ }
+
+ private IEnumerable ListFiles(string folderPath)
+ {
+ var folderFullPath = string.IsNullOrEmpty(folderPath)
+ ? _storagePath
+ : Path.Combine(_storagePath, folderPath);
+
+ var dirs = Directory.GetDirectories(folderFullPath)
+ .Select(file => new FileInfo(file))
+ .Where(fileInfo => !fileInfo.Attributes.HasFlag(FileAttributes.Hidden))
+ .OrderBy(fileInfo => fileInfo.Name)
+ .ThenByDescending(fileInfo => fileInfo.CreationTime)
+ .Select(directory =>
+ FileSystemEntry.Directory(directory.Name, PathUtils.GetRelativePath(_storagePath, directory.FullName), 0L));
+
+ var files = Directory
+ .GetFiles(folderFullPath)
+ .Select(file => new FileInfo(file))
+ .Where(fileInfo => !fileInfo.Attributes.HasFlag(FileAttributes.Hidden))
+ .OrderBy(fileInfo => fileInfo.Name)
+ .ThenByDescending(fileInfo => fileInfo.CreationTime)
+ .Select(file =>
+ FileSystemEntry.File(file.Name, PathUtils.GetRelativePath(_storagePath, file.FullName), file.Length));
+
+ var dirsAndFiles = dirs.Concat(files);
+ return dirsAndFiles;
+ }
+
+ public Task> ListDirsAndFilesAsync(string dirPath) =>
+ Task.FromResult(ListFiles(dirPath));
+
+ public async Task ReadFileAsync(string filePath)
+ {
+ var fullPath = Path.Combine(_storagePath, filePath);
+ using (FileStream fs = GetStream(fullPath, FileMode.Open, FileAccess.Read, FileShare.None))
+ {
+ var memoryStream = new MemoryStream();
+ await fs.CopyToAsync(memoryStream);
+
+ return memoryStream.ToArray();
+ }
+ }
+
+ public async Task WriteFileAsync(string fileName, byte[] bytes, bool rewrite)
+ {
+ var newFileName = rewrite ? fileName : GetFreeFileName(fileName);
+ var fullPath = Path.Combine(_storagePath, newFileName);
+ var fileMode = rewrite ? FileMode.Create : FileMode.CreateNew;
+
+ using (FileStream fs = GetStream(fullPath, fileMode, FileAccess.Write, FileShare.None))
+ {
+ await fs.WriteAsync(bytes, 0, bytes.Length);
+ }
+
+ return newFileName;
+ }
+
+ private FileStream GetStream(string path, FileMode mode, FileAccess access, FileShare share)
+ {
+ FileStream stream = null;
+ TimeSpan interval = new TimeSpan(0, 0, 0, 0, 50);
+ TimeSpan totalTime = new TimeSpan();
+
+ while (stream == null)
+ {
+ try
+ {
+ stream = File.Open(path, mode, access, share);
+ }
+ catch (IOException)
+ {
+ Thread.Sleep(interval);
+ totalTime += interval;
+
+ if (_waitTimeout.Ticks != 0 && totalTime > _waitTimeout)
+ {
+ throw;
+ }
+ }
+ }
+
+ return stream;
+ }
+
+ private string GetFreeFileName(string fileName)
+ {
+ var fullPath = Path.Combine(_storagePath, fileName);
+
+ if (!File.Exists(fullPath))
+ return fileName;
+
+ List dirFiles = Directory.GetFiles(_storagePath)
+ .Select(filePath => Path.GetFileName(filePath))
+ .ToList();
+
+ var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName);
+ var number = 1;
+ string fileNameCandidate;
+ do
+ {
+ string newFileName = $"{fileNameWithoutExtension} ({number})";
+ fileNameCandidate = fileName.Replace(fileNameWithoutExtension, newFileName);
+ number++;
+ } while (dirFiles.Contains(fileNameCandidate));
+
+ return fileNameCandidate;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Core/Utils/PathUtils.cs b/Demos/ASP.NET MVC/src/Core/Utils/PathUtils.cs
new file mode 100644
index 000000000..418b52214
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Core/Utils/PathUtils.cs
@@ -0,0 +1,54 @@
+using System.IO;
+using System;
+using System.Linq;
+
+namespace GroupDocs.Viewer.AspNetMvc.Core.Utils
+{
+ public static class PathUtils
+ {
+ public static string GetRelativePath(string relativeTo, string path)
+ {
+ if (string.IsNullOrEmpty(relativeTo))
+ throw new ArgumentNullException(nameof(relativeTo));
+
+ if (string.IsNullOrEmpty(path))
+ throw new ArgumentNullException("path");
+
+ Uri fromUri = new Uri(AppendDirectorySeparatorChar(relativeTo));
+ Uri toUri = new Uri(AppendDirectorySeparatorChar(path));
+
+ if (fromUri.Scheme != toUri.Scheme)
+ return path;
+
+ Uri relativeUri = fromUri.MakeRelativeUri(toUri);
+ string relativePath = Uri.UnescapeDataString(relativeUri.ToString());
+
+ if (string.Equals(toUri.Scheme, Uri.UriSchemeFile, StringComparison.OrdinalIgnoreCase))
+ relativePath = relativePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
+
+ return relativePath;
+ }
+
+ private static string AppendDirectorySeparatorChar(string path)
+ {
+ // Append a slash only if the path is a directory and does not have a slash.
+ if (!Path.HasExtension(path) &&
+ !path.EndsWith(Path.DirectorySeparatorChar.ToString()))
+ {
+ return path + Path.DirectorySeparatorChar;
+ }
+
+ return path;
+ }
+
+ public static string RemoveInvalidFileNameChars(string path)
+ {
+ Path.GetInvalidFileNameChars().ToList().ForEach(ch =>
+ {
+ path = path.Replace(ch.ToString(), string.Empty);
+ });
+
+ return path;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Core/ViewerType.cs b/Demos/ASP.NET MVC/src/Core/ViewerType.cs
new file mode 100644
index 000000000..4a2726d44
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Core/ViewerType.cs
@@ -0,0 +1,10 @@
+namespace GroupDocs.Viewer.AspNetMvc.Core
+{
+ public enum ViewerType
+ {
+ HtmlWithEmbeddedResources,
+ HtmlWithExternalResources,
+ Png,
+ Jpg,
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Core/Viewers/BaseViewer.cs b/Demos/ASP.NET MVC/src/Core/Viewers/BaseViewer.cs
new file mode 100644
index 000000000..83511cad9
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Core/Viewers/BaseViewer.cs
@@ -0,0 +1,175 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using GroupDocs.Viewer.AspNetMvc.Core.Configuration;
+using GroupDocs.Viewer.AspNetMvc.Core.Entities;
+using GroupDocs.Viewer.AspNetMvc.Core.FileTypeResolution;
+using GroupDocs.Viewer.AspNetMvc.Core.Licensing;
+using GroupDocs.Viewer.AspNetMvc.Core.Viewers.Extensions;
+using GroupDocs.Viewer.Options;
+using GroupDocs.Viewer.Results;
+using Page = GroupDocs.Viewer.AspNetMvc.Core.Entities.Page;
+
+namespace GroupDocs.Viewer.AspNetMvc.Core.Viewers
+{
+ internal abstract class BaseViewer : IViewer, IDisposable
+ {
+ private readonly ViewerConfig _config;
+ private readonly IViewerLicenser _viewerLicenser;
+ private readonly IFileStorage _fileStorage;
+ private readonly IFileTypeResolver _fileTypeResolver;
+ private readonly IPageFormatter _pageFormatter;
+ private Viewer _viewer;
+
+ protected BaseViewer(ViewerConfig config,
+ IViewerLicenser viewerLicenser,
+ IFileStorage fileStorage,
+ IFileTypeResolver fileTypeResolver,
+ IPageFormatter pageFormatter)
+ {
+ _config = config;
+ _viewerLicenser = viewerLicenser;
+ _fileStorage = fileStorage;
+ _fileTypeResolver = fileTypeResolver;
+ _pageFormatter = pageFormatter;
+ }
+
+ public abstract string PageExtension { get; }
+
+ public abstract Page CreatePage(int pageNumber, byte[] data);
+
+ protected abstract Page RenderPage(Viewer viewer, string filePath, int pageNumber);
+
+ protected abstract ViewInfoOptions CreateViewInfoOptions();
+
+ public async Task GetDocumentInfoAsync(FileCredentials fileCredentials)
+ {
+ var viewer = await InitViewerAsync(fileCredentials);
+ var viewInfoOptions = CreateViewInfoOptions();
+ var viewInfo = viewer.GetViewInfo(viewInfoOptions);
+
+ var documentInfo = ToDocumentInfo(viewInfo);
+ return documentInfo;
+ }
+
+ public async Task GetPageAsync(FileCredentials fileCredentials, int pageNumber)
+ {
+ var viewer = await InitViewerAsync(fileCredentials);
+ var page = await RenderPageInternalAsync(viewer, fileCredentials, pageNumber);
+
+ return page;
+ }
+
+ public async Task GetPagesAsync(FileCredentials fileCredentials, int[] pageNumbers)
+ {
+ var viewer = await InitViewerAsync(fileCredentials);
+
+ var pages = new Pages();
+
+ foreach (var pageNumber in pageNumbers)
+ {
+ var page = await RenderPageInternalAsync(viewer, fileCredentials, pageNumber);
+ pages.Add(page);
+ }
+
+ return pages;
+ }
+
+ public async Task GetPdfAsync(FileCredentials fileCredentials)
+ {
+ var pdfStream = new MemoryStream();
+ var viewOptions = CreatePdfViewOptions(pdfStream);
+
+ var viewer = await InitViewerAsync(fileCredentials);
+ viewer.View(viewOptions);
+
+ return pdfStream.ToArray();
+ }
+
+ public abstract Task GetPageResourceAsync(FileCredentials fileCredentials, int pageNumber, string resourceName);
+
+ private PdfViewOptions CreatePdfViewOptions(MemoryStream pdfStream)
+ {
+ var viewOptions = new PdfViewOptions(() => pdfStream, _ => { /* NOTE: nothing to do here */ });
+
+ viewOptions.CopyViewOptions(_config.PdfViewOptions);
+
+ return viewOptions;
+ }
+
+ private async Task InitViewerAsync(FileCredentials fileCredentials)
+ {
+ if (_viewer == null)
+ {
+ _viewerLicenser.SetLicense();
+
+ var fileStream = await GetFileStreamAsync(fileCredentials.FilePath);
+ var loadOptions = await CreateLoadOptionsAsync(fileCredentials);
+ _viewer = new Viewer(fileStream, loadOptions);
+ }
+
+ return _viewer;
+ }
+
+ private async Task GetFileStreamAsync(string filePath)
+ {
+ byte[] bytes = await _fileStorage.ReadFileAsync(filePath);
+ MemoryStream memoryStream = new MemoryStream(bytes);
+ return memoryStream;
+ }
+
+ private async Task CreateLoadOptionsAsync(FileCredentials fileCredentials)
+ {
+ FileType loadFileType = FileType.FromExtension(fileCredentials.FileType);
+ if(loadFileType == FileType.Unknown)
+ loadFileType = await _fileTypeResolver.ResolveFileTypeAsync(fileCredentials.FilePath);
+
+ LoadOptions loadOptions = new LoadOptions
+ {
+ FileType = FileType.FromExtension(loadFileType.Extension),
+ Password = fileCredentials.Password,
+ ResourceLoadingTimeout = TimeSpan.FromSeconds(3)
+ };
+ return loadOptions;
+ }
+
+ private async Task RenderPageInternalAsync(
+ Viewer viewer, FileCredentials fileCredentials, int pageNumber)
+ {
+ var page = RenderPage(viewer, fileCredentials.FilePath, pageNumber);
+ page = await _pageFormatter.FormatAsync(fileCredentials, page);
+
+ return page;
+ }
+
+ private static DocumentInfo ToDocumentInfo(ViewInfo viewInfo)
+ {
+ var printAllowed = true;
+ if (viewInfo is PdfViewInfo info)
+ printAllowed = info.PrintingAllowed;
+
+ var fileType = viewInfo.FileType.Extension
+ .Replace(".", string.Empty);
+
+ return new DocumentInfo
+ {
+ FileType = fileType,
+ PrintAllowed = printAllowed,
+ Pages = viewInfo.Pages.Select(page => new PageInfo
+ {
+ Number = page.Number,
+ Width = page.Width,
+ Height = page.Height,
+ Name = page.Name
+ })
+ };
+ }
+
+ public void Dispose()
+ {
+ _viewer?.Dispose();
+ _viewer = null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Core/Viewers/Extensions/ViewOptionsExtensions.cs b/Demos/ASP.NET MVC/src/Core/Viewers/Extensions/ViewOptionsExtensions.cs
new file mode 100644
index 000000000..3a450ae76
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Core/Viewers/Extensions/ViewOptionsExtensions.cs
@@ -0,0 +1,90 @@
+using GroupDocs.Viewer.Options;
+
+namespace GroupDocs.Viewer.AspNetMvc.Core.Viewers.Extensions
+{
+ internal static class ViewOptionsExtensions
+ {
+ public static void CopyViewOptions(this HtmlViewOptions dst, HtmlViewOptions src)
+ {
+ dst.CopyBaseViewOptions(src);
+ dst.CopyHtmlViewOptions(src);
+ }
+
+ public static void CopyViewOptions(this PdfViewOptions dst, PdfViewOptions src)
+ {
+ dst.CopyBaseViewOptions(src);
+ dst.CopyPdfViewOptions(src);
+ }
+
+ public static void CopyViewOptions(this PngViewOptions dst, PngViewOptions src)
+ {
+ dst.CopyBaseViewOptions(src);
+ dst.CopyPngViewOptions(src);
+ }
+
+ public static void CopyViewOptions(this JpgViewOptions dst, JpgViewOptions src)
+ {
+ dst.CopyBaseViewOptions(src);
+ dst.CopyJpgViewOptions(src);
+ }
+
+ private static void CopyBaseViewOptions(this BaseViewOptions dst, BaseViewOptions src)
+ {
+ dst.RenderComments = src.RenderComments;
+ dst.RenderNotes = src.RenderNotes;
+ dst.RenderHiddenPages = src.RenderHiddenPages;
+ dst.DefaultFontName = src.DefaultFontName;
+ dst.ArchiveOptions = src.ArchiveOptions;
+ dst.CadOptions = src.CadOptions;
+ dst.EmailOptions = src.EmailOptions;
+ dst.OutlookOptions = src.OutlookOptions;
+ dst.PdfOptions = src.PdfOptions;
+ dst.ProjectManagementOptions = src.ProjectManagementOptions;
+ dst.SpreadsheetOptions = src.SpreadsheetOptions;
+ dst.WordProcessingOptions = src.WordProcessingOptions;
+ }
+
+ private static void CopyHtmlViewOptions(this HtmlViewOptions dst, HtmlViewOptions src)
+ {
+ dst.RenderResponsive = src.RenderResponsive;
+ dst.Minify = src.Minify;
+ dst.RenderToSinglePage = src.RenderToSinglePage;
+ dst.ImageMaxWidth = src.ImageMaxWidth;
+ dst.ImageMaxHeight = src.ImageMaxHeight;
+ dst.ImageWidth = src.ImageWidth;
+ dst.ImageHeight = src.ImageHeight;
+ dst.ForPrinting = src.ForPrinting;
+ dst.ExcludeFonts = src.ExcludeFonts;
+ dst.FontsToExclude = src.FontsToExclude;
+ dst.FontsToExclude = src.FontsToExclude;
+ }
+
+ private static void CopyPdfViewOptions(this PdfViewOptions dst, PdfViewOptions src)
+ {
+ dst.JpgQuality = src.JpgQuality;
+ dst.Security = src.Security;
+ dst.ImageMaxWidth = src.ImageMaxWidth;
+ dst.ImageMaxHeight = src.ImageMaxHeight;
+ dst.ImageWidth = src.ImageWidth;
+ dst.ImageHeight = src.ImageHeight;
+ }
+
+ private static void CopyPngViewOptions(this PngViewOptions dst, PngViewOptions src)
+ {
+ dst.ExtractText = src.ExtractText;
+ dst.Width = src.Width;
+ dst.Height = src.Height;
+ dst.MaxWidth = src.MaxWidth;
+ dst.MaxHeight = src.MaxHeight;
+ }
+ private static void CopyJpgViewOptions(this JpgViewOptions dst, JpgViewOptions src)
+ {
+ dst.Quality = src.Quality;
+ dst.ExtractText = src.ExtractText;
+ dst.Width = src.Width;
+ dst.Height = src.Height;
+ dst.MaxWidth = src.MaxWidth;
+ dst.MaxHeight = src.MaxHeight;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Core/Viewers/HtmlWithEmbeddedResourcesViewer.cs b/Demos/ASP.NET MVC/src/Core/Viewers/HtmlWithEmbeddedResourcesViewer.cs
new file mode 100644
index 000000000..d979c0600
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Core/Viewers/HtmlWithEmbeddedResourcesViewer.cs
@@ -0,0 +1,63 @@
+using System.IO;
+using System.Threading.Tasks;
+using GroupDocs.Viewer.AspNetMvc.Core.Configuration;
+using GroupDocs.Viewer.AspNetMvc.Core.Entities;
+using GroupDocs.Viewer.AspNetMvc.Core.FileTypeResolution;
+using GroupDocs.Viewer.AspNetMvc.Core.Licensing;
+using GroupDocs.Viewer.AspNetMvc.Core.Viewers.Extensions;
+using GroupDocs.Viewer.Options;
+using Page = GroupDocs.Viewer.AspNetMvc.Core.Entities.Page;
+
+namespace GroupDocs.Viewer.AspNetMvc.Core.Viewers
+{
+ internal class HtmlWithEmbeddedResourcesViewer : BaseViewer
+ {
+ private readonly ViewerConfig _config;
+
+ public HtmlWithEmbeddedResourcesViewer(ViewerConfig config,
+ IViewerLicenser licenser,
+ IFileStorage fileStorage,
+ IFileTypeResolver fileTypeResolver,
+ IPageFormatter pageFormatter)
+ : base(config, licenser, fileStorage, fileTypeResolver, pageFormatter)
+ {
+ _config = config;
+ }
+
+ public override string PageExtension => HtmlPage.Extension;
+
+ public override Page CreatePage(int pageNumber, byte[] data)
+ => new HtmlPage(pageNumber, data);
+
+ public override Task GetPageResourceAsync(
+ FileCredentials fileCredentials, int pageNumber, string resourceName) =>
+ throw new System.NotImplementedException(
+ $"{nameof(HtmlWithEmbeddedResourcesViewer)} does not support retrieving external HTML resources.");
+
+ protected override ViewInfoOptions CreateViewInfoOptions() =>
+ ViewInfoOptions.FromHtmlViewOptions(_config.HtmlViewOptions);
+
+ protected override Page RenderPage(Viewer viewer, string filePath, int pageNumber)
+ {
+ var pageStream = new MemoryStream();
+ var viewOptions = CreateViewOptions(pageStream);
+
+ viewer.View(viewOptions, pageNumber);
+
+ var bytes = pageStream.ToArray();
+ var page = CreatePage(pageNumber, bytes);
+
+ return page;
+ }
+
+ private HtmlViewOptions CreateViewOptions(MemoryStream pageStream)
+ {
+ var viewOptions = HtmlViewOptions.ForEmbeddedResources(_ => pageStream,
+ (_, __) => { /*NOTE: Do nothing here*/ });
+
+ viewOptions.CopyViewOptions(_config.HtmlViewOptions);
+
+ return viewOptions;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Core/Viewers/HtmlWithExternalResourcesViewer.cs b/Demos/ASP.NET MVC/src/Core/Viewers/HtmlWithExternalResourcesViewer.cs
new file mode 100644
index 000000000..8b85efc93
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Core/Viewers/HtmlWithExternalResourcesViewer.cs
@@ -0,0 +1,120 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Threading.Tasks;
+using GroupDocs.Viewer.AspNetMvc.Core.Configuration;
+using GroupDocs.Viewer.AspNetMvc.Core.Entities;
+using GroupDocs.Viewer.AspNetMvc.Core.FileTypeResolution;
+using GroupDocs.Viewer.AspNetMvc.Core.Licensing;
+using GroupDocs.Viewer.AspNetMvc.Core.Viewers.Extensions;
+using GroupDocs.Viewer.Interfaces;
+using GroupDocs.Viewer.Options;
+using GroupDocs.Viewer.Results;
+using Page = GroupDocs.Viewer.AspNetMvc.Core.Entities.Page;
+
+namespace GroupDocs.Viewer.AspNetMvc.Core.Viewers
+{
+ internal class HtmlWithExternalResourcesViewer : BaseViewer
+ {
+ private readonly ViewerConfig _config;
+
+ public HtmlWithExternalResourcesViewer(
+ ViewerConfig config,
+ IViewerLicenser licenser,
+ IFileStorage fileStorage,
+ IFileTypeResolver fileTypeResolver,
+ IPageFormatter pageFormatter)
+ : base(config, licenser, fileStorage, fileTypeResolver, pageFormatter)
+ {
+ _config = config;
+ }
+
+ public override string PageExtension => HtmlPage.Extension;
+
+ public override Page CreatePage(int pageNumber, byte[] data)
+ => new HtmlPage(pageNumber, data);
+
+ protected override Page RenderPage(Viewer viewer, string filePath, int pageNumber)
+ {
+ var basePath = Constants.API_PATH;
+ var actionName = Constants.LOAD_DOCUMENT_PAGE_RESOURCE_ACTION_NAME;
+
+ var streamFactory = new MemoryPageStreamFactory(basePath, actionName, filePath);
+ var viewOptions = HtmlViewOptions.ForExternalResources(streamFactory, streamFactory);
+ viewOptions.CopyViewOptions(_config.HtmlViewOptions);
+ viewer.View(viewOptions, pageNumber);
+
+ var pageContents = streamFactory.GetPageContents();
+ var page = CreatePage(pageNumber, pageContents.GetPageData());
+ foreach (var resource in pageContents.Resources)
+ {
+ var pageResource = new PageResource(resource.Key, resource.Value.ToArray());
+ page.AddResource(pageResource);
+ }
+
+ return page;
+ }
+
+ protected override ViewInfoOptions CreateViewInfoOptions() =>
+ ViewInfoOptions.FromHtmlViewOptions(_config.HtmlViewOptions);
+
+ public override async Task GetPageResourceAsync(
+ FileCredentials fileCredentials, int pageNumber, string resourceName)
+ {
+ var page = await GetPageAsync(fileCredentials, pageNumber);
+ var resource = page.GetResource(resourceName);
+
+ return resource.Data;
+ }
+
+ private class MemoryPageStreamFactory : IPageStreamFactory, IResourceStreamFactory
+ {
+ private readonly string _basePath;
+ private readonly string _actionName;
+ private readonly string _filePath;
+ private readonly PageContents _pageContents;
+
+ public MemoryPageStreamFactory(string basePath, string actionName, string filePath)
+ {
+ _basePath = basePath;
+ _actionName = actionName;
+ _filePath = WebUtility.UrlEncode(filePath);
+ _pageContents = new PageContents();
+ }
+
+ public PageContents GetPageContents() =>
+ _pageContents;
+
+ public Stream CreatePageStream(int pageNumber) =>
+ _pageContents.GetPageStream();
+
+ public void ReleasePageStream(int pageNumber, Stream pageStream) { }
+
+ public Stream CreateResourceStream(int pageNumber, Resource resource) =>
+ _pageContents.GetResourceStream(resource.FileName);
+
+ public string CreateResourceUrl(int pageNumber, Resource resource) =>
+ $"/{_basePath}/{_actionName}?guid={_filePath}&pageNumber={pageNumber}&resourceName={resource.FileName}";
+
+ public void ReleaseResourceStream(int pageNumber, Resource resource, Stream resourceStream) { }
+ }
+
+ private class PageContents
+ {
+ private MemoryStream PageStream { get; } = new MemoryStream();
+
+ public Dictionary Resources { get; } = new Dictionary();
+
+ public byte[] GetPageData() => PageStream.ToArray();
+
+ public Stream GetPageStream() => PageStream;
+
+ public Stream GetResourceStream(string fileName)
+ {
+ var resourceStream = new MemoryStream();
+ Resources.Add(fileName, resourceStream);
+ return resourceStream;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Core/Viewers/JpgViewer.cs b/Demos/ASP.NET MVC/src/Core/Viewers/JpgViewer.cs
new file mode 100644
index 000000000..8aa060203
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Core/Viewers/JpgViewer.cs
@@ -0,0 +1,63 @@
+using System.IO;
+using System.Threading.Tasks;
+using GroupDocs.Viewer.AspNetMvc.Core.Configuration;
+using GroupDocs.Viewer.AspNetMvc.Core.Entities;
+using GroupDocs.Viewer.AspNetMvc.Core.FileTypeResolution;
+using GroupDocs.Viewer.AspNetMvc.Core.Licensing;
+using GroupDocs.Viewer.AspNetMvc.Core.Viewers.Extensions;
+using GroupDocs.Viewer.Options;
+using Page = GroupDocs.Viewer.AspNetMvc.Core.Entities.Page;
+
+namespace GroupDocs.Viewer.AspNetMvc.Core.Viewers
+{
+ internal class JpgViewer : BaseViewer
+ {
+ private readonly ViewerConfig _config;
+
+ public JpgViewer(ViewerConfig config,
+ IViewerLicenser licenser,
+ IFileStorage fileStorage,
+ IFileTypeResolver fileTypeResolver,
+ IPageFormatter pageFormatter)
+ : base(config, licenser, fileStorage, fileTypeResolver, pageFormatter)
+ {
+ _config = config;
+ }
+
+ public override string PageExtension => JpgPage.Extension;
+
+ public override Page CreatePage(int pageNumber, byte[] data) =>
+ new JpgPage(pageNumber, data);
+
+ public override Task GetPageResourceAsync(
+ FileCredentials fileCredentials, int pageNumber, string resourceName) =>
+ throw new System.NotImplementedException(
+ $"{nameof(JpgViewer)} does not support retrieving external HTML resources.");
+
+ protected override Page RenderPage(Viewer viewer, string filePath, int pageNumber)
+ {
+ var pageStream = new MemoryStream();
+ var viewOptions = CreateViewOptions(pageStream);
+
+ viewer.View(viewOptions, pageNumber);
+
+ var bytes = pageStream.ToArray();
+ var page = CreatePage(pageNumber, bytes);
+
+ return page;
+ }
+
+ protected override ViewInfoOptions CreateViewInfoOptions() =>
+ ViewInfoOptions.FromJpgViewOptions(_config.JpgViewOptions);
+
+ private JpgViewOptions CreateViewOptions(MemoryStream pageStream)
+ {
+ var viewOptions = new JpgViewOptions(_ => pageStream,
+ (_, __) => { /*NOTE: Do nothing here*/ });
+
+ viewOptions.CopyViewOptions(_config.JpgViewOptions);
+
+ return viewOptions;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Core/Viewers/PngViewer.cs b/Demos/ASP.NET MVC/src/Core/Viewers/PngViewer.cs
new file mode 100644
index 000000000..d114172b0
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Core/Viewers/PngViewer.cs
@@ -0,0 +1,63 @@
+using System.IO;
+using System.Threading.Tasks;
+using GroupDocs.Viewer.AspNetMvc.Core.Configuration;
+using GroupDocs.Viewer.AspNetMvc.Core.Entities;
+using GroupDocs.Viewer.AspNetMvc.Core.FileTypeResolution;
+using GroupDocs.Viewer.AspNetMvc.Core.Licensing;
+using GroupDocs.Viewer.AspNetMvc.Core.Viewers.Extensions;
+using GroupDocs.Viewer.Options;
+using Page = GroupDocs.Viewer.AspNetMvc.Core.Entities.Page;
+
+namespace GroupDocs.Viewer.AspNetMvc.Core.Viewers
+{
+ internal class PngViewer : BaseViewer
+ {
+ private readonly ViewerConfig _config;
+
+ public PngViewer(ViewerConfig config,
+ IViewerLicenser licenser,
+ IFileStorage fileStorage,
+ IFileTypeResolver fileTypeResolver,
+ IPageFormatter pageFormatter)
+ : base(config, licenser, fileStorage, fileTypeResolver, pageFormatter)
+ {
+ _config = config;
+ }
+
+ public override string PageExtension => PngPage.Extension;
+
+ public override Page CreatePage(int pageNumber, byte[] data) =>
+ new PngPage(pageNumber, data);
+
+ public override Task GetPageResourceAsync(
+ FileCredentials fileCredentials, int pageNumber, string resourceName) =>
+ throw new System.NotImplementedException(
+ $"{nameof(PngViewer)} does not support retrieving external HTML resources.");
+
+ protected override Page RenderPage(Viewer viewer, string filePath, int pageNumber)
+ {
+ var pageStream = new MemoryStream();
+ var viewOptions = CreateViewOptions(pageStream);
+
+ viewer.View(viewOptions, pageNumber);
+
+ var bytes = pageStream.ToArray();
+ var page = CreatePage(pageNumber, bytes);
+
+ return page;
+ }
+
+ protected override ViewInfoOptions CreateViewInfoOptions() =>
+ ViewInfoOptions.FromJpgViewOptions(_config.JpgViewOptions);
+
+ private PngViewOptions CreateViewOptions(MemoryStream pageStream)
+ {
+ var viewOptions = new PngViewOptions(_ => pageStream,
+ (_, __) => { /*NOTE: Do nothing here*/ });
+
+ viewOptions.CopyViewOptions(_config.PngViewOptions);
+
+ return viewOptions;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/MVC/src/Global.asax b/Demos/ASP.NET MVC/src/Global.asax
similarity index 54%
rename from Demos/MVC/src/Global.asax
rename to Demos/ASP.NET MVC/src/Global.asax
index 8c1c899fe..84e6facc2 100644
--- a/Demos/MVC/src/Global.asax
+++ b/Demos/ASP.NET MVC/src/Global.asax
@@ -1 +1 @@
-<%@ Application Codebehind="Global.asax.cs" Inherits="GroupDocs.Viewer.MVC.WebApiApplication" Language="C#" %>
+<%@ Application Codebehind="Global.asax.cs" Inherits="GroupDocs.Viewer.AspNetMvc.MvcApplication" Language="C#" %>
diff --git a/Demos/ASP.NET MVC/src/Global.asax.cs b/Demos/ASP.NET MVC/src/Global.asax.cs
new file mode 100644
index 000000000..e79f7b294
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Global.asax.cs
@@ -0,0 +1,80 @@
+using GroupDocs.Viewer.AspNetMvc.Core.Configuration;
+using GroupDocs.Viewer.AspNetMvc.Core.FileTypeResolution;
+using GroupDocs.Viewer.AspNetMvc.Core.Licensing;
+using GroupDocs.Viewer.AspNetMvc.Core.PageFormatting;
+using GroupDocs.Viewer.AspNetMvc.Core.Viewers;
+using GroupDocs.Viewer.AspNetMvc.Core;
+using System.Web;
+using System.Web.Http;
+using System.Web.Routing;
+using GroupDocs.Viewer.AspNetMvc.Core.Caching;
+using GroupDocs.Viewer.AspNetMvc.Core.Storage;
+using Unity;
+using Unity.WebApi;
+
+namespace GroupDocs.Viewer.AspNetMvc
+{
+ public class MvcApplication : HttpApplication
+ {
+ protected void Application_Start()
+ {
+ UnityContainer container = new UnityContainer();
+ ConfigureServices(container);
+
+ GlobalConfiguration.Configuration.DependencyResolver =
+ new UnityDependencyResolver(container);
+
+ GlobalConfiguration.Configure(WebApiConfig.Register);
+ RouteConfig.RegisterRoutes(RouteTable.Routes);
+ }
+
+ private void ConfigureServices(UnityContainer container)
+ {
+ var viewerType = ViewerType.HtmlWithEmbeddedResources;
+
+ //Temporary license can be requested at https://purchase.groupdocs.com/temporary-license
+ var licensePath = Server.MapPath("~/GroupDocs.Viewer.lic");
+ var filesPath = Server.MapPath("~/Storage/Files");
+ var cachePath = Server.MapPath("~/Storage/Cache");
+
+ var uiConfig = UIConfig.Instance
+ .SetViewerType(viewerType);
+ var viewerConfig = ViewerConfig.Instance
+ .SetLicensePath(licensePath);
+
+ container.RegisterFactory(c => viewerConfig);
+ container.RegisterFactory(c => uiConfig);
+ container.RegisterFactory(c => new LocalFileStorage(filesPath));
+ container.RegisterFactory(c => new LocalFileCache(cachePath));
+ container.RegisterType();
+ container.RegisterType();
+ container.RegisterType();
+ container.RegisterType();
+
+ container.RegisterFactory(c =>
+ {
+ IViewer viewer;
+ switch (uiConfig.ViewerType)
+ {
+ case ViewerType.HtmlWithExternalResources:
+ viewer = c.Resolve();
+ break;
+ case ViewerType.Jpg:
+ viewer = c.Resolve();
+ break;
+ case ViewerType.Png:
+ viewer = c.Resolve();
+ break;
+ default:
+ viewer = c.Resolve();
+ break;
+ }
+
+ var fileCache = c.Resolve();
+ var asyncLock = c.Resolve();
+
+ return new CachingViewer(viewer, fileCache, asyncLock);
+ });
+ }
+ }
+}
diff --git a/Demos/ASP.NET MVC/src/GroupDocs.Viewer.AspNetMvc.csproj b/Demos/ASP.NET MVC/src/GroupDocs.Viewer.AspNetMvc.csproj
new file mode 100644
index 000000000..980b7f176
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/GroupDocs.Viewer.AspNetMvc.csproj
@@ -0,0 +1,295 @@
+
+
+
+
+ Debug
+ AnyCPU
+
+
+ 2.0
+ {C2FB838B-47FC-47BF-9532-C2D4DAAA58B7}
+ {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}
+ Library
+ Properties
+ GroupDocs.Viewer.AspNetMvc
+ GroupDocs.Viewer.AspNetMvc
+ v4.8
+ false
+ true
+
+ 44305
+
+
+
+
+
+ false
+ ClientApp\
+ true
+
+
+ true
+ full
+ false
+ bin\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ true
+ pdbonly
+ true
+ bin\
+ TRACE
+ prompt
+ 4
+
+
+
+ packages\GroupDocs.Viewer.22.9.0\lib\net40\GroupDocs.Viewer.dll
+
+
+ packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.3.6.0\lib\net45\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll
+
+
+
+ packages\Microsoft.Web.Infrastructure.2.0.0\lib\net40\Microsoft.Web.Infrastructure.dll
+
+
+ packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll
+
+
+
+
+
+ packages\Microsoft.AspNet.WebApi.Client.5.2.9\lib\net45\System.Net.Http.Formatting.dll
+
+
+ packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll
+
+
+ packages\Microsoft.AspNet.Cors.5.2.9\lib\net45\System.Web.Cors.dll
+
+
+
+
+
+
+
+
+ packages\Microsoft.AspNet.WebPages.3.2.9\lib\net45\System.Web.Helpers.dll
+
+
+ packages\Microsoft.AspNet.WebApi.Core.5.2.9\lib\net45\System.Web.Http.dll
+
+
+ packages\Microsoft.AspNet.WebApi.Cors.5.2.9\lib\net45\System.Web.Http.Cors.dll
+
+
+ packages\Microsoft.AspNet.WebApi.WebHost.5.2.9\lib\net45\System.Web.Http.WebHost.dll
+
+
+ packages\Microsoft.AspNet.Mvc.5.2.9\lib\net45\System.Web.Mvc.dll
+
+
+ packages\Microsoft.AspNet.Razor.3.2.9\lib\net45\System.Web.Razor.dll
+
+
+ packages\Microsoft.AspNet.WebPages.3.2.9\lib\net45\System.Web.WebPages.dll
+
+
+ packages\Microsoft.AspNet.WebPages.3.2.9\lib\net45\System.Web.WebPages.Deployment.dll
+
+
+ packages\Microsoft.AspNet.WebPages.3.2.9\lib\net45\System.Web.WebPages.Razor.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ packages\Unity.5.11.9\lib\net48\Unity.Abstractions.dll
+
+
+ packages\Unity.5.11.9\lib\net48\Unity.Container.dll
+
+
+ packages\Unity.WebAPI.5.4.0\lib\net45\Unity.WebApi.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Global.asax
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Web.config
+
+
+ Web.config
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 10.0
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+
+
+
+
+
+
+
+
+
+
+
+ True
+ True
+ 8080
+ /
+ https://localhost:44391/
+ False
+ False
+
+
+ False
+
+
+
+
+
+
+
+ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/GroupDocs.Viewer.AspNetMvc.sln b/Demos/ASP.NET MVC/src/GroupDocs.Viewer.AspNetMvc.sln
new file mode 100644
index 000000000..7cc86dc27
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/GroupDocs.Viewer.AspNetMvc.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.32510.428
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GroupDocs.Viewer.AspNetMvc", "GroupDocs.Viewer.AspNetMvc.csproj", "{C2FB838B-47FC-47BF-9532-C2D4DAAA58B7}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {C2FB838B-47FC-47BF-9532-C2D4DAAA58B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C2FB838B-47FC-47BF-9532-C2D4DAAA58B7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C2FB838B-47FC-47BF-9532-C2D4DAAA58B7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C2FB838B-47FC-47BF-9532-C2D4DAAA58B7}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {074D56D1-F6C6-4C9A-9D2B-6FA06AB48B29}
+ EndGlobalSection
+EndGlobal
diff --git a/Demos/ASP.NET MVC/src/Models/ErrorResponse.cs b/Demos/ASP.NET MVC/src/Models/ErrorResponse.cs
new file mode 100644
index 000000000..54e2013b1
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Models/ErrorResponse.cs
@@ -0,0 +1,18 @@
+using Newtonsoft.Json;
+
+namespace GroupDocs.Viewer.AspNetMvc.Models
+{
+ public class ErrorResponse
+ {
+ ///
+ /// The error message.
+ ///
+ [JsonProperty("message")]
+ public string Message { get; set; }
+
+ public ErrorResponse(string message)
+ {
+ this.Message = message;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Models/FileDescription.cs b/Demos/ASP.NET MVC/src/Models/FileDescription.cs
new file mode 100644
index 000000000..34da217f2
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Models/FileDescription.cs
@@ -0,0 +1,42 @@
+using Newtonsoft.Json;
+
+namespace GroupDocs.Viewer.AspNetMvc.Models
+{
+ public class FileDescription
+ {
+ ///
+ /// File unique ID.
+ ///
+ [JsonProperty("guid")]
+ public string Guid { get; }
+
+ ///
+ /// File file name.
+ ///
+ [JsonProperty("name")]
+ public string Name { get; }
+
+ ///
+ /// True when it is a directory.
+ ///
+ [JsonProperty("isDirectory")]
+ public bool IsDirectory { get; }
+
+ ///
+ /// Size in bytes.
+ ///
+ [JsonProperty("size")]
+ public long Size { get; }
+
+ ///
+ /// .ctor
+ ///
+ public FileDescription(string guid, string name, bool isDirectory, long size)
+ {
+ Guid = guid;
+ Name = name;
+ IsDirectory = isDirectory;
+ Size = size;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Models/LoadConfigResponse.cs b/Demos/ASP.NET MVC/src/Models/LoadConfigResponse.cs
new file mode 100644
index 000000000..d56a1b46b
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Models/LoadConfigResponse.cs
@@ -0,0 +1,121 @@
+using Newtonsoft.Json;
+
+namespace GroupDocs.Viewer.AspNetMvc.Models
+{
+ public class LoadConfigResponse
+ {
+ ///
+ /// Enables page selector control.
+ ///
+ [JsonProperty("pageSelector")]
+ public bool PageSelector { get; set; }
+
+ ///
+ /// Enables download button.
+ ///
+ [JsonProperty("download")]
+ public bool Download { get; set; }
+
+ ///
+ /// Enables upload.
+ ///
+ [JsonProperty("upload")]
+ public bool Upload { get; set; }
+
+ ///
+ /// Enables printing.
+ ///
+ [JsonProperty("print")]
+ public bool Print { get; set; }
+
+ ///
+ /// Enables file browser.
+ ///
+ [JsonProperty("browse")]
+ public bool Browse { get; set; }
+
+ ///
+ /// Enables file rewrite.
+ ///
+ [JsonProperty("rewrite")]
+ public bool Rewrite { get; set; }
+
+ ///
+ /// Enables right click.
+ ///
+ [JsonProperty("enableRightClick")]
+ public bool EnableRightClick { get; set; }
+
+ ///
+ /// The default document to view.
+ ///
+ [JsonProperty("defaultDocument")]
+ public string DefaultDocument { get; set; }
+
+ ///
+ /// Count pages to preload.
+ ///
+ [JsonProperty("preloadPageCount")]
+ public int PreloadPageCount { get; set; }
+
+ ///
+ /// Enables zoom.
+ ///
+ [JsonProperty("zoom")]
+ public bool Zoom { get; set; }
+
+ ///
+ /// Enables searching.
+ ///
+ [JsonProperty("search")]
+ public bool Search { get; set; }
+
+ ///
+ /// Enables thumbnails.
+ ///
+ [JsonProperty("thumbnails")]
+ public bool Thumbnails { get; set; }
+
+ ///
+ /// Image or HTML mode.
+ ///
+ [JsonProperty("htmlMode")]
+ public bool HtmlMode { get; set; }
+
+ ///
+ /// Enables printing
+ ///
+ [JsonProperty("printAllowed")]
+ public bool PrintAllowed { get; set; }
+
+ ///
+ /// Enables rotation
+ ///
+ [JsonProperty("rotate")]
+ public bool Rotate { get; set; }
+
+ ///
+ /// Enables saving of rotation state
+ ///
+ [JsonProperty("saveRotateState")]
+ public bool SaveRotateState { get; set; }
+
+ ///
+ /// Default language e.g. "en".
+ ///
+ [JsonProperty("defaultLanguage")]
+ public string DefaultLanguage { get; set; }
+
+ ///
+ /// Supported languages e.g. [ "en", "fr", "de" ]
+ ///
+ [JsonProperty("supportedLanguages")]
+ public string[] SupportedLanguages { get; set; }
+
+ ///
+ /// Enables language menu.
+ ///
+ [JsonProperty("showLanguageMenu")]
+ public bool ShowLanguageMenu { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Models/LoadDocumentDescriptionRequest.cs b/Demos/ASP.NET MVC/src/Models/LoadDocumentDescriptionRequest.cs
new file mode 100644
index 000000000..ab0ce624c
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Models/LoadDocumentDescriptionRequest.cs
@@ -0,0 +1,25 @@
+using Newtonsoft.Json;
+
+namespace GroupDocs.Viewer.AspNetMvc.Models
+{
+ public class LoadDocumentDescriptionRequest
+ {
+ ///
+ /// File unique ID.
+ ///
+ [JsonProperty("guid")]
+ public string Guid { get; set; }
+
+ ///
+ /// File type e.g "docx".
+ ///
+ [JsonProperty("fileType")]
+ public string FileType { get; set; }
+
+ ///
+ /// The password to open a document.
+ ///
+ [JsonProperty("password")]
+ public string Password { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Models/LoadDocumentDescriptionResponse.cs b/Demos/ASP.NET MVC/src/Models/LoadDocumentDescriptionResponse.cs
new file mode 100644
index 000000000..8c99a3d1e
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Models/LoadDocumentDescriptionResponse.cs
@@ -0,0 +1,32 @@
+using System.Collections.Generic;
+using Newtonsoft.Json;
+
+namespace GroupDocs.Viewer.AspNetMvc.Models
+{
+ public class LoadDocumentDescriptionResponse
+ {
+ ///
+ /// File unique ID.
+ ///
+ [JsonProperty("guid")]
+ public string Guid { get; set; }
+
+ ///
+ /// File type e.g "docx".
+ ///
+ [JsonProperty("fileType")]
+ public string FileType { get; set; }
+
+ ///
+ /// Indicates if printing of the document is allowed.
+ ///
+ [JsonProperty("printAllowed")]
+ public bool PrintAllowed { get; set; }
+
+ ///
+ /// Document pages.
+ ///
+ [JsonProperty("pages")]
+ public List Pages { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Models/LoadDocumentPageRequest.cs b/Demos/ASP.NET MVC/src/Models/LoadDocumentPageRequest.cs
new file mode 100644
index 000000000..58d842853
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Models/LoadDocumentPageRequest.cs
@@ -0,0 +1,31 @@
+using Newtonsoft.Json;
+
+namespace GroupDocs.Viewer.AspNetMvc.Models
+{
+ public class LoadDocumentPageRequest
+ {
+ ///
+ /// File unique ID.
+ ///
+ [JsonProperty("guid")]
+ public string Guid { get; set; }
+
+ ///
+ /// File type e.g "docx".
+ ///
+ [JsonProperty("fileType")]
+ public string FileType { get; set; }
+
+ ///
+ /// The password to open a document.
+ ///
+ [JsonProperty("password")]
+ public string Password { get; set; }
+
+ ///
+ /// The page to return.
+ ///
+ [JsonProperty("page")]
+ public int Page { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Models/LoadDocumentPagesRequest.cs b/Demos/ASP.NET MVC/src/Models/LoadDocumentPagesRequest.cs
new file mode 100644
index 000000000..ea0516350
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Models/LoadDocumentPagesRequest.cs
@@ -0,0 +1,31 @@
+using Newtonsoft.Json;
+
+namespace GroupDocs.Viewer.AspNetMvc.Models
+{
+ public class LoadDocumentPagesRequest
+ {
+ ///
+ /// File unique ID.
+ ///
+ [JsonProperty("guid")]
+ public string Guid { get; set; }
+
+ ///
+ /// File type e.g. "docx".
+ ///
+ [JsonProperty("fileType")]
+ public string FileType { get; set; }
+
+ ///
+ /// The password to open a document.
+ ///
+ [JsonProperty("password")]
+ public string Password { get; set; }
+
+ ///
+ /// The pages to return.
+ ///
+ [JsonProperty("pages")]
+ public int[] Pages { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Models/LoadFileTreeRequest.cs b/Demos/ASP.NET MVC/src/Models/LoadFileTreeRequest.cs
new file mode 100644
index 000000000..3f7ee5ca4
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Models/LoadFileTreeRequest.cs
@@ -0,0 +1,13 @@
+using Newtonsoft.Json;
+
+namespace GroupDocs.Viewer.AspNetMvc.Models
+{
+ public class LoadFileTreeRequest
+ {
+ ///
+ /// Folder path.
+ ///
+ [JsonProperty("path")]
+ public string Path { get; set; } = string.Empty;
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Models/PageContent.cs b/Demos/ASP.NET MVC/src/Models/PageContent.cs
new file mode 100644
index 000000000..9f8ebab9d
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Models/PageContent.cs
@@ -0,0 +1,19 @@
+using Newtonsoft.Json;
+
+namespace GroupDocs.Viewer.AspNetMvc.Models
+{
+ public class PageContent
+ {
+ ///
+ /// Page number.
+ ///
+ [JsonProperty("number")]
+ public int Number { get; set; }
+
+ ///
+ /// Page contents. It can be HTML or base64-encoded image.
+ ///
+ [JsonProperty("data")]
+ public string Data { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Models/PageDescription.cs b/Demos/ASP.NET MVC/src/Models/PageDescription.cs
new file mode 100644
index 000000000..025f0649c
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Models/PageDescription.cs
@@ -0,0 +1,25 @@
+using Newtonsoft.Json;
+
+namespace GroupDocs.Viewer.AspNetMvc.Models
+{
+ public class PageDescription : PageContent
+ {
+ ///
+ /// Page with in pixels.
+ ///
+ [JsonProperty("width")]
+ public int Width { get; set; }
+
+ ///
+ /// Page height in pixels.
+ ///
+ [JsonProperty("height")]
+ public int Height { get; set; }
+
+ ///
+ /// Worksheet name for spreadsheets.
+ ///
+ [JsonProperty("sheetName")]
+ public string SheetName { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Models/PrintPdfRequest.cs b/Demos/ASP.NET MVC/src/Models/PrintPdfRequest.cs
new file mode 100644
index 000000000..63f5ceba8
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Models/PrintPdfRequest.cs
@@ -0,0 +1,25 @@
+using Newtonsoft.Json;
+
+namespace GroupDocs.Viewer.AspNetMvc.Models
+{
+ public class PrintPdfRequest
+ {
+ ///
+ /// Unique file ID.
+ ///
+ [JsonProperty("guid")]
+ public string Guid { get; set; }
+
+ ///
+ /// File type e.g. "docx".
+ ///
+ [JsonProperty("fileType")]
+ public string FileType { get; set; }
+
+ ///
+ /// Password to open the document.
+ ///
+ [JsonProperty("password")]
+ public string Password { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/Models/UploadFileResponse.cs b/Demos/ASP.NET MVC/src/Models/UploadFileResponse.cs
new file mode 100644
index 000000000..a4c087b12
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Models/UploadFileResponse.cs
@@ -0,0 +1,21 @@
+using Newtonsoft.Json;
+
+namespace GroupDocs.Viewer.AspNetMvc.Models
+{
+ public class UploadFileResponse
+ {
+ ///
+ /// Unique file ID.
+ ///
+ [JsonProperty("guid")]
+ public string Guid { get; }
+
+ ///
+ /// .ctor
+ ///
+ public UploadFileResponse(string filePath)
+ {
+ Guid = filePath;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/MVC/src/Properties/AssemblyInfo.cs b/Demos/ASP.NET MVC/src/Properties/AssemblyInfo.cs
similarity index 73%
rename from Demos/MVC/src/Properties/AssemblyInfo.cs
rename to Demos/ASP.NET MVC/src/Properties/AssemblyInfo.cs
index 175774fff..b8ad6f19d 100644
--- a/Demos/MVC/src/Properties/AssemblyInfo.cs
+++ b/Demos/ASP.NET MVC/src/Properties/AssemblyInfo.cs
@@ -1,35 +1,35 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-// General Information about an assembly is controlled through the following
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyTitle("GroupDocs.Viewer MVC")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("Aspose Pty Ltd")]
-[assembly: AssemblyProduct("GroupDocs.Viewer MVC")]
-[assembly: AssemblyCopyright("2001-2018 Aspose Pty Ltd")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
-// COM, set the ComVisible attribute to true on that type.
-[assembly: ComVisible(false)]
-
-// The following GUID is for the ID of the typelib if this project is exposed to COM
-[assembly: Guid("cb1d0987-217d-43e0-afff-fae210c23d96")]
-
-// Version information for an assembly consists of the following four values:
-//
-// Major Version
-// Minor Version
-// Build Number
-// Revision
-//
-// You can specify all the values or you can default the Revision and Build Numbers
-// by using the '*' as shown below:
-[assembly: AssemblyVersion("1.20.0.0")]
-[assembly: AssemblyFileVersion("1.20.0.0")]
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("GroupDocs.Viewer.AspNetMvc")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("GroupDocs.Viewer.AspNetMvc")]
+[assembly: AssemblyCopyright("Copyright © 2022")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("4647bffb-d10b-4211-8782-2e9d3f57166e")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Revision and Build Numbers
+// by using the '*' as shown below:
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Demos/MVC/src/Licenses/.gitkeep b/Demos/ASP.NET MVC/src/Storage/Cache/.gitkeep
similarity index 100%
rename from Demos/MVC/src/Licenses/.gitkeep
rename to Demos/ASP.NET MVC/src/Storage/Cache/.gitkeep
diff --git a/Demos/ASP.NET MVC/src/Storage/Files/flowchart.vsdx b/Demos/ASP.NET MVC/src/Storage/Files/flowchart.vsdx
new file mode 100644
index 000000000..12db7e009
Binary files /dev/null and b/Demos/ASP.NET MVC/src/Storage/Files/flowchart.vsdx differ
diff --git a/Demos/ASP.NET MVC/src/Storage/Files/groupdocs.pptx b/Demos/ASP.NET MVC/src/Storage/Files/groupdocs.pptx
new file mode 100644
index 000000000..d94d696dd
Binary files /dev/null and b/Demos/ASP.NET MVC/src/Storage/Files/groupdocs.pptx differ
diff --git a/Demos/ASP.NET MVC/src/Storage/Files/house-plan.dwg b/Demos/ASP.NET MVC/src/Storage/Files/house-plan.dwg
new file mode 100644
index 000000000..ff07697b5
Binary files /dev/null and b/Demos/ASP.NET MVC/src/Storage/Files/house-plan.dwg differ
diff --git a/Demos/ASP.NET MVC/src/Storage/Files/invoice.xlsx b/Demos/ASP.NET MVC/src/Storage/Files/invoice.xlsx
new file mode 100644
index 000000000..f223175b2
Binary files /dev/null and b/Demos/ASP.NET MVC/src/Storage/Files/invoice.xlsx differ
diff --git a/Demos/ASP.NET MVC/src/Storage/Files/resume.docx b/Demos/ASP.NET MVC/src/Storage/Files/resume.docx
new file mode 100644
index 000000000..1d9f67edb
Binary files /dev/null and b/Demos/ASP.NET MVC/src/Storage/Files/resume.docx differ
diff --git a/Demos/ASP.NET MVC/src/Storage/Files/resume.pdf b/Demos/ASP.NET MVC/src/Storage/Files/resume.pdf
new file mode 100644
index 000000000..ef4cae4db
Binary files /dev/null and b/Demos/ASP.NET MVC/src/Storage/Files/resume.pdf differ
diff --git a/Demos/ASP.NET MVC/src/Storage/Files/software-development-plan.mpp b/Demos/ASP.NET MVC/src/Storage/Files/software-development-plan.mpp
new file mode 100644
index 000000000..5e48341a2
Binary files /dev/null and b/Demos/ASP.NET MVC/src/Storage/Files/software-development-plan.mpp differ
diff --git a/Demos/ASP.NET MVC/src/Storage/Files/vector-image.svg b/Demos/ASP.NET MVC/src/Storage/Files/vector-image.svg
new file mode 100644
index 000000000..6df8640de
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Storage/Files/vector-image.svg
@@ -0,0 +1,425 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Demos/ASP.NET MVC/src/Views/Viewer/Index.cshtml b/Demos/ASP.NET MVC/src/Views/Viewer/Index.cshtml
new file mode 100644
index 000000000..1708d9c0b
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Views/Viewer/Index.cshtml
@@ -0,0 +1,23 @@
+@using GroupDocs.Viewer.AspNetMvc.Core
+@{
+ var apiEndpoint = $"/{Constants.API_PATH}";
+ var uiSettingsPath = $"/{Constants.API_PATH}/{Constants.LOAD_CONFIG_ACTION_NAME}";
+}
+
+
+
+
+
+ GroupDocs.Viewer for .NET ASP.NET MVC Demo
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demos/MVC/src/Views/Web.config b/Demos/ASP.NET MVC/src/Views/Web.config
similarity index 83%
rename from Demos/MVC/src/Views/Web.config
rename to Demos/ASP.NET MVC/src/Views/Web.config
index 48d2d9d7f..b2c758bef 100644
--- a/Demos/MVC/src/Views/Web.config
+++ b/Demos/ASP.NET MVC/src/Views/Web.config
@@ -1,43 +1,42 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Demos/ASP.NET MVC/src/Web.Debug.config b/Demos/ASP.NET MVC/src/Web.Debug.config
new file mode 100644
index 000000000..d7712aaf1
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Web.Debug.config
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/Demos/ASP.NET MVC/src/Web.Release.config b/Demos/ASP.NET MVC/src/Web.Release.config
new file mode 100644
index 000000000..28a4d5fcc
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/Web.Release.config
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Demos/MVC/src/Web.config b/Demos/ASP.NET MVC/src/Web.config
similarity index 52%
rename from Demos/MVC/src/Web.config
rename to Demos/ASP.NET MVC/src/Web.config
index ff7ad2dae..50ef64a20 100644
--- a/Demos/MVC/src/Web.config
+++ b/Demos/ASP.NET MVC/src/Web.config
@@ -1,179 +1,136 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demos/ASP.NET MVC/src/packages.config b/Demos/ASP.NET MVC/src/packages.config
new file mode 100644
index 000000000..0d61d9edf
--- /dev/null
+++ b/Demos/ASP.NET MVC/src/packages.config
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demos/WebForms/.gitattributes b/Demos/ASP.NET Web Forms/.gitattributes
similarity index 100%
rename from Demos/WebForms/.gitattributes
rename to Demos/ASP.NET Web Forms/.gitattributes
diff --git a/Demos/WebForms/.gitignore b/Demos/ASP.NET Web Forms/.gitignore
similarity index 100%
rename from Demos/WebForms/.gitignore
rename to Demos/ASP.NET Web Forms/.gitignore
diff --git a/Demos/MVC/LICENSE b/Demos/ASP.NET Web Forms/LICENSE
similarity index 98%
rename from Demos/MVC/LICENSE
rename to Demos/ASP.NET Web Forms/LICENSE
index 23998b433..af0a3dfc5 100644
--- a/Demos/MVC/LICENSE
+++ b/Demos/ASP.NET Web Forms/LICENSE
@@ -1,21 +1,21 @@
-MIT License
-
-Copyright (c) 2001-2018 Aspose Pty Ltd
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+MIT License
+
+Copyright (c) 2001-2018 Aspose Pty Ltd
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Demos/ASP.NET Web Forms/README.md b/Demos/ASP.NET Web Forms/README.md
new file mode 100644
index 000000000..aba8fbbd4
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/README.md
@@ -0,0 +1,69 @@
+![Alt text](https://raw.githubusercontent.com/groupdocs-viewer/groupdocs-viewer.github.io/master/resources/image/banner.png "GroupDocs.Viewer")
+
+# GroupDocs.Viewer for .NET ASP.NET Web Forms Demo
+
+![GitHub](https://img.shields.io/github/license/groupdocs-viewer/GroupDocs.Viewer-for-.NET)
+
+In order to demonstrate [GroupDocs.Viewer for .NET](https://products.groupdocs.com/viewer/net) reach and powerful features we prepared **document viewer** demo. Which can be used as a standalone application or easily integrated into your project.
+
+## System Requirements
+- .NET Framework 4.8
+- Visual Studio 2022
+
+## Supported Document Formats
+
+GroupDocs.Viewer for .NET enables you to render Microsoft Word, Microsoft Excel, Microsoft PowerPoint, and many more file types in HTML, PDF, PNG, and JPEG formats. The complete list of the supported document and file formats can be found in the [Supported document formats](https://docs.groupdocs.com/viewer/net/supported-document-formats/) documentation article.
+
+## Demo Video
+
+
+
+
+
+
+
+## Features
+- Responsive design
+- Fully customizable navigation panel & thumbnails
+- Text searching & highlighting
+- Download & upload documents
+- Print document as PDF
+- Zoom in/out documents without quality loss in HTML mode
+- Smooth page navigation & scrolling
+- Preload pages for faster document rendering
+- Multi-language support
+
+## How To Run
+
+Download the [source code](https://github.com/groupdocs-viewer/GroupDocs.Viewer-for-.NET/archive/master.zip) from github or clone this repository:
+
+```bash
+git clone https://github.com/groupdocs-viewer/GroupDocs.Viewer-for-.NET
+```
+
+Navigate to `Demos\ASP.NET Web Forms\src` and open `GroupDocs.Viewer.AspNetWebForms.sln` in Visual Studio. Build and run the project.
+
+Open the app by navigating to in your favorite browser.
+
+## Configuration
+
+You can configure this demo project in [Global.asax.cs](./src/Global.asax.cs) file in `ConfigureServices` method which includes configuration for GroupDocs.Viewer and UI.
+
+NOTE: without a license application will run in trial mode, [purchase a license](https://purchase.groupdocs.com/buy) or [request a temporary license](https://purchase.groupdocs.com/temporary-license).
+
+## License
+
+The MIT License (MIT).
+
+Please have a look at the [LICENSE](LICENSE) for more details
+
+## More Demo Projects
+
+- [Java Dropwizard Demo](https://github.com/groupdocs-viewer/GroupDocs.Viewer-for-Java/tree/master/Demos/Dropwizard)
+- [Java Spring Demo](https://github.com/groupdocs-viewer/GroupDocs.Viewer-for-Java/tree/master/Demos/Spring)
+- [ASP.NET Core Demo](https://github.com/groupdocs-viewer/GroupDocs.Viewer-for-.NET/tree/master/Demos/ASP.NET%20Core)
+- [ASP.NET MVC Demo](https://github.com/groupdocs-viewer/GroupDocs.Viewer-for-.NET/tree/master/Demos/ASP.NET%20MVC)
+- [Windows Forms Demo](https://github.com/groupdocs-viewer/GroupDocs.Viewer-for-.NET/tree/master/Demos/Windows%20Forms)
+- [WPF Demo](https://github.com/groupdocs-viewer/GroupDocs.Viewer-for-.NET/tree/master/Demos/WPF)
+
+[Home](https://www.groupdocs.com/) | [Product Page](https://products.groupdocs.com/viewer/net) | [Documentation](https://docs.groupdocs.com/viewer/net/) | [Demo](https://products.groupdocs.app/viewer/total) | [API Reference](https://apireference.groupdocs.com/net/viewer) | [Examples](https://github.com/groupdocs-viewer/GroupDocs.Viewer-for-.NET/tree/master/Examples) | [Blog](https://blog.groupdocs.com/category/viewer/) | [Free Support](https://forum.groupdocs.com/c/viewer) | [Temporary License](https://purchase.groupdocs.com/temporary-license)
diff --git a/Demos/ASP.NET Web Forms/src/ActionResults/FileActionResult.cs b/Demos/ASP.NET Web Forms/src/ActionResults/FileActionResult.cs
new file mode 100644
index 000000000..1e56f8103
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/ActionResults/FileActionResult.cs
@@ -0,0 +1,50 @@
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Web.Http;
+
+namespace GroupDocs.Viewer.AspNetWebForms.ActionResults
+{
+ internal class FileActionResult : IHttpActionResult
+ {
+ private readonly byte[] _data;
+ private readonly string _fileName;
+ private readonly string _contentType;
+ readonly HttpRequestMessage _request;
+
+ public FileActionResult(byte[] data, string fileName, string contentType,
+ HttpRequestMessage request)
+ {
+ _data = data;
+ _fileName = fileName;
+ _contentType = contentType;
+ _request = request;
+ }
+
+ public Task ExecuteAsync(CancellationToken cancellationToken)
+ {
+ var response = new HttpResponseMessage
+ {
+ Content = new ByteArrayContent(_data),
+ StatusCode = HttpStatusCode.OK,
+ RequestMessage = _request
+ };
+
+ var contentType = string.IsNullOrEmpty(_contentType)
+ ? "application/octet-stream"
+ : _contentType;
+
+ response.Content.Headers.ContentType =
+ new MediaTypeHeaderValue(contentType);
+ response.Content.Headers.ContentDisposition =
+ new ContentDispositionHeaderValue("attachment")
+ {
+ FileName = _fileName
+ };
+
+ return Task.FromResult(response);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET Web Forms/src/ActionResults/JsonActionResult.cs b/Demos/ASP.NET Web Forms/src/ActionResults/JsonActionResult.cs
new file mode 100644
index 000000000..8d2eb0b8a
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/ActionResults/JsonActionResult.cs
@@ -0,0 +1,39 @@
+using System.Net;
+using System.Net.Http;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Web.Http;
+using Newtonsoft.Json;
+
+namespace GroupDocs.Viewer.AspNetWebForms.ActionResults
+{
+ internal class JsonActionResult : IHttpActionResult
+ {
+ readonly object _value;
+ public HttpStatusCode StatusCode { get; set; } = HttpStatusCode.OK;
+
+ readonly HttpRequestMessage _request;
+
+ public JsonActionResult(object value, HttpRequestMessage request)
+ {
+ _value = value;
+ _request = request;
+ }
+
+ public Task ExecuteAsync(CancellationToken cancellationToken)
+ {
+ var json = JsonConvert.SerializeObject(_value, Formatting.Indented);
+ var content = new StringContent(json, Encoding.UTF8, "application/json");
+
+ var response = new HttpResponseMessage
+ {
+ Content = content,
+ StatusCode = StatusCode,
+ RequestMessage = _request
+ };
+
+ return Task.FromResult(response);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET Web Forms/src/ActionResults/ResourceActionResult.cs b/Demos/ASP.NET Web Forms/src/ActionResults/ResourceActionResult.cs
new file mode 100644
index 000000000..7a7f313f9
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/ActionResults/ResourceActionResult.cs
@@ -0,0 +1,40 @@
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Web.Http;
+
+namespace GroupDocs.Viewer.AspNetWebForms.ActionResults
+{
+ internal class ResourceActionResult : IHttpActionResult
+ {
+ private readonly byte[] _data;
+ private readonly string _contentType;
+ readonly HttpRequestMessage _request;
+
+ public ResourceActionResult(byte[] data, string contentType, HttpRequestMessage request)
+ {
+ _data = data;
+ _contentType = contentType;
+ _request = request;
+ }
+
+ public Task ExecuteAsync(CancellationToken cancellationToken)
+ {
+ var response = new HttpResponseMessage
+ {
+ Content = new ByteArrayContent(_data),
+ StatusCode = HttpStatusCode.OK,
+ RequestMessage = _request
+ };
+
+ response.Content.Headers.ContentType =
+ new MediaTypeHeaderValue(_contentType);
+ response.Content.Headers.ContentDisposition
+ = new ContentDispositionHeaderValue("inline");
+
+ return Task.FromResult(response);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/MVC/src/App_Start/WebApiConfig.cs b/Demos/ASP.NET Web Forms/src/App_Start/WebApiConfig.cs
similarity index 78%
rename from Demos/MVC/src/App_Start/WebApiConfig.cs
rename to Demos/ASP.NET Web Forms/src/App_Start/WebApiConfig.cs
index df8d9156e..c5d261b34 100644
--- a/Demos/MVC/src/App_Start/WebApiConfig.cs
+++ b/Demos/ASP.NET Web Forms/src/App_Start/WebApiConfig.cs
@@ -1,16 +1,14 @@
-using System.Web.Http;
-
-namespace GroupDocs.Viewer.MVC
-{
- public static class WebApiConfig
- {
- public static void Register(HttpConfiguration config)
- {
- // enable CORS
- config.EnableCors();
-
- // Web API routes
- config.MapHttpAttributeRoutes();
- }
- }
-}
+using System.Web.Http;
+
+namespace GroupDocs.Viewer.AspNetWebForms
+{
+ public static class WebApiConfig
+ {
+ public static void Register(HttpConfiguration config)
+ {
+ // Web API routes
+ config.EnableCors();
+ config.MapHttpAttributeRoutes();
+ }
+ }
+}
diff --git a/Demos/ASP.NET Web Forms/src/ClientApp/.browserslistrc b/Demos/ASP.NET Web Forms/src/ClientApp/.browserslistrc
new file mode 100644
index 000000000..427441dc9
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/ClientApp/.browserslistrc
@@ -0,0 +1,17 @@
+# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
+# For additional information regarding the format and rule options, please see:
+# https://github.com/browserslist/browserslist#queries
+
+# For the full list of supported browsers by the Angular framework, please see:
+# https://angular.io/guide/browser-support
+
+# You can see what browsers were selected by your queries by running:
+# npx browserslist
+
+last 1 Chrome version
+last 1 Firefox version
+last 2 Edge major versions
+last 2 Safari major versions
+last 2 iOS major versions
+Firefox ESR
+not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line.
diff --git a/Demos/MVC/src/client/.gitignore b/Demos/ASP.NET Web Forms/src/ClientApp/.gitignore
similarity index 79%
rename from Demos/MVC/src/client/.gitignore
rename to Demos/ASP.NET Web Forms/src/ClientApp/.gitignore
index ee5c9d833..86d943a9b 100644
--- a/Demos/MVC/src/client/.gitignore
+++ b/Demos/ASP.NET Web Forms/src/ClientApp/.gitignore
@@ -4,10 +4,16 @@
/dist
/tmp
/out-tsc
+# Only exists if Bazel was run
+/bazel-out
# dependencies
/node_modules
+# profiling files
+chrome-profiler-events*.json
+speed-measure-plugin*.json
+
# IDEs and editors
/.idea
.project
@@ -23,6 +29,7 @@
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
+.history/*
# misc
/.sass-cache
diff --git a/Demos/ASP.NET Web Forms/src/ClientApp/README.md b/Demos/ASP.NET Web Forms/src/ClientApp/README.md
new file mode 100644
index 000000000..daf9ae209
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/ClientApp/README.md
@@ -0,0 +1,19 @@
+# GroupdocsViewerUI
+
+This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 11.2.3.
+
+## Development server
+
+Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
+
+## Code scaffolding
+
+Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
+
+## Build
+
+Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.
+
+## Further help
+
+To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
diff --git a/Demos/ASP.NET Web Forms/src/ClientApp/angular.json b/Demos/ASP.NET Web Forms/src/ClientApp/angular.json
new file mode 100644
index 000000000..55575bfe2
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/ClientApp/angular.json
@@ -0,0 +1,116 @@
+{
+ "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
+ "version": 1,
+ "newProjectRoot": "projects",
+ "projects": {
+ "groupdocs-viewer-ui": {
+ "projectType": "application",
+ "schematics": {
+ "@schematics/angular:component": {
+ "inlineTemplate": true,
+ "inlineStyle": true,
+ "skipTests": true
+ },
+ "@schematics/angular:class": {
+ "skipTests": true
+ },
+ "@schematics/angular:directive": {
+ "skipTests": true
+ },
+ "@schematics/angular:guard": {
+ "skipTests": true
+ },
+ "@schematics/angular:interceptor": {
+ "skipTests": true
+ },
+ "@schematics/angular:module": {
+ "skipTests": true
+ },
+ "@schematics/angular:pipe": {
+ "skipTests": true
+ },
+ "@schematics/angular:service": {
+ "skipTests": true
+ },
+ "@schematics/angular:application": {
+ "strict": true
+ }
+ },
+ "root": "",
+ "sourceRoot": "src",
+ "prefix": "app",
+ "architect": {
+ "build": {
+ "builder": "@angular-devkit/build-angular:browser",
+ "options": {
+ "outputPath": "dist",
+ "index": "src/index.html",
+ "main": "src/main.ts",
+ "polyfills": "src/polyfills.ts",
+ "tsConfig": "tsconfig.app.json",
+ "aot": true,
+ "assets": [
+ "src/assets"
+ ],
+ "styles": [],
+ "scripts": [],
+ "allowedCommonJsDependencies": [
+ "hammerjs", "jquery"
+ ]
+ },
+ "configurations": {
+ "production": {
+ "fileReplacements": [
+ {
+ "replace": "src/environments/environment.ts",
+ "with": "src/environments/environment.prod.ts"
+ }
+ ],
+ "index": {
+ "input": "src/index.prod.html",
+ "output": "index.html"
+ },
+ "outputPath": "../assets",
+ "optimization": true,
+ "outputHashing": "none",
+ "sourceMap": false,
+ "namedChunks": false,
+ "extractLicenses": false,
+ "vendorChunk": false,
+ "buildOptimizer": true,
+ "budgets": [
+ {
+ "type": "initial",
+ "maximumWarning": "4000kb",
+ "maximumError": "5000kb"
+ },
+ {
+ "type": "anyComponentStyle",
+ "maximumWarning": "4kb",
+ "maximumError": "5kb"
+ }
+ ]
+ }
+ }
+ },
+ "serve": {
+ "builder": "@angular-devkit/build-angular:dev-server",
+ "options": {
+ "browserTarget": "groupdocs-viewer-ui:build"
+ },
+ "configurations": {
+ "production": {
+ "browserTarget": "groupdocs-viewer-ui:build:production"
+ }
+ }
+ },
+ "extract-i18n": {
+ "builder": "@angular-devkit/build-angular:extract-i18n",
+ "options": {
+ "browserTarget": "groupdocs-viewer-ui:build"
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Demos/ASP.NET Web Forms/src/ClientApp/package.json b/Demos/ASP.NET Web Forms/src/ClientApp/package.json
new file mode 100644
index 000000000..e6ff78140
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/ClientApp/package.json
@@ -0,0 +1,31 @@
+{
+ "name": "groupdocs-viewer-ui",
+ "version": "1.0.0",
+ "scripts": {
+ "ng": "ng",
+ "start": "ng serve",
+ "build": "ng build --configuration=production"
+ },
+ "private": true,
+ "dependencies": {
+ "@angular/animations": "^14.1.0",
+ "@angular/common": "^14.1.0",
+ "@angular/compiler": "^14.1.0",
+ "@angular/core": "^14.1.0",
+ "@angular/forms": "^14.1.0",
+ "@angular/platform-browser": "^14.1.0",
+ "@angular/platform-browser-dynamic": "^14.1.0",
+ "@angular/router": "^14.1.0",
+ "@groupdocs.examples.angular/viewer": "^0.8.92",
+ "rxjs": "^7.5.6",
+ "tslib": "^2.4.0",
+ "zone.js": "^0.11.7"
+ },
+ "devDependencies": {
+ "@angular-devkit/build-angular": "^14.1.0",
+ "@angular/cli": "^14.1.0",
+ "@angular/compiler-cli": "^14.1.0",
+ "@types/node": "^18.0.6",
+ "typescript": "^4.7.4"
+ }
+}
diff --git a/Demos/ASP.NET Web Forms/src/ClientApp/src/app/app.component.html b/Demos/ASP.NET Web Forms/src/ClientApp/src/app/app.component.html
new file mode 100644
index 000000000..be08b9bd6
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/ClientApp/src/app/app.component.html
@@ -0,0 +1,109 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{currentPage}}/{{countPages}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{'Present' | translate}}
+
+
+
+
+
+ Viewer
+ {{getFileName()}}
+
+
+
+
+ {{'Stop' | translate}}
+
+
+
+
+
+
+
+
+
+
+
+
+ = 10 ? 'seconds-remaining two-digits' : 'seconds-remaining'">{{secondsLeft}}
+
+
+
+
+
+
+
+ {{'Click' | translate}} {{'to open file' | translate}}
+ {{'Or drop file here' | translate}}
+
+
+
+
+
+
+
diff --git a/Demos/ASP.NET Web Forms/src/ClientApp/src/app/app.component.less b/Demos/ASP.NET Web Forms/src/ClientApp/src/app/app.component.less
new file mode 100644
index 000000000..5bf4280e9
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/ClientApp/src/app/app.component.less
@@ -0,0 +1,256 @@
+@import (css) url('https://fonts.googleapis.com/css?family=Open+Sans&display=swap');
+@import "./variables";
+
+:host * {
+ font-family: 'Open Sans', Arial, Helvetica, sans-serif;
+}
+
+.noselect {
+ -webkit-touch-callout: none; /* iOS Safari */
+ -webkit-user-select: none; /* Safari */
+ -khtml-user-select: none; /* Konqueror HTML */
+ -moz-user-select: none; /* Old versions of Firefox */
+ -ms-user-select: none; /* Internet Explorer/Edge */
+ user-select: none; /* Non-prefixed version, currently
+ supported by Chrome, Edge, Opera and Firefox */
+}
+
+.current-page-number {
+ margin-left: 7px;
+ font-size : 14px;
+ color : @regent-gray;
+ width : 37px;
+ height : 37px;
+ line-height: 37px;
+ text-align : center;
+
+ &.active {
+ color: #fff;
+ }
+}
+
+.wrapper {
+ align-items: stretch;
+ height : 100%;
+ width : 100%;
+ position : fixed;
+ top : 0;
+ bottom : 0;
+ left : 0;
+ right : 0;
+}
+
+.doc-panel {
+ display : flex;
+ height : calc(100vh - 60px);
+ flex-direction: row;
+}
+
+.top-panel {
+ display : flex;
+ align-items: center;
+ width : 100%;
+}
+
+.toolbar-panel {
+ background-color: @nav-main-background;
+ width : 100%;
+}
+
+.toolbar-panel-right {
+ display: flex;
+ flex: 1;
+ place-content: flex-end;
+}
+
+.btn-right {
+ margin-right: 7px;
+}
+
+.smp-start-stop {
+ ::ng-deep .button {
+ flex-direction: row;
+ border: 1px solid;
+ border-radius: 5px;
+ padding: 0px 10px !important;
+ }
+}
+
+.language-menu {
+ margin-right: 15px;
+}
+
+.select-language-menu {
+ ::ng-deep .select {
+ width: 100%;
+ ::ng-deep .dropdown-menu {
+ overflow-y: scroll;
+ max-height: 90%;
+ }
+ }
+
+ ::ng-deep .selected-value {
+ max-width: 100%;
+ }
+}
+
+.thumbnails-button {
+ ::ng-deep .button {
+ margin-left: 0 !important;
+ }
+}
+
+
+::ng-deep .tools {
+
+ .button,
+ .selected-value,
+ .nav-caret {
+ color: #fff !important;
+
+ &.inactive {
+ color: @regent-gray !important;
+ }
+ }
+
+ .button {
+ flex-flow: column;
+ }
+
+ .select-left {
+ .select {
+ position: relative;
+ }
+
+ .dropdown-menu {
+ top: 40px;
+ left: 0px;
+ }
+ }
+
+ .select-right {
+ .select {
+ position: relative;
+ }
+
+ .dropdown-menu {
+ top: 40px;
+ right: 0px;
+ }
+ }
+
+ .dropdown-menu .option {
+ color: @dove-gray !important;
+ }
+
+ .dropdown-menu .option:hover {
+ background-color: @folder !important;
+ }
+
+ .icon-button {
+ margin: 0px 0px 0px 15px !important;
+ }
+
+ .select {
+ width : 37px;
+ height : 37px;
+ margin-left: 7px;
+ line-height: 37px;
+ text-align : center;
+ }
+
+ .slides-title {
+ color: white;
+ padding-left: 12px;
+ font-size: 18px;
+ }
+
+ .slides-filename {
+ flex-grow: 1;
+ text-align: center;
+ color: white;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ padding-left: 20px;
+ overflow: hidden;
+ }
+
+ .slides-buttons {
+ display: flex;
+ ::ng-deep .select {
+ color: white;
+ cursor: pointer;
+ }
+ }
+
+ ::ng-deep .gd-nav-search-container
+ .icon-button {
+ margin: 0 0 0 7px !important;
+ }
+}
+
+.slides-nav {
+ position: absolute;
+ right: 30px;
+ bottom: 30px;
+ display: flex;
+ ::ng-deep .button {
+ font-size: 37px;
+ background-color: @porcelain;
+ border-radius: 3px;
+ }
+ ::ng-deep .timer {
+ font-size: 42px;
+ line-height: 6px;
+ color: @regent-gray;
+ position: relative;
+ .seconds-remaining {
+ position: absolute;
+ margin-left: 5px;
+ font-size: 16px;
+ top: 18px;
+ left: 12px;
+ &.two-digits {
+ left: 6px !important;
+ }
+ }
+ }
+}
+
+::ng-deep .page.presentation .gd-wrapper {
+ pointer-events: none;
+}
+
+@media (max-width: 1037px) {
+
+ .mobile-hide,
+ .current-page-number {
+ display: none;
+ }
+
+ ::ng-deep .tools {
+ gd-button:nth-child(1)>.icon-button {
+ margin: 0px 0px 0px 10px !important;
+ }
+
+ .icon-button {
+ height: 60px;
+ width : 60px;
+ }
+
+ .gd-nav-search-btn {
+ .icon-button {
+ height: 37px;
+ width : 37px;
+ }
+
+ .button {
+ font-size: 14px;
+ }
+ }
+
+ .gd-nav-search-container {
+ top: 59px !important;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET Web Forms/src/ClientApp/src/app/app.component.ts b/Demos/ASP.NET Web Forms/src/ClientApp/src/app/app.component.ts
new file mode 100644
index 000000000..54b9deeda
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/ClientApp/src/app/app.component.ts
@@ -0,0 +1,122 @@
+import { Component, ChangeDetectorRef } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+
+import { ViewerAppComponent, ViewerService, ViewerConfigService } from '@groupdocs.examples.angular/viewer';
+import { Api, ConfigService, ModalService, UploadFilesService, NavigateService, ZoomService, PagePreloadService, RenderPrintService, PasswordService, WindowService, LoadingMaskService, PageModel, TypedFileCredentials } from '@groupdocs.examples.angular/common-components';
+
+import { TranslateService } from '@ngx-translate/core';
+
+@Component({
+ selector: 'app-root',
+ templateUrl: './app.component.html',
+ styleUrls: ['./app.component.less', './variables.less']
+})
+export class AppComponent extends ViewerAppComponent {
+
+ configService: ConfigService;
+ viewerService: ViewerService;
+ pagesLoading: number[];
+ http: HttpClient;
+
+ constructor(viewerService: ViewerService,
+ modalService: ModalService,
+ viewerConfigService: ViewerConfigService,
+ uploadFilesService: UploadFilesService,
+ navigateService: NavigateService,
+ zoomService: ZoomService,
+ pagePreloadService: PagePreloadService,
+ renderPrintService: RenderPrintService,
+ passwordService: PasswordService,
+ windowService: WindowService,
+ loadingMaskService: LoadingMaskService,
+ http: HttpClient,
+ configService: ConfigService,
+ cdr: ChangeDetectorRef,
+ translate: TranslateService) {
+
+ super(viewerService,
+ modalService,
+ viewerConfigService,
+ uploadFilesService,
+ navigateService,
+ zoomService,
+ pagePreloadService,
+ renderPrintService,
+ passwordService,
+ windowService,
+ loadingMaskService,
+ cdr,
+ translate);
+
+ this.configService = configService;
+ this.viewerService = viewerService;
+ this.pagesLoading = [];
+ this.http = http;
+ }
+
+ preloadPages(start: number, end: number) {
+ const pagesToLoad = [];
+ const isInitialLoad = start === 1;
+ const minPagesToLoad = this.viewerConfig.preloadPageCount;
+ const countPages = this.file.pages.length;
+ this.selectedPageNumber = 1;
+
+ if (isInitialLoad) {
+ this.pagesLoading = [];
+ }
+
+ for (let i = start; i <= end; i++) {
+ const page = this.file.pages.find(p => p.number === i);
+ if(page && page.data) {
+ continue;
+ }
+
+ if (this.pagesLoading.indexOf(i) === -1) {
+ this.pagesLoading.push(i);
+ pagesToLoad.push(i);
+ }
+ }
+
+ if (pagesToLoad.length > 0) {
+ const last = pagesToLoad[pagesToLoad.length - 1];
+ if (!isInitialLoad && pagesToLoad.length < minPagesToLoad) {
+ const addPages = minPagesToLoad - pagesToLoad.length;
+ for (let i = last; i < last + addPages; i++) {
+ const pageNumber = i + 1;
+
+ if (pageNumber <= countPages && this.pagesLoading.indexOf(pageNumber) === -1) {
+ pagesToLoad.push(pageNumber);
+ this.pagesLoading.push(pageNumber);
+ }
+ }
+ }
+
+ this.loadPages(this.credentials, pagesToLoad).subscribe((
+ (pages: any) => {
+ pages.forEach((page: PageModel) => {
+ const pageIndex = page.number - 1;
+ const currPage = this.file.pages[pageIndex];
+
+ if (currPage) {
+ currPage.data = page.data;
+ if (this.file.thumbnails[pageIndex]) {
+ this.file.thumbnails[pageIndex].data = page.data;
+ this.file.thumbnails[pageIndex].width = currPage.width;
+ this.file.thumbnails[pageIndex].height = currPage.height;
+ }
+ }
+ });
+ }
+ ));
+ }
+ }
+
+ loadPages(credentials: TypedFileCredentials, pages: number[]) {
+ return this.http.post(this.configService.getViewerApiEndpoint() + Api.LOAD_DOCUMENT_PAGE + "s", {
+ 'guid': credentials.guid,
+ 'fileType': credentials.fileType,
+ 'password': credentials.password,
+ 'pages': pages
+ }, Api.httpOptionsJson);
+ }
+}
diff --git a/Demos/ASP.NET Web Forms/src/ClientApp/src/app/app.module.ts b/Demos/ASP.NET Web Forms/src/ClientApp/src/app/app.module.ts
new file mode 100644
index 000000000..645a436d3
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/ClientApp/src/app/app.module.ts
@@ -0,0 +1,89 @@
+import { NgModule } from '@angular/core';
+import { BrowserModule } from '@angular/platform-browser';
+import { APP_BASE_HREF } from '@angular/common';
+
+import { AppComponent } from './app.component';
+import { ConfigService } from '@groupdocs.examples.angular/common-components';
+import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
+import { ViewerConfigService, ViewerModule } from '@groupdocs.examples.angular/viewer';
+import { BehaviorSubject, Observable } from 'rxjs';
+
+import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
+import { ViewerTranslateLoader } from "@groupdocs.examples.angular/viewer";
+
+declare global {
+ interface Window {
+ apiEndpoint: string;
+ uiSettingsPath: string;
+ }
+}
+
+/*
+export class StaticViewerConfigService {
+ public updatedConfig: Observable = new BehaviorSubject({
+ pageSelector: true,
+ download: true,
+ upload: true,
+ print: true,
+ browse: true,
+ rewrite: true,
+ enableRightClick: true,
+ filesDirectory: "",
+ fontsDirectory: "",
+ defaultDocument: "",
+ watermarkText: "",
+ preloadPageCount: 3,
+ zoom: true,
+ search: true,
+ thumbnails: true,
+ rotate: false,
+ htmlMode: true,
+ cache: true,
+ saveRotateState: false,
+ printAllowed: true,
+ showGridLines: true,
+ showLanguageMenu: true,
+ defaultLanguage: 'en',
+ supportedLanguages: ['en', 'fr', 'de']
+ }).asObservable();
+
+ load(): Promise {
+ return Promise.resolve();
+ }
+}
+*/
+
+export function configServiceFactory() {
+ let config = new ConfigService();
+ config.apiEndpoint = window.apiEndpoint;
+ config.getViewerApiEndpoint = () => window.apiEndpoint;
+ config.getConfigEndpoint = () => window.uiSettingsPath;
+ return config;
+}
+
+@NgModule({
+ declarations: [
+ AppComponent
+ ],
+ imports: [
+ BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),
+ ViewerModule,
+ FontAwesomeModule,
+ TranslateModule.forRoot({
+ loader: {
+ provide: TranslateLoader,
+ useClass: ViewerTranslateLoader
+ }
+ })
+ ],
+ providers: [
+ { provide: APP_BASE_HREF, useValue: '/' },
+ { provide: ConfigService, useFactory: configServiceFactory },
+/*
+ { provide: ViewerConfigService, useClass: StaticViewerConfigService },
+*/
+ { provide: 'WINDOW', useValue: window },
+ ],
+ bootstrap: [AppComponent]
+})
+export class AppModule { }
diff --git a/Demos/ASP.NET Web Forms/src/ClientApp/src/app/variables.less b/Demos/ASP.NET Web Forms/src/ClientApp/src/app/variables.less
new file mode 100644
index 000000000..6a8bedf1d
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/ClientApp/src/app/variables.less
@@ -0,0 +1,31 @@
+@wide-screen-down: ~'(max-width: 1199px)';
+@desktop-down: ~'(max-width: 991px)';
+@tablet-down: ~'(max-width: 767px)';
+@phone-down: ~'(max-width: 1037px)';
+@small-phone-down: ~'(max-width: 320px)';
+
+@nav-logo-background: #25c2d4;
+@nav-main-background: #3e4e5a;
+@nav-accent-background: #E5E5E5;
+@nav-tabs-height: 30px;
+@editor-nav-height: 90px;
+@nav-tabs-height-m: 60px;
+@nav-height: 60px;
+@nav-icon-size: 20px;
+
+@primary: #3E4E5A;
+@brand: #25c2d4;
+@silver-chalice: #acacac;
+@dove-gray: #6e6e6e;
+@regent-gray: #959da5;
+@wild-sand: #f4f4f4;
+@mercury: #e7e7e7;
+@silver: #c4c4c4;
+@porcelain: #EDF0F2;
+
+@pdf: #e04e4e;
+@word: #539CF0;
+@powerpoint: #e29e1e;
+@excel: #7cbc46;
+@image: #c375ed;
+@folder: #4b566c;
diff --git a/Demos/MVC/src/Resources/.gitkeep b/Demos/ASP.NET Web Forms/src/ClientApp/src/assets/.gitkeep
similarity index 100%
rename from Demos/MVC/src/Resources/.gitkeep
rename to Demos/ASP.NET Web Forms/src/ClientApp/src/assets/.gitkeep
diff --git a/Demos/WebForms/src/client/apps/viewer/src/environments/environment.prod.ts b/Demos/ASP.NET Web Forms/src/ClientApp/src/environments/environment.prod.ts
similarity index 100%
rename from Demos/WebForms/src/client/apps/viewer/src/environments/environment.prod.ts
rename to Demos/ASP.NET Web Forms/src/ClientApp/src/environments/environment.prod.ts
diff --git a/Demos/WebForms/src/client/apps/viewer/src/environments/environment.ts b/Demos/ASP.NET Web Forms/src/ClientApp/src/environments/environment.ts
similarity index 100%
rename from Demos/WebForms/src/client/apps/viewer/src/environments/environment.ts
rename to Demos/ASP.NET Web Forms/src/ClientApp/src/environments/environment.ts
diff --git a/Demos/ASP.NET Web Forms/src/ClientApp/src/favicon.ico b/Demos/ASP.NET Web Forms/src/ClientApp/src/favicon.ico
new file mode 100644
index 000000000..54fb2a9b6
Binary files /dev/null and b/Demos/ASP.NET Web Forms/src/ClientApp/src/favicon.ico differ
diff --git a/Demos/ASP.NET Web Forms/src/ClientApp/src/index.html b/Demos/ASP.NET Web Forms/src/ClientApp/src/index.html
new file mode 100644
index 000000000..0b3e826b6
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/ClientApp/src/index.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+ GroupDocs.Viewer UI
+
+
+
+
+
+
+
diff --git a/Demos/ASP.NET Web Forms/src/ClientApp/src/index.prod.html b/Demos/ASP.NET Web Forms/src/ClientApp/src/index.prod.html
new file mode 100644
index 000000000..e42db0a95
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/ClientApp/src/index.prod.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+ GroupDocs.Viewer UI
+
+
+
+
+
+
+
+
diff --git a/Demos/WebForms/src/client/apps/viewer/src/main.ts b/Demos/ASP.NET Web Forms/src/ClientApp/src/main.ts
similarity index 85%
rename from Demos/WebForms/src/client/apps/viewer/src/main.ts
rename to Demos/ASP.NET Web Forms/src/ClientApp/src/main.ts
index fa4e0aef3..c7b673cf4 100644
--- a/Demos/WebForms/src/client/apps/viewer/src/main.ts
+++ b/Demos/ASP.NET Web Forms/src/ClientApp/src/main.ts
@@ -8,6 +8,5 @@ if (environment.production) {
enableProdMode();
}
-platformBrowserDynamic()
- .bootstrapModule(AppModule)
+platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));
diff --git a/Demos/WebForms/src/client/apps/viewer/src/polyfills.ts b/Demos/ASP.NET Web Forms/src/ClientApp/src/polyfills.ts
similarity index 94%
rename from Demos/WebForms/src/client/apps/viewer/src/polyfills.ts
rename to Demos/ASP.NET Web Forms/src/ClientApp/src/polyfills.ts
index 2f258e56c..d5f67bd91 100644
--- a/Demos/WebForms/src/client/apps/viewer/src/polyfills.ts
+++ b/Demos/ASP.NET Web Forms/src/ClientApp/src/polyfills.ts
@@ -18,7 +18,9 @@
* BROWSER POLYFILLS
*/
-/** IE10 and IE11 requires the following for NgClass support on SVG elements */
+/**
+ * IE11 requires the following for NgClass support on SVG elements
+ */
// import 'classlist.js'; // Run `npm install --save classlist.js`.
/**
@@ -35,7 +37,7 @@
* will put import in the top of bundle, so user need to create a separate file
* in this directory (for example: zone-flags.ts), and put the following flags
* into that file, and then add the following code before importing zone.js.
- * import './zone-flags.ts';
+ * import './zone-flags';
*
* The flags allowed in zone-flags.ts are listed here.
*
@@ -55,7 +57,8 @@
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
-import 'zone.js/dist/zone'; // Included with Angular CLI.
+import 'zone.js/dist/zone'; // Included with Angular CLI.
+
/***************************************************************************************************
* APPLICATION IMPORTS
diff --git a/Demos/WebForms/src/client/apps/viewer/src/styles.less b/Demos/ASP.NET Web Forms/src/ClientApp/src/styles.css
similarity index 100%
rename from Demos/WebForms/src/client/apps/viewer/src/styles.less
rename to Demos/ASP.NET Web Forms/src/ClientApp/src/styles.css
diff --git a/Demos/ASP.NET Web Forms/src/ClientApp/tsconfig.app.json b/Demos/ASP.NET Web Forms/src/ClientApp/tsconfig.app.json
new file mode 100644
index 000000000..82d91dc4a
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/ClientApp/tsconfig.app.json
@@ -0,0 +1,15 @@
+/* To learn more about this file see: https://angular.io/config/tsconfig. */
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./out-tsc/app",
+ "types": []
+ },
+ "files": [
+ "src/main.ts",
+ "src/polyfills.ts"
+ ],
+ "include": [
+ "src/**/*.d.ts"
+ ]
+}
diff --git a/Demos/ASP.NET Web Forms/src/ClientApp/tsconfig.json b/Demos/ASP.NET Web Forms/src/ClientApp/tsconfig.json
new file mode 100644
index 000000000..94a24fe1e
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/ClientApp/tsconfig.json
@@ -0,0 +1,30 @@
+/* To learn more about this file see: https://angular.io/config/tsconfig. */
+{
+ "compileOnSave": false,
+ "compilerOptions": {
+ "baseUrl": "./",
+ "outDir": "./dist/out-tsc",
+ "forceConsistentCasingInFileNames": true,
+ "strict": true,
+ "noImplicitReturns": true,
+ "noFallthroughCasesInSwitch": true,
+ "sourceMap": true,
+ "declaration": false,
+ "downlevelIteration": true,
+ "experimentalDecorators": true,
+ "moduleResolution": "node",
+ "importHelpers": true,
+ "target": "es2015",
+ "module": "es2020",
+ "lib": [
+ "es2018",
+ "dom"
+ ]
+ },
+ "angularCompilerOptions": {
+ "enableI18nLegacyMessageIdFormat": false,
+ "strictInjectionParameters": true,
+ "strictInputAccessModifiers": true,
+ "strictTemplates": true
+ }
+}
diff --git a/Demos/ASP.NET Web Forms/src/Controllers/ViewerApiController.cs b/Demos/ASP.NET Web Forms/src/Controllers/ViewerApiController.cs
new file mode 100644
index 000000000..3d06f1a50
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Controllers/ViewerApiController.cs
@@ -0,0 +1,385 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Threading.Tasks;
+using System.Web;
+using System.Web.Http;
+using System.Web.Http.Cors;
+using GroupDocs.Viewer.AspNetWebForms.ActionResults;
+using GroupDocs.Viewer.AspNetWebForms.Core;
+using GroupDocs.Viewer.AspNetWebForms.Core.Configuration;
+using GroupDocs.Viewer.AspNetWebForms.Core.Entities;
+using GroupDocs.Viewer.AspNetWebForms.Core.Extensions;
+using GroupDocs.Viewer.AspNetWebForms.Core.Utils;
+using GroupDocs.Viewer.AspNetWebForms.Models;
+
+namespace GroupDocs.Viewer.AspNetWebForms.Controllers
+{
+ [RoutePrefix(Constants.API_PATH)]
+ [EnableCors(origins: "*", headers: "*", methods: "*")]
+ public class ViewerApiController : ApiController
+ {
+ private readonly UIConfig _config;
+ private readonly IFileStorage _fileStorage;
+ private readonly IViewer _viewer;
+
+ public ViewerApiController(
+ UIConfig uiConfig,
+ IFileStorage fileStorage,
+ IViewer viewer)
+ {
+ _config = uiConfig;
+ _fileStorage = fileStorage;
+ _viewer = viewer;
+ }
+
+ [HttpGet]
+ [Route(Constants.LOAD_CONFIG_ACTION_NAME)]
+ public IHttpActionResult LoadConfig()
+ {
+ var config = new LoadConfigResponse
+ {
+ PageSelector = _config.PageSelector,
+ Download = _config.Download,
+ Upload = _config.Upload,
+ Print = _config.Print,
+ Browse = _config.Browse,
+ Rewrite = _config.Rewrite,
+ EnableRightClick = _config.EnableRightClick,
+ DefaultDocument = _config.DefaultDocument,
+ PreloadPageCount = _config.PreloadPageCount,
+ Zoom = _config.Zoom,
+ Search = _config.Search,
+ Thumbnails = _config.Thumbnails,
+ HtmlMode = _config.HtmlMode,
+ PrintAllowed = _config.PrintAllowed,
+ Rotate = _config.Rotate,
+ SaveRotateState = _config.SaveRotateState,
+ DefaultLanguage = _config.DefaultLanguage,
+ SupportedLanguages = _config.SupportedLanguages,
+ ShowLanguageMenu = _config.ShowLanguageMenu
+ };
+
+ return OkJsonResult(config);
+ }
+
+ [HttpPost]
+ [Route(Constants.LOAD_FILE_TREE_ACTION_NAME)]
+ public async Task GetFileTree(LoadFileTreeRequest request)
+ {
+ if (!_config.Browse)
+ return ErrorJsonResult("Browsing files is disabled.");
+
+ try
+ {
+ var files =
+ await _fileStorage.ListDirsAndFilesAsync(request.Path);
+
+ var result = files
+ .Select(entity => new FileDescription(entity.FilePath, entity.FilePath, entity.IsDirectory, entity.Size))
+ .ToList();
+
+ return OkJsonResult(result);
+ }
+ catch (Exception ex)
+ {
+ return ErrorJsonResult(ex.Message);
+ }
+ }
+
+ [HttpGet]
+ [Route(Constants.DOWNLOAD_DOCUMENT_ACTION_NAME)]
+ public async Task DownloadDocument(string path)
+ {
+ if (!_config.Download)
+ return ErrorJsonResult("Downloading files is disabled.");
+
+ try
+ {
+ var fileName = Path.GetFileName(path);
+ var bytes = await _fileStorage.ReadFileAsync(path);
+
+ return FileResult(bytes, fileName);
+ }
+ catch (Exception ex)
+ {
+ return ErrorJsonResult(ex.Message);
+ }
+ }
+
+ [HttpGet]
+ [Route(Constants.LOAD_DOCUMENT_PAGE_RESOURCE_ACTION_NAME)]
+ public async Task LoadDocumentPageResource(
+ [FromUri]string guid, [FromUri] int pageNumber, [FromUri] string resourceName)
+ {
+ if (!_config.HtmlMode)
+ return ErrorJsonResult("Loading page resources is disabled in image mode.");
+
+ try
+ {
+ var fileCredentials =
+ new FileCredentials(guid, "", "");
+ var bytes =
+ await _viewer.GetPageResourceAsync(fileCredentials, pageNumber, resourceName);
+
+ if (bytes.Length == 0)
+ return NotFoundJsonResult($"Resource {resourceName} was not found");
+
+ var contentType = resourceName.ContentTypeFromFileName();
+
+ return ResourceFileResult(bytes, contentType);
+ }
+ catch (Exception ex)
+ {
+ return ErrorJsonResult(ex.Message);
+ }
+ }
+
+ [HttpPost]
+ [Route(Constants.UPLOAD_DOCUMENT_ACTION_NAME)]
+ public async Task UploadDocument()
+ {
+ if (!_config.Upload)
+ return ErrorJsonResult("Uploading files is disabled.");
+
+ try
+ {
+ var url = HttpContext.Current.Request.Form["url"];
+ var (fileName, bytes) = await ReadOrDownloadFile(url);
+ bool.TryParse(HttpContext.Current.Request.Form["rewrite"], out var rewrite);
+
+ var filePath = await _fileStorage.WriteFileAsync(fileName, bytes, rewrite);
+ var result = new UploadFileResponse(filePath);
+
+ return OkJsonResult(result);
+ }
+ catch (Exception ex)
+ {
+ return ErrorJsonResult(ex.Message);
+ }
+ }
+
+ [HttpPost]
+ [Route(Constants.PRINT_PDF_ACTION_NAME)]
+ public async Task PrintPdf([FromBody] PrintPdfRequest request)
+ {
+ if (!_config.Print)
+ return ErrorJsonResult("Printing files is disabled.");
+
+ try
+ {
+ var fileCredentials =
+ new FileCredentials(request.Guid, request.FileType, request.Password);
+
+ var filename = Path.GetFileName(request.Guid);
+ var pdfFileName = Path.ChangeExtension(filename, ".pdf");
+ var pdfFileBytes = await _viewer.GetPdfAsync(fileCredentials);
+
+ return FileResult(pdfFileBytes, pdfFileName, "application/pdf");
+ }
+ catch (Exception ex)
+ {
+ if (ex.Message.Contains("password"))
+ {
+ var message = string.IsNullOrEmpty(request.Password)
+ ? "Password Required"
+ : "Incorrect Password";
+
+ return ForbiddenJsonResult(message);
+ }
+
+ return ErrorJsonResult(ex.Message);
+ }
+ }
+
+ [HttpPost]
+ [Route(Constants.LOAD_DOCUMENT_DESCRIPTION_ACTION_NAME)]
+ public async Task LoadDocumentDescription([FromBody] LoadDocumentDescriptionRequest request)
+ {
+ try
+ {
+ var fileCredentials =
+ new FileCredentials(request.Guid, request.FileType, request.Password);
+ var documentDescription =
+ await _viewer.GetDocumentInfoAsync(fileCredentials);
+
+ var pageNumbers = GetPageNumbers(documentDescription.Pages.Count());
+ var pagesData = await _viewer.GetPagesAsync(fileCredentials, pageNumbers);
+
+ var pages = new List();
+ foreach (PageInfo pageInfo in documentDescription.Pages)
+ {
+ var pageData = pagesData.FirstOrDefault(p => p.PageNumber == pageInfo.Number);
+ var pageDescription = new PageDescription
+ {
+ Width = pageInfo.Width,
+ Height = pageInfo.Height,
+ Number = pageInfo.Number,
+ SheetName = pageInfo.Name,
+ Data = pageData?.GetContent()
+ };
+
+ pages.Add(pageDescription);
+ }
+
+ var result = new LoadDocumentDescriptionResponse
+ {
+ Guid = request.Guid,
+ FileType = documentDescription.FileType,
+ PrintAllowed = documentDescription.PrintAllowed,
+ Pages = pages
+ };
+
+ return OkJsonResult(result);
+ }
+ catch (Exception ex)
+ {
+ if (ex.Message.Contains("password"))
+ {
+ var message = string.IsNullOrEmpty(request.Password)
+ ? "Password Required"
+ : "Incorrect Password";
+
+ return ForbiddenJsonResult(message);
+ }
+
+ return ErrorJsonResult(ex.Message);
+ }
+ }
+
+ private int[] GetPageNumbers(int totalPageCount)
+ {
+ if (_config.PreloadPageCount == 0)
+ return Enumerable.Range(1, totalPageCount).ToArray();
+
+ var pageCount =
+ Math.Min(totalPageCount, _config.PreloadPageCount);
+
+ return Enumerable.Range(1, pageCount).ToArray();
+ }
+
+ [HttpPost]
+ [Route(Constants.LOAD_DOCUMENT_PAGES_ACTION_NAME)]
+ public async Task LoadDocumentPages([FromBody] LoadDocumentPagesRequest request)
+ {
+ try
+ {
+ var fileCredentials =
+ new FileCredentials(request.Guid, request.FileType, request.Password);
+ var pages = await _viewer.GetPagesAsync(fileCredentials, request.Pages);
+ var pageContents = pages
+ .Select(page => new PageContent { Number = page.PageNumber, Data = page.GetContent() })
+ .ToList();
+
+ return OkJsonResult(pageContents);
+ }
+ catch (Exception ex)
+ {
+ if (ex.Message.Contains("password"))
+ {
+ var message = string.IsNullOrEmpty(request.Password)
+ ? "Password Required"
+ : "Incorrect Password";
+
+ return ForbiddenJsonResult(message);
+ }
+
+ return ErrorJsonResult(ex.Message);
+ }
+ }
+
+ [HttpPost]
+ [Route(Constants.LOAD_DOCUMENT_PAGE_ACTION_NAME)]
+ public async Task LoadDocumentPage([FromBody] LoadDocumentPageRequest request)
+ {
+ try
+ {
+ var fileCredentials =
+ new FileCredentials(request.Guid, request.FileType, request.Password);
+ var page = await _viewer.GetPageAsync(fileCredentials, request.Page);
+ var pageContent = new PageContent { Number = page.PageNumber, Data = page.GetContent() };
+
+ return OkJsonResult(pageContent);
+ }
+ catch (Exception ex)
+ {
+ if (ex.Message.Contains("password"))
+ {
+ var message = string.IsNullOrEmpty(request.Password)
+ ? "Password Required"
+ : "Incorrect Password";
+
+ return ForbiddenJsonResult(message);
+ }
+
+ return ErrorJsonResult(ex.Message);
+ }
+ }
+
+ private Task<(string, byte[])> ReadOrDownloadFile(string url)
+ {
+ return string.IsNullOrEmpty(url)
+ ? ReadFileFromRequest()
+ : DownloadFileAsync(url);
+ }
+
+ private async Task<(string, byte[])> ReadFileFromRequest()
+ {
+ var provider = await Request.Content.ReadAsMultipartAsync();
+ var firstFile = provider.Contents.First();
+
+ var bytes = await firstFile.ReadAsByteArrayAsync();
+ var fileName = PathUtils.RemoveInvalidFileNameChars(
+ firstFile.Headers.ContentDisposition.FileName);
+
+ return (fileName, bytes);
+ }
+
+ private async Task<(string, byte[])> DownloadFileAsync(string url)
+ {
+ using (HttpClient httpClient = new HttpClient())
+ {
+ httpClient.DefaultRequestHeaders.Add("User-Agent", "Other");
+
+ Uri uri = new Uri(url);
+ string fileName = Path.GetFileName(uri.LocalPath);
+ byte[] bytes = await httpClient.GetByteArrayAsync(uri);
+
+ return (fileName, bytes);
+ }
+ }
+
+ private IHttpActionResult ErrorJsonResult(string message) =>
+ new JsonActionResult(new ErrorResponse(message), Request)
+ {
+ StatusCode = HttpStatusCode.InternalServerError
+ };
+
+ private IHttpActionResult ForbiddenJsonResult(string message) =>
+ new JsonActionResult(new ErrorResponse(message), Request)
+ {
+ StatusCode = HttpStatusCode.Forbidden
+ };
+
+ private IHttpActionResult NotFoundJsonResult(string message) =>
+ new JsonActionResult(new ErrorResponse(message), Request)
+ {
+ StatusCode = HttpStatusCode.NotFound
+ };
+
+ private IHttpActionResult OkJsonResult(object result) =>
+ new JsonActionResult(result, Request);
+
+ private IHttpActionResult FileResult(byte[] data, string fileName) =>
+ new FileActionResult(data, fileName, "application/octet-stream", Request);
+
+ private IHttpActionResult FileResult(byte[] data, string fileName, string contentType) =>
+ new FileActionResult(data, fileName, contentType, Request);
+
+ private IHttpActionResult ResourceFileResult(byte[] data, string contentType) =>
+ new ResourceActionResult(data, contentType, Request);
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET Web Forms/src/Core/Caching/AsyncDuplicateLock.cs b/Demos/ASP.NET Web Forms/src/Core/Caching/AsyncDuplicateLock.cs
new file mode 100644
index 000000000..4ad88214b
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Core/Caching/AsyncDuplicateLock.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace GroupDocs.Viewer.AspNetWebForms.Core.Caching
+{
+ public sealed class AsyncDuplicateLock : IAsyncLock
+ {
+ private sealed class RefCounted
+ {
+ public RefCounted(T value)
+ {
+ RefCount = 1;
+ Value = value;
+ }
+
+ public int RefCount { get; set; }
+ public T Value { get; private set; }
+ }
+
+ private static readonly Dictionary> SemaphoreSlims
+ = new Dictionary>();
+
+ private SemaphoreSlim GetOrCreate(object key)
+ {
+ RefCounted item;
+ lock (SemaphoreSlims)
+ {
+ if (SemaphoreSlims.TryGetValue(key, out item))
+ {
+ ++item.RefCount;
+ }
+ else
+ {
+ item = new RefCounted(new SemaphoreSlim(1, 1));
+ SemaphoreSlims[key] = item;
+ }
+ }
+ return item.Value;
+ }
+
+ public IDisposable Lock(object key)
+ {
+ GetOrCreate(key).Wait();
+ return new Releaser { Key = key };
+ }
+
+ public async Task LockAsync(object key)
+ {
+ await GetOrCreate(key).WaitAsync().ConfigureAwait(false);
+ return new Releaser { Key = key };
+ }
+
+ private sealed class Releaser : IDisposable
+ {
+ public object Key { get; set; }
+
+ public void Dispose()
+ {
+ RefCounted item;
+ lock (SemaphoreSlims)
+ {
+ item = SemaphoreSlims[Key];
+ --item.RefCount;
+ if (item.RefCount == 0)
+ SemaphoreSlims.Remove(Key);
+ }
+ item.Value.Release();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET Web Forms/src/Core/Caching/CacheKeys.cs b/Demos/ASP.NET Web Forms/src/Core/Caching/CacheKeys.cs
new file mode 100644
index 000000000..7d2d2da0a
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Core/Caching/CacheKeys.cs
@@ -0,0 +1,14 @@
+namespace GroupDocs.Viewer.AspNetWebForms.Core.Caching
+{
+ public static class CacheKeys
+ {
+ public const string FILE_INFO_CACHE_KEY = "info.json";
+ public const string PDF_FILE_CACHE_KEY = "file.pdf";
+
+ public static string GetHtmlPageResourceCacheKey(int pageNumber, string resourceName)
+ => $"p{pageNumber}_{resourceName}";
+
+ public static string GetPageCacheKey(int pageNumber, string extension) =>
+ $"p{pageNumber}{extension}";
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET Web Forms/src/Core/Caching/CachedPage.cs b/Demos/ASP.NET Web Forms/src/Core/Caching/CachedPage.cs
new file mode 100644
index 000000000..914bd5982
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Core/Caching/CachedPage.cs
@@ -0,0 +1,21 @@
+namespace GroupDocs.Viewer.AspNetWebForms.Core.Caching
+{
+ internal class CachedPage
+ {
+ ///
+ /// The page number.
+ ///
+ public int PageNumber { get; }
+
+ ///
+ /// The data. Can be null.
+ ///
+ public byte[] Data { get; }
+
+ public CachedPage(int pageNumber, byte[] data)
+ {
+ PageNumber = pageNumber;
+ Data = data;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET Web Forms/src/Core/Caching/CachingViewer.cs b/Demos/ASP.NET Web Forms/src/Core/Caching/CachingViewer.cs
new file mode 100644
index 000000000..ce8620e70
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Core/Caching/CachingViewer.cs
@@ -0,0 +1,204 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using GroupDocs.Viewer.AspNetWebForms.Core.Entities;
+using GroupDocs.Viewer.AspNetWebForms.Core.Extensions;
+
+namespace GroupDocs.Viewer.AspNetWebForms.Core.Caching
+{
+ public class CachingViewer : IViewer
+ {
+ private readonly IViewer _viewer;
+ private readonly IFileCache _fileCache;
+ private readonly IAsyncLock _asyncLock;
+
+ public string PageExtension =>
+ _viewer.PageExtension;
+
+ public Page CreatePage(int pageNumber, byte[] data) =>
+ _viewer.CreatePage(pageNumber, data);
+
+ public CachingViewer(IViewer viewer, IFileCache fileCache, IAsyncLock asyncLock)
+ {
+ _viewer = viewer;
+ _fileCache = fileCache;
+ _asyncLock = asyncLock;
+ }
+
+ public async Task GetPagesAsync(FileCredentials fileCredentials, int[] pageNumbers)
+ {
+ var pagesOrNulls = GetPagesOrNullsFromCache(fileCredentials.FilePath, pageNumbers);
+ var missingPageNumbers = GetMissingPageNumbers(pagesOrNulls);
+
+ if (missingPageNumbers.Length == 0)
+ return ToPages(pagesOrNulls);
+
+ var createdPages = await CreatePages(fileCredentials, missingPageNumbers);
+
+ var pages = Combine(pagesOrNulls, createdPages);
+
+ return pages;
+ }
+
+ public async Task GetPageAsync(FileCredentials fileCredentials, int pageNumber)
+ {
+ var cacheKey = CacheKeys.GetPageCacheKey(pageNumber, PageExtension);
+ var bytes = await _fileCache.GetValueAsync(cacheKey, fileCredentials.FilePath, async () =>
+ {
+ using (await _asyncLock.LockAsync(fileCredentials.FilePath))
+ {
+ return await _fileCache.GetValueAsync(cacheKey, fileCredentials.FilePath, async () =>
+ {
+ var newPage = await _viewer.GetPageAsync(fileCredentials, pageNumber);
+
+ await SaveResourcesAsync(fileCredentials.FilePath, newPage.PageNumber, newPage.Resources);
+
+ return newPage.Data;
+ });
+ }
+ });
+
+ var page = CreatePage(pageNumber, bytes);
+ return page;
+ }
+
+ public Task GetDocumentInfoAsync(FileCredentials fileCredentials)
+ {
+ var cacheKey = CacheKeys.FILE_INFO_CACHE_KEY;
+ return _fileCache.GetValueAsync(cacheKey, fileCredentials.FilePath, async () =>
+ {
+ using (await _asyncLock.LockAsync(fileCredentials.FilePath))
+ {
+ return await _fileCache.GetValueAsync(cacheKey, fileCredentials.FilePath, () =>
+ _viewer.GetDocumentInfoAsync(fileCredentials));
+ }
+ });
+ }
+
+ public Task GetPdfAsync(FileCredentials fileCredentials)
+ {
+ var cacheKey = CacheKeys.PDF_FILE_CACHE_KEY;
+ return _fileCache.GetValueAsync(cacheKey, fileCredentials.FilePath, async () =>
+ {
+ using (await _asyncLock.LockAsync(fileCredentials.FilePath))
+ {
+ return await _fileCache.GetValueAsync(cacheKey, fileCredentials.FilePath, () =>
+ _viewer.GetPdfAsync(fileCredentials));
+ }
+ });
+ }
+
+ public Task GetPageResourceAsync(FileCredentials fileCredentials, int pageNumber,
+ string resourceName)
+ {
+ var cacheKey = CacheKeys.GetHtmlPageResourceCacheKey(pageNumber, resourceName);
+ return _fileCache.GetValueAsync(cacheKey, fileCredentials.FilePath,
+ async () =>
+ {
+ using (await _asyncLock.LockAsync(fileCredentials.FilePath))
+ {
+ return await _fileCache.GetValueAsync(cacheKey, fileCredentials.FilePath, () =>
+ _viewer.GetPageResourceAsync(fileCredentials, pageNumber, resourceName));
+ }
+ });
+ }
+
+ private async Task SaveResourcesAsync(string filePath, int pageNumber, IEnumerable pageResources)
+ {
+ var tasks = pageResources.Select(resource =>
+ {
+ var resourceCacheKey =
+ CacheKeys.GetHtmlPageResourceCacheKey(pageNumber, resource.ResourceName);
+
+ return _fileCache.SetAsync(resourceCacheKey, filePath, resource.Data);
+ });
+
+ await Task.WhenAll(tasks);
+ }
+
+ private async Task CreatePages(FileCredentials fileCredentials, int[] pageNumbers)
+ {
+ using (await _asyncLock.LockAsync(fileCredentials.FilePath))
+ {
+ var pagesOrNulls = GetPagesOrNullsFromCache(fileCredentials.FilePath, pageNumbers);
+ var missingPageNumbers = GetMissingPageNumbers(pagesOrNulls);
+
+ if (missingPageNumbers.Length == 0)
+ return ToPages(pagesOrNulls);
+
+ var createdPages = await _viewer.GetPagesAsync(fileCredentials, missingPageNumbers);
+
+ await SaveToCache(fileCredentials.FilePath, createdPages);
+
+ var pages = Combine(pagesOrNulls, createdPages);
+
+ return pages;
+ }
+ }
+
+ private Pages Combine(List dst, Pages missing)
+ {
+ var pages = dst
+ .Select(pageOrNull =>
+ {
+ if (pageOrNull.Data == null)
+ {
+ var page = missing
+ .Where(p => p.PageNumber == pageOrNull.PageNumber)
+ .Select(p => p)
+ .FirstOrDefault();
+
+ return page;
+ }
+
+ return CreatePage(pageOrNull.PageNumber, pageOrNull.Data);
+ }).ToList();
+
+ return new Pages(pages);
+ }
+
+ private Task SaveToCache(string filePath, Pages createdPages)
+ {
+ var tasks = createdPages
+ .SelectMany(page =>
+ {
+ var cacheKey = CacheKeys.GetPageCacheKey(page.PageNumber, _viewer.PageExtension);
+
+ var savePageTask = _fileCache.SetAsync(cacheKey, filePath, page.Data);
+ var saveResourcesTask = SaveResourcesAsync(filePath, page.PageNumber, page.Resources);
+
+ return new[] {savePageTask, saveResourcesTask};
+ });
+
+ return Task.WhenAll(tasks);
+ }
+
+ private Pages ToPages(List pagesOrNulls)
+ {
+ var pages = pagesOrNulls
+ .Select(p => CreatePage(p.PageNumber, p.Data))
+ .ToList();
+
+ var result = new Pages(pages);
+ return result;
+ }
+
+ private int[] GetMissingPageNumbers(List pagesOrNulls)
+ {
+ return pagesOrNulls
+ .Where(p => p.Data == null)
+ .Select(p => p.PageNumber)
+ .ToArray();
+ }
+
+ private List GetPagesOrNullsFromCache(string filePath, int[] pageNumbers)
+ {
+ return pageNumbers
+ .Select(pageNumber =>
+ (pageNumber, cacheKey: CacheKeys.GetPageCacheKey(pageNumber, PageExtension)))
+ .Select(page =>
+ new CachedPage(page.pageNumber, _fileCache.TryGetValue(page.cacheKey, filePath)))
+ .ToList();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET Web Forms/src/Core/Caching/LocalFileCache.cs b/Demos/ASP.NET Web Forms/src/Core/Caching/LocalFileCache.cs
new file mode 100644
index 000000000..6429c681f
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Core/Caching/LocalFileCache.cs
@@ -0,0 +1,277 @@
+using System;
+using System.IO;
+using System.Runtime.Serialization;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Newtonsoft.Json;
+
+namespace GroupDocs.Viewer.AspNetWebForms.Core.Caching
+{
+ public class LocalFileCache : IFileCache
+ {
+ ///
+ /// The Relative or absolute path to the cache folder.
+ ///
+ private string CachePath { get; }
+
+ private readonly TimeSpan _waitTimeout = TimeSpan.FromMilliseconds(100);
+
+ ///
+ /// Creates new instance of class.
+ ///
+ /// Relative or absolute path where document cache will be stored.
+ /// Thrown when is null.
+ public LocalFileCache(string cachePath)
+ {
+ if (cachePath == null)
+ throw new ArgumentNullException(nameof(cachePath));
+
+ CachePath = cachePath;
+ }
+
+ ///
+ /// Deserializes data associated with this key if present.
+ ///
+ /// A key identifying the requested entry.
+ /// The relative or absolute filepath.
+ /// True
if the key was found.
+ public TEntry TryGetValue(string cacheKey, string filePath)
+ {
+ string cacheFilePath = GetCacheFilePath(cacheKey, filePath);
+
+ if (File.Exists(cacheFilePath))
+ {
+ if (typeof(TEntry) == typeof(byte[]))
+ return (TEntry)ReadBytes(cacheFilePath);
+
+ if (typeof(TEntry) == typeof(Stream))
+ return (TEntry)ReadStream(cacheFilePath);
+
+ return Deserialize(cacheFilePath);
+ }
+
+ return default(TEntry);
+ }
+
+ ///
+ /// Deserializes data associated with this key if present.
+ ///
+ /// A key identifying the requested entry.
+ /// The relative or absolute filepath.
+ /// True
if the key was found.
+ public async Task TryGetValueAsync(string cacheKey, string filePath)
+ {
+ string cacheFilePath = GetCacheFilePath(cacheKey, filePath);
+
+ if (File.Exists(cacheFilePath))
+ {
+ if (typeof(TEntry) == typeof(byte[]))
+ return (TEntry)ReadBytes(cacheFilePath);
+
+ if (typeof(TEntry) == typeof(Stream))
+ return (TEntry)ReadStream(cacheFilePath);
+
+ return await DeserializeAsync(cacheFilePath);
+ }
+
+ return default(TEntry);
+ }
+
+ ///
+ /// Serializes data to the local disk.
+ ///
+ /// An unique identifier for the cache entry.
+ /// The relative or absolute filepath.
+ /// The object to serialize.
+ public void Set(string cacheKey, string filePath, TEntry value)
+ {
+ if (value == null)
+ return;
+
+ string cacheFilePath = GetCacheFilePath(cacheKey, filePath);
+
+ if (value is byte[] data)
+ {
+ using (FileStream dst = GetStream(cacheFilePath, FileMode.Create, FileAccess.Write, FileShare.None))
+ {
+ dst.Write(data, 0, data.Length);
+ }
+ }
+ else if (value is Stream src)
+ {
+ using (FileStream dst = GetStream(cacheFilePath, FileMode.Create, FileAccess.Write, FileShare.None))
+ {
+ if (src.CanSeek)
+ src.Position = 0;
+ src.CopyTo(dst);
+ }
+ }
+ else
+ {
+ var json = JsonConvert.SerializeObject(value, Formatting.Indented);
+ var bytes = Encoding.UTF8.GetBytes(json);
+
+ using (FileStream stream = GetStream(cacheFilePath, FileMode.Create, FileAccess.Write, FileShare.None))
+ {
+ stream.Write(bytes, 0, bytes.Length);
+ }
+ }
+ }
+
+ ///
+ /// Serializes data to the local disk.
+ ///
+ /// An unique identifier for the cache entry.
+ /// The relative or absolute filepath.
+ /// The object to serialize.
+ public async Task SetAsync(string cacheKey, string filePath, TEntry value)
+ {
+ if (value == null)
+ return;
+
+ string cacheFilePath = GetCacheFilePath(cacheKey, filePath);
+
+ if (value is byte[] data)
+ {
+ using (FileStream dst = GetStream(cacheFilePath, FileMode.Create, FileAccess.Write, FileShare.None))
+ {
+ await dst.WriteAsync(data, 0, data.Length);
+ }
+ }
+ else if (value is Stream src)
+ {
+ using (FileStream dst = GetStream(cacheFilePath, FileMode.Create, FileAccess.Write, FileShare.None))
+ {
+ if (src.CanSeek)
+ src.Position = 0;
+
+ await src.CopyToAsync(dst);
+ }
+ }
+ else
+ {
+ var json = JsonConvert.SerializeObject(value, Formatting.Indented);
+ var bytes = Encoding.UTF8.GetBytes(json);
+
+ using (FileStream stream = GetStream(cacheFilePath, FileMode.Create, FileAccess.Write, FileShare.None))
+ {
+ await stream.WriteAsync(bytes, 0, bytes.Length);
+ }
+ }
+ }
+
+ private object ReadStream(string cacheFilePath)
+ => GetStream(cacheFilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
+
+ private object ReadBytes(string cacheFilePath)
+ => GetBytes(cacheFilePath);
+
+ private TEntry Deserialize(string cachePath)
+ {
+ object data;
+ try
+ {
+ var bytes = GetBytes(cachePath);
+ var json = Encoding.UTF8.GetString(bytes);
+
+ data = JsonConvert.DeserializeObject(json);
+ }
+ catch (SerializationException)
+ {
+ data = default(TEntry);
+ }
+
+ return (TEntry)data;
+ }
+
+ private async Task DeserializeAsync(string cachePath)
+ {
+ object data;
+ try
+ {
+ using (var stream = GetStream(cachePath, FileMode.Open, FileAccess.Read, FileShare.Read))
+ {
+ var memory = new MemoryStream();
+ await stream.CopyToAsync(memory);
+ var json = Encoding.UTF8.GetString(memory.ToArray());
+
+ data = JsonConvert.DeserializeObject(json);
+ }
+ }
+ catch (SerializationException)
+ {
+ data = default(TEntry);
+ }
+
+ return (TEntry)data;
+ }
+
+ private string GetCacheFilePath(string cacheKey, string filePath)
+ {
+ string cacheSubFolder = string.Join("_", filePath.Split(Path.GetInvalidPathChars()))
+ .Replace(".", "_");
+ string cacheDirPath = Path.Combine(CachePath, cacheSubFolder);
+ string cacheFilePath = Path.Combine(cacheDirPath, cacheKey);
+
+ if (!Directory.Exists(cacheDirPath))
+ Directory.CreateDirectory(cacheDirPath);
+
+ return cacheFilePath;
+ }
+
+ private FileStream GetStream(string path, FileMode mode, FileAccess access, FileShare share)
+ {
+ FileStream stream = null;
+ TimeSpan interval = new TimeSpan(0, 0, 0, 0, 50);
+ TimeSpan totalTime = new TimeSpan();
+
+ while (stream == null)
+ {
+ try
+ {
+ stream = File.Open(path, mode, access, share);
+ }
+ catch (IOException)
+ {
+ Thread.Sleep(interval);
+ totalTime += interval;
+
+ if (_waitTimeout.Ticks != 0 && totalTime > _waitTimeout)
+ {
+ throw;
+ }
+ }
+ }
+
+ return stream;
+ }
+
+ private byte[] GetBytes(string path)
+ {
+ byte[] bytes = null;
+ TimeSpan interval = new TimeSpan(0, 0, 0, 0, 50);
+ TimeSpan totalTime = new TimeSpan();
+
+ while (bytes == null)
+ {
+ try
+ {
+ bytes = File.ReadAllBytes(path);
+ }
+ catch (IOException)
+ {
+ Thread.Sleep(interval);
+ totalTime += interval;
+
+ if (_waitTimeout.Ticks != 0 && totalTime > _waitTimeout)
+ {
+ throw;
+ }
+ }
+ }
+
+ return bytes;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET Web Forms/src/Core/Caching/NoopFileCache.cs b/Demos/ASP.NET Web Forms/src/Core/Caching/NoopFileCache.cs
new file mode 100644
index 000000000..afba6d538
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Core/Caching/NoopFileCache.cs
@@ -0,0 +1,18 @@
+using System.Threading.Tasks;
+
+namespace GroupDocs.Viewer.AspNetWebForms.Core.Caching
+{
+ internal class NoopFileCache : IFileCache
+ {
+ public TEntry TryGetValue(string cacheKey, string filePath) =>
+ default(TEntry);
+
+ public Task TryGetValueAsync(string cacheKey, string filePath) =>
+ Task.FromResult(default(TEntry));
+
+ public void Set(string cacheKey, string filePath, TEntry entry) { }
+
+ public Task SetAsync(string cacheKey, string filePath, TEntry entry) =>
+ Task.CompletedTask;
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET Web Forms/src/Core/Configuration/Language.cs b/Demos/ASP.NET Web Forms/src/Core/Configuration/Language.cs
new file mode 100644
index 000000000..92c0d9db4
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Core/Configuration/Language.cs
@@ -0,0 +1,43 @@
+namespace GroupDocs.Viewer.AspNetWebForms.Core.Configuration
+{
+ public class Language
+ {
+ public static readonly Language Arabic = new Language("ar");
+ public static readonly Language Catalan = new Language("ca");
+ public static readonly Language Czech = new Language("cs");
+ public static readonly Language Danish = new Language("da");
+ public static readonly Language German = new Language("de");
+ public static readonly Language Greek = new Language("el");
+ public static readonly Language English = new Language("en");
+ public static readonly Language Spanish = new Language("es");
+ public static readonly Language Filipino = new Language("fil");
+ public static readonly Language French = new Language("fr");
+ public static readonly Language Hebrew = new Language("he");
+ public static readonly Language Hindi = new Language("hi");
+ public static readonly Language Indonesian = new Language("id");
+ public static readonly Language Italian = new Language("it");
+ public static readonly Language Japanese = new Language("ja");
+ public static readonly Language Kazakh = new Language("kk");
+ public static readonly Language Korean = new Language("ko");
+ public static readonly Language Malay = new Language("ms");
+ public static readonly Language Dutch = new Language("nl");
+ public static readonly Language Polish = new Language("pl");
+ public static readonly Language Portuguese = new Language("pt");
+ public static readonly Language Romanian = new Language("ro");
+ public static readonly Language Russian = new Language("ru");
+ public static readonly Language Swedish = new Language("sv");
+ public static readonly Language Vietnamese = new Language("vi");
+ public static readonly Language Thai = new Language("th");
+ public static readonly Language Turkish = new Language("tr");
+ public static readonly Language Ukrainian = new Language("uk");
+ public static readonly Language ChineseSimplified = new Language("zh-hans");
+ public static readonly Language ChineseTraditional = new Language("zh-hant");
+
+ public string Code { get; }
+
+ public Language(string code)
+ {
+ Code = code;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET Web Forms/src/Core/Configuration/UIConfig.cs b/Demos/ASP.NET Web Forms/src/Core/Configuration/UIConfig.cs
new file mode 100644
index 000000000..72afdebe8
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Core/Configuration/UIConfig.cs
@@ -0,0 +1,238 @@
+using System.Linq;
+
+namespace GroupDocs.Viewer.AspNetWebForms.Core.Configuration
+{
+ public class UIConfig
+ {
+ public static readonly UIConfig Instance = new UIConfig();
+
+ internal string DefaultDocument { get; private set; } = string.Empty;
+ internal int PreloadPageCount { get; private set; } = 3;
+ internal bool PageSelector { get; private set; } = true;
+ internal bool Thumbnails { get; private set; } = true;
+ internal bool Zoom { get; private set; } = true;
+ internal bool Search { get; private set; } = true;
+ internal bool EnableRightClick { get; private set; } = true;
+ internal bool Download { get; private set; } = true;
+ internal bool Upload { get; private set; } = true;
+ internal bool Rewrite { get; private set; } = false;
+ internal bool Print { get; private set; } = true;
+ internal bool Browse { get; private set; } = true;
+ internal bool PrintAllowed { get; private set; } = true;
+ internal bool HtmlMode { get; private set; } = true;
+ internal bool ShowLanguageMenu { get; private set; } = true;
+ internal string DefaultLanguage { get; private set; } = "en";
+ internal string[] SupportedLanguages { get; private set; } = new string[]
+ {
+ "ar", // ar - العربية
+ "ca", // ca-ES - Català
+ "cs", // cs-CZ - Čeština
+ "da", // da-DK - Dansk
+ "de", // de-DE - Deutsch
+ "el", // el-GR - Ελληνικά
+ "en", // en-US - English
+ "es", // es-ES - Español
+ "fil", // fil-PH - Filipino
+ "fr", // fr-FR - Français
+ "he", // he-IL - עברית
+ "hi", // hi-IN - हिन्दी
+ "id", // id-ID - Indonesia
+ "it", // it-IT - Italiano
+ "ja", // ja-JP - 日本語
+ "kk", // kk-KZ - Қазақ Тілі
+ "ko", // ko-KR - 한국어
+ "ms", // ms-MY - Melayu
+ "nl", // nl-NL - Nederlands
+ "pl", // pl-PL - Polski
+ "pt", // pt-PT - Português
+ "ro", // ro-RO - Română
+ "ru", // ru-RU - Русский
+ "sv", // sv-SE - Svenska
+ "vi", // vi-VN - Tiếng Việt
+ "th", // th-TH - ไทย
+ "tr", // tr-TR - Türkçe
+ "uk", // uk-UA - Українська
+ "zh-hans", // zh-Hans - 中文(简体)
+ "zh-hant", // zh-Hant" - 中文(繁體)
+ };
+
+ internal bool Rotate { get; private set; } = false;
+ internal bool SaveRotateState { get; private set; } = false;
+ internal ViewerType ViewerType { get; private set; }
+
+ public UIConfig SetViewerType(ViewerType viewerType)
+ {
+ ViewerType = viewerType;
+ HtmlMode = viewerType == ViewerType.HtmlWithExternalResources ||
+ viewerType == ViewerType.HtmlWithEmbeddedResources;
+ return this;
+ }
+
+ public UIConfig SetPreloadPageCount(int countPages)
+ {
+ PreloadPageCount = countPages;
+ return this;
+ }
+
+ public UIConfig SetDefaultDocument(string filePath)
+ {
+ DefaultDocument = filePath;
+ return this;
+ }
+
+ public UIConfig HidePageSelectorControl()
+ {
+ PageSelector = false;
+ return this;
+ }
+
+ public UIConfig HideThumbnailsControl()
+ {
+ Thumbnails = false;
+ return this;
+ }
+
+ public UIConfig DisableFileDownload()
+ {
+ Download = false;
+ return this;
+ }
+
+ public UIConfig DisableFileUpload()
+ {
+ Upload = false;
+ return this;
+ }
+
+ public UIConfig RewriteFilesOnUpload()
+ {
+ Rewrite = true;
+ return this;
+ }
+
+ public UIConfig DisablePrint()
+ {
+ Print = false;
+ PrintAllowed = false;
+ return this;
+ }
+
+ public UIConfig DisableFileBrowsing()
+ {
+ Browse = false;
+ return this;
+ }
+
+ public UIConfig HideZoomButton()
+ {
+ Zoom = false;
+ return this;
+ }
+
+ public UIConfig HideSearchControl()
+ {
+ Search = false;
+ return this;
+ }
+
+ public UIConfig HidePageRotationControl()
+ {
+ Rotate = false;
+ return this;
+ }
+
+ public UIConfig DisableRightClick()
+ {
+ EnableRightClick = false;
+ return this;
+ }
+
+ public UIConfig HideLanguageMenu()
+ {
+ ShowLanguageMenu = false;
+ return this;
+ }
+
+ ///
+ /// Sets default language out of supported:
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ ///
+ ///
+ /// Default language e.g. .
+ /// This UIConfig instance.
+ public UIConfig SetDefaultLanguage(Language language)
+ {
+ DefaultLanguage = language.Code;
+ return this;
+ }
+
+ ///
+ /// Set supported UI languages. The following languages are supported:
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ /// ,
+ ///
+ ///
+ /// Supported languages.
+ /// This UIConfig instance.
+ public UIConfig SetSupportedLanguages(params Language[] languages)
+ {
+ SupportedLanguages = languages.Select(l => l.Code).ToArray();
+ return this;
+ }
+ }
+}
diff --git a/Demos/ASP.NET Web Forms/src/Core/Configuration/ViewerConfig.cs b/Demos/ASP.NET Web Forms/src/Core/Configuration/ViewerConfig.cs
new file mode 100644
index 000000000..98946d4f4
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Core/Configuration/ViewerConfig.cs
@@ -0,0 +1,48 @@
+using System;
+using GroupDocs.Viewer.Options;
+
+namespace GroupDocs.Viewer.AspNetWebForms.Core.Configuration
+{
+ public class ViewerConfig
+ {
+ public static ViewerConfig Instance = new ViewerConfig();
+
+ internal string LicensePath = string.Empty;
+ internal readonly HtmlViewOptions HtmlViewOptions = HtmlViewOptions.ForEmbeddedResources();
+ internal readonly PngViewOptions PngViewOptions = new PngViewOptions();
+ internal readonly JpgViewOptions JpgViewOptions = new JpgViewOptions();
+ internal readonly PdfViewOptions PdfViewOptions = new PdfViewOptions();
+
+ private ViewerConfig() { }
+
+ public ViewerConfig SetLicensePath(string licensePath)
+ {
+ LicensePath = licensePath;
+ return this;
+ }
+
+ public ViewerConfig ConfigureHtmlViewOptions(Action setupOptions)
+ {
+ setupOptions?.Invoke(HtmlViewOptions);
+ return this;
+ }
+
+ public ViewerConfig ConfigurePngViewOptions(Action setupOptions)
+ {
+ setupOptions?.Invoke(PngViewOptions);
+ return this;
+ }
+
+ public ViewerConfig ConfigureJpgViewOptions(Action setupOptions)
+ {
+ setupOptions?.Invoke(JpgViewOptions);
+ return this;
+ }
+
+ public ViewerConfig ConfigurePdfViewOptions(Action setupOptions)
+ {
+ setupOptions?.Invoke(PdfViewOptions);
+ return this;
+ }
+ }
+}
diff --git a/Demos/ASP.NET Web Forms/src/Core/Constants.cs b/Demos/ASP.NET Web Forms/src/Core/Constants.cs
new file mode 100644
index 000000000..5b5c38fc9
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Core/Constants.cs
@@ -0,0 +1,16 @@
+namespace GroupDocs.Viewer.AspNetWebForms.Core
+{
+ public class Constants
+ {
+ public const string API_PATH = "viewer-api";
+ public const string LOAD_CONFIG_ACTION_NAME = "LoadConfig";
+ public const string LOAD_FILE_TREE_ACTION_NAME = "LoadFileTree";
+ public const string DOWNLOAD_DOCUMENT_ACTION_NAME = "DownloadDocument";
+ public const string LOAD_DOCUMENT_PAGE_RESOURCE_ACTION_NAME = "LoadDocumentPageResource";
+ public const string UPLOAD_DOCUMENT_ACTION_NAME = "UploadDocument";
+ public const string LOAD_DOCUMENT_DESCRIPTION_ACTION_NAME = "LoadDocumentDescription";
+ public const string LOAD_DOCUMENT_PAGES_ACTION_NAME = "LoadDocumentPages";
+ public const string LOAD_DOCUMENT_PAGE_ACTION_NAME = "LoadDocumentPage";
+ public const string PRINT_PDF_ACTION_NAME = "PrintPdf";
+ }
+}
diff --git a/Demos/ASP.NET Web Forms/src/Core/Entities/DocumentInfo.cs b/Demos/ASP.NET Web Forms/src/Core/Entities/DocumentInfo.cs
new file mode 100644
index 000000000..2696c6b80
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Core/Entities/DocumentInfo.cs
@@ -0,0 +1,13 @@
+using System.Collections.Generic;
+
+namespace GroupDocs.Viewer.AspNetWebForms.Core.Entities
+{
+ public class DocumentInfo
+ {
+ public string FileType { get; set; }
+
+ public bool PrintAllowed { get; set; }
+
+ public IEnumerable Pages { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET Web Forms/src/Core/Entities/FileCredentials.cs b/Demos/ASP.NET Web Forms/src/Core/Entities/FileCredentials.cs
new file mode 100644
index 000000000..3e061c268
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Core/Entities/FileCredentials.cs
@@ -0,0 +1,16 @@
+namespace GroupDocs.Viewer.AspNetWebForms.Core.Entities
+{
+ public class FileCredentials
+ {
+ public string FilePath { get; }
+ public string FileType { get; }
+ public string Password { get; }
+
+ public FileCredentials(string filePath, string fileType, string password)
+ {
+ FilePath = filePath;
+ FileType = fileType;
+ Password = password;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET Web Forms/src/Core/Entities/FileSystemEntry.cs b/Demos/ASP.NET Web Forms/src/Core/Entities/FileSystemEntry.cs
new file mode 100644
index 000000000..0c12f9540
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Core/Entities/FileSystemEntry.cs
@@ -0,0 +1,33 @@
+namespace GroupDocs.Viewer.AspNetWebForms.Core.Entities
+{
+ public class FileSystemEntry
+ {
+ public string FileName { get; private set; }
+
+ public string FilePath { get; private set; }
+
+ public bool IsDirectory { get; private set; }
+
+ public long Size { get; private set; }
+
+ private FileSystemEntry () { }
+
+ public static FileSystemEntry Directory(string name, string path, long size) =>
+ new FileSystemEntry
+ {
+ FileName = name,
+ FilePath = path,
+ IsDirectory = true,
+ Size = size
+ };
+
+ public static FileSystemEntry File(string name, string path, long size) =>
+ new FileSystemEntry
+ {
+ FileName = name,
+ FilePath = path,
+ IsDirectory = false,
+ Size = size
+ };
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET Web Forms/src/Core/Entities/HtmlPage.cs b/Demos/ASP.NET Web Forms/src/Core/Entities/HtmlPage.cs
new file mode 100644
index 000000000..b9ea961b0
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Core/Entities/HtmlPage.cs
@@ -0,0 +1,22 @@
+using System.Text;
+
+namespace GroupDocs.Viewer.AspNetWebForms.Core.Entities
+{
+ public class HtmlPage : Page
+ {
+ public static string Extension => ".html";
+
+ public override string GetContent() =>
+ Encoding.UTF8.GetString(Data);
+
+ public override void SetContent(string contents)
+ {
+ Data = Encoding.UTF8.GetBytes(contents);
+ }
+
+ public HtmlPage(int pageNumber, byte[] data)
+ : base(pageNumber, data)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET Web Forms/src/Core/Entities/JpgPage.cs b/Demos/ASP.NET Web Forms/src/Core/Entities/JpgPage.cs
new file mode 100644
index 000000000..cd9ab047f
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Core/Entities/JpgPage.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Text;
+
+namespace GroupDocs.Viewer.AspNetWebForms.Core.Entities
+{
+ public class JpgPage : Page
+ {
+ const string DATA_IMAGE = "data:image/jpeg;base64,";
+
+ public static string Extension => ".jpeg";
+
+ public override string GetContent()
+ {
+ return DATA_IMAGE + Convert.ToBase64String(Data);
+ }
+
+ public override void SetContent(string content)
+ {
+ this.Data = content.StartsWith(DATA_IMAGE)
+ ? Encoding.UTF8.GetBytes(content)
+ : Encoding.UTF8.GetBytes(content.Substring(DATA_IMAGE.Length - 1));
+ }
+
+ public JpgPage(int pageNumber, byte[] data)
+ : base(pageNumber, data)
+ {
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET Web Forms/src/Core/Entities/Page.cs b/Demos/ASP.NET Web Forms/src/Core/Entities/Page.cs
new file mode 100644
index 000000000..94453a619
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Core/Entities/Page.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace GroupDocs.Viewer.AspNetWebForms.Core.Entities
+{
+ public abstract class Page
+ {
+ private readonly List _resources = new List();
+
+ protected Page(int pageNumber, byte[] data)
+ {
+ PageNumber = pageNumber;
+ Data = data;
+ }
+
+ protected Page(int pageNumber, byte[] data, IEnumerable resources)
+ {
+ PageNumber = pageNumber;
+ Data = data;
+ _resources.AddRange(resources);
+ }
+
+ public IEnumerable Resources => _resources;
+
+ public int PageNumber { get; }
+
+ public byte[] Data { get; protected set; }
+
+ public abstract string GetContent();
+
+ public abstract void SetContent(string content);
+
+ public void AddResource(PageResource pageResource)
+ {
+ _resources.Add(pageResource);
+ }
+
+ public PageResource GetResource(string resourceName) =>
+ _resources.First(resource =>
+ resource.ResourceName.Equals(resourceName, StringComparison.InvariantCulture));
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET Web Forms/src/Core/Entities/PageInfo.cs b/Demos/ASP.NET Web Forms/src/Core/Entities/PageInfo.cs
new file mode 100644
index 000000000..a119b14a1
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Core/Entities/PageInfo.cs
@@ -0,0 +1,10 @@
+namespace GroupDocs.Viewer.AspNetWebForms.Core.Entities
+{
+ public class PageInfo
+ {
+ public int Width { get; set; }
+ public int Height { get; set; }
+ public int Number { get; set; }
+ public string Name { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET Web Forms/src/Core/Entities/PageResource.cs b/Demos/ASP.NET Web Forms/src/Core/Entities/PageResource.cs
new file mode 100644
index 000000000..0da699cd3
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Core/Entities/PageResource.cs
@@ -0,0 +1,15 @@
+namespace GroupDocs.Viewer.AspNetWebForms.Core.Entities
+{
+ public class PageResource
+ {
+ public PageResource(string resourceName, byte[] data)
+ {
+ ResourceName = resourceName;
+ Data = data;
+ }
+
+ public string ResourceName { get; }
+
+ public byte[] Data { get; }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET Web Forms/src/Core/Entities/Pages.cs b/Demos/ASP.NET Web Forms/src/Core/Entities/Pages.cs
new file mode 100644
index 000000000..8cd687720
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Core/Entities/Pages.cs
@@ -0,0 +1,35 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace GroupDocs.Viewer.AspNetWebForms.Core.Entities
+{
+ public class Pages : IEnumerable
+ {
+ readonly List _pages;
+
+ public Pages()
+ {
+ _pages = new List();
+ }
+
+ public Pages(IEnumerable pages)
+ {
+ _pages = pages.ToList();
+ }
+
+ public void Add(Page page) => _pages.Add(page);
+
+ public Page this[int index]
+ {
+ get => _pages[index];
+ set => _pages.Insert(index, value);
+ }
+
+ public IEnumerator GetEnumerator()
+ => _pages.GetEnumerator();
+
+ IEnumerator IEnumerable.GetEnumerator()
+ => GetEnumerator();
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET Web Forms/src/Core/Entities/PngPage.cs b/Demos/ASP.NET Web Forms/src/Core/Entities/PngPage.cs
new file mode 100644
index 000000000..2bf1ea6b0
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Core/Entities/PngPage.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Text;
+
+namespace GroupDocs.Viewer.AspNetWebForms.Core.Entities
+{
+ public class PngPage : Page
+ {
+ const string DATA_IMAGE = "data:image/png;base64,";
+
+ public static string Extension => ".png";
+
+ public override string GetContent() =>
+ DATA_IMAGE + Convert.ToBase64String(Data);
+
+ public override void SetContent(string content)
+ {
+ this.Data = content.StartsWith(DATA_IMAGE)
+ ? Encoding.UTF8.GetBytes(content)
+ : Encoding.UTF8.GetBytes(content.Substring(DATA_IMAGE.Length - 1));
+ }
+
+ public PngPage(int pageNumber, byte[] data)
+ : base(pageNumber, data)
+ {
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET Web Forms/src/Core/Extensions/FileCacheExtensions.cs b/Demos/ASP.NET Web Forms/src/Core/Extensions/FileCacheExtensions.cs
new file mode 100644
index 000000000..402db253f
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Core/Extensions/FileCacheExtensions.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Threading.Tasks;
+
+namespace GroupDocs.Viewer.AspNetWebForms.Core.Extensions
+{
+ public static class FileCacheExtensions
+ {
+ ///
+ /// Gets the entry associated with this key if present or acquires and sets the entry if not present.
+ ///
+ /// Type of entry.
+ /// The cache.
+ /// A key identifying the requested entry.
+ /// The source file relative file path.
+ /// The method which returns entry.
+ /// The entry associated with this key if present or acquires and sets the entry if not present.
+ public static async Task GetValueAsync(this IFileCache cache, string cacheKey, string filePath, Func> acquire)
+ {
+ var entry = await cache.TryGetValueAsync(cacheKey, filePath);
+ if (entry == null)
+ {
+ entry = await acquire();
+ await cache.SetAsync(cacheKey, filePath, entry);
+ }
+
+ return entry;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET Web Forms/src/Core/Extensions/MediaTypeExtensions.cs b/Demos/ASP.NET Web Forms/src/Core/Extensions/MediaTypeExtensions.cs
new file mode 100644
index 000000000..2ffc09d8f
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Core/Extensions/MediaTypeExtensions.cs
@@ -0,0 +1,24 @@
+using System.IO;
+
+namespace GroupDocs.Viewer.AspNetWebForms.Core.Extensions
+{
+ public static class ContentTypeExtensions
+ {
+ public static string ContentTypeFromFileName(this string filename)
+ {
+ var extension = Path.GetExtension(filename);
+
+ switch (extension)
+ {
+ case ".css": return "text/css";
+ case ".woff": return "font/woff";
+ case ".png": return "image/png";
+ case ".jpg":
+ case ".jpeg": return "image/jpeg";
+ case ".svg": return "image/svg+xml";
+ default:
+ return "application/octet-stream";
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET Web Forms/src/Core/FileTypeResolution/FileExtensionFileTypeResolver.cs b/Demos/ASP.NET Web Forms/src/Core/FileTypeResolution/FileExtensionFileTypeResolver.cs
new file mode 100644
index 000000000..7e484253f
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Core/FileTypeResolution/FileExtensionFileTypeResolver.cs
@@ -0,0 +1,16 @@
+using System.IO;
+using System.Threading.Tasks;
+
+namespace GroupDocs.Viewer.AspNetWebForms.Core.FileTypeResolution
+{
+ public class FileExtensionFileTypeResolver : IFileTypeResolver
+ {
+ public Task ResolveFileTypeAsync(string filePath)
+ {
+ string extension = Path.GetExtension(filePath);
+ FileType fileType = FileType.FromExtension(extension);
+
+ return Task.FromResult(fileType);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET Web Forms/src/Core/FileTypeResolution/IFileTypeResolver.cs b/Demos/ASP.NET Web Forms/src/Core/FileTypeResolution/IFileTypeResolver.cs
new file mode 100644
index 000000000..88b29d35c
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Core/FileTypeResolution/IFileTypeResolver.cs
@@ -0,0 +1,9 @@
+using System.Threading.Tasks;
+
+namespace GroupDocs.Viewer.AspNetWebForms.Core.FileTypeResolution
+{
+ public interface IFileTypeResolver
+ {
+ Task ResolveFileTypeAsync(string filePath);
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET Web Forms/src/Core/IAsyncLock.cs b/Demos/ASP.NET Web Forms/src/Core/IAsyncLock.cs
new file mode 100644
index 000000000..c9086efef
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Core/IAsyncLock.cs
@@ -0,0 +1,10 @@
+using System;
+using System.Threading.Tasks;
+
+namespace GroupDocs.Viewer.AspNetWebForms.Core
+{
+ public interface IAsyncLock
+ {
+ Task LockAsync(object key);
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET Web Forms/src/Core/IFileCache.cs b/Demos/ASP.NET Web Forms/src/Core/IFileCache.cs
new file mode 100644
index 000000000..b9f52d6b1
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Core/IFileCache.cs
@@ -0,0 +1,15 @@
+using System.Threading.Tasks;
+
+namespace GroupDocs.Viewer.AspNetWebForms.Core
+{
+ public interface IFileCache
+ {
+ TEntry TryGetValue(string cacheKey, string filePath);
+
+ Task TryGetValueAsync(string cacheKey, string filePath);
+
+ void Set(string cacheKey, string filePath, TEntry entry);
+
+ Task SetAsync(string cacheKey, string filePath, TEntry entry);
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET Web Forms/src/Core/IFileStorage.cs b/Demos/ASP.NET Web Forms/src/Core/IFileStorage.cs
new file mode 100644
index 000000000..2055883df
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Core/IFileStorage.cs
@@ -0,0 +1,15 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using GroupDocs.Viewer.AspNetWebForms.Core.Entities;
+
+namespace GroupDocs.Viewer.AspNetWebForms.Core
+{
+ public interface IFileStorage
+ {
+ Task> ListDirsAndFilesAsync(string dirPath);
+
+ Task ReadFileAsync(string filePath);
+
+ Task WriteFileAsync(string fileName, byte[] bytes, bool rewrite);
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET Web Forms/src/Core/IPageFormatter.cs b/Demos/ASP.NET Web Forms/src/Core/IPageFormatter.cs
new file mode 100644
index 000000000..8e9c1d31e
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Core/IPageFormatter.cs
@@ -0,0 +1,10 @@
+using System.Threading.Tasks;
+using GroupDocs.Viewer.AspNetWebForms.Core.Entities;
+
+namespace GroupDocs.Viewer.AspNetWebForms.Core
+{
+ public interface IPageFormatter
+ {
+ Task FormatAsync(FileCredentials fileCredentials, Page page);
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET Web Forms/src/Core/IViewer.cs b/Demos/ASP.NET Web Forms/src/Core/IViewer.cs
new file mode 100644
index 000000000..9e473105e
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Core/IViewer.cs
@@ -0,0 +1,16 @@
+using System.Threading.Tasks;
+using GroupDocs.Viewer.AspNetWebForms.Core.Entities;
+
+namespace GroupDocs.Viewer.AspNetWebForms.Core
+{
+ public interface IViewer
+ {
+ string PageExtension { get; }
+ Page CreatePage(int pageNumber, byte[] data);
+ Task GetDocumentInfoAsync(FileCredentials fileCredentials);
+ Task GetPageAsync(FileCredentials fileCredentials, int pageNumber);
+ Task GetPagesAsync(FileCredentials fileCredentials, int[] pageNumbers);
+ Task GetPdfAsync(FileCredentials fileCredentials);
+ Task GetPageResourceAsync(FileCredentials fileCredentials, int pageNumber, string resourceName);
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET Web Forms/src/Core/Licensing/IViewerLicenser.cs b/Demos/ASP.NET Web Forms/src/Core/Licensing/IViewerLicenser.cs
new file mode 100644
index 000000000..8d88f1ba6
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Core/Licensing/IViewerLicenser.cs
@@ -0,0 +1,7 @@
+namespace GroupDocs.Viewer.AspNetWebForms.Core.Licensing
+{
+ internal interface IViewerLicenser
+ {
+ void SetLicense();
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET Web Forms/src/Core/Licensing/LicenseFileViewerLicenser.cs b/Demos/ASP.NET Web Forms/src/Core/Licensing/LicenseFileViewerLicenser.cs
new file mode 100644
index 000000000..c7969ec08
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Core/Licensing/LicenseFileViewerLicenser.cs
@@ -0,0 +1,45 @@
+using System;
+using System.IO;
+using GroupDocs.Viewer.AspNetWebForms.Core.Configuration;
+
+namespace GroupDocs.Viewer.AspNetWebForms.Core.Licensing
+{
+ internal class LicenseFileViewerLicenser : IViewerLicenser
+ {
+ private readonly ViewerConfig _config;
+ private readonly object _lock = new object();
+ private bool _licenseSet;
+
+ public LicenseFileViewerLicenser(ViewerConfig config)
+ {
+ _config = config;
+ }
+
+ public void SetLicense()
+ {
+ if (_licenseSet)
+ return;
+
+ if (File.Exists(_config.LicensePath))
+ SetLicense(_config.LicensePath);
+
+ string licensePath = Environment.GetEnvironmentVariable("GROUPDOCS_LIC_PATH");
+ if (!string.IsNullOrEmpty(licensePath))
+ SetLicense(licensePath);
+ }
+
+ private void SetLicense(string licensePath)
+ {
+ lock (_lock)
+ {
+ if (!_licenseSet)
+ {
+ License license = new License();
+ license.SetLicense(licensePath);
+
+ _licenseSet = true;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET Web Forms/src/Core/PageFormatting/NoopPageFormatter.cs b/Demos/ASP.NET Web Forms/src/Core/PageFormatting/NoopPageFormatter.cs
new file mode 100644
index 000000000..70a3893a2
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Core/PageFormatting/NoopPageFormatter.cs
@@ -0,0 +1,11 @@
+using System.Threading.Tasks;
+using GroupDocs.Viewer.AspNetWebForms.Core.Entities;
+
+namespace GroupDocs.Viewer.AspNetWebForms.Core.PageFormatting
+{
+ public class NoopPageFormatter : IPageFormatter
+ {
+ public Task FormatAsync(FileCredentials fileCredentials, Page page) =>
+ Task.FromResult(page);
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET Web Forms/src/Core/Storage/LocalFileStorage.cs b/Demos/ASP.NET Web Forms/src/Core/Storage/LocalFileStorage.cs
new file mode 100644
index 000000000..ebf186820
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Core/Storage/LocalFileStorage.cs
@@ -0,0 +1,129 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using GroupDocs.Viewer.AspNetWebForms.Core.Entities;
+using GroupDocs.Viewer.AspNetWebForms.Core.Utils;
+
+namespace GroupDocs.Viewer.AspNetWebForms.Core.Storage
+{
+ public class LocalFileStorage : IFileStorage
+ {
+ private readonly string _storagePath;
+ private readonly TimeSpan _waitTimeout = TimeSpan.FromMilliseconds(100);
+
+ public LocalFileStorage(string storagePath)
+ {
+ _storagePath = storagePath;
+ }
+
+ private IEnumerable ListFiles(string folderPath)
+ {
+ var folderFullPath = string.IsNullOrEmpty(folderPath)
+ ? _storagePath
+ : Path.Combine(_storagePath, folderPath);
+
+ var dirs = Directory.GetDirectories(folderFullPath)
+ .Select(file => new FileInfo(file))
+ .Where(fileInfo => !fileInfo.Attributes.HasFlag(FileAttributes.Hidden))
+ .OrderBy(fileInfo => fileInfo.Name)
+ .ThenByDescending(fileInfo => fileInfo.CreationTime)
+ .Select(directory =>
+ FileSystemEntry.Directory(directory.Name, PathUtils.GetRelativePath(_storagePath, directory.FullName), 0L));
+
+ var files = Directory
+ .GetFiles(folderFullPath)
+ .Select(file => new FileInfo(file))
+ .Where(fileInfo => !fileInfo.Attributes.HasFlag(FileAttributes.Hidden))
+ .OrderBy(fileInfo => fileInfo.Name)
+ .ThenByDescending(fileInfo => fileInfo.CreationTime)
+ .Select(file =>
+ FileSystemEntry.File(file.Name, PathUtils.GetRelativePath(_storagePath, file.FullName), file.Length));
+
+ var dirsAndFiles = dirs.Concat(files);
+ return dirsAndFiles;
+ }
+
+ public Task> ListDirsAndFilesAsync(string dirPath) =>
+ Task.FromResult(ListFiles(dirPath));
+
+ public async Task ReadFileAsync(string filePath)
+ {
+ var fullPath = Path.Combine(_storagePath, filePath);
+ using (FileStream fs = GetStream(fullPath, FileMode.Open, FileAccess.Read, FileShare.None))
+ {
+ var memoryStream = new MemoryStream();
+ await fs.CopyToAsync(memoryStream);
+
+ return memoryStream.ToArray();
+ }
+ }
+
+ public async Task WriteFileAsync(string fileName, byte[] bytes, bool rewrite)
+ {
+ var newFileName = rewrite ? fileName : GetFreeFileName(fileName);
+ var fullPath = Path.Combine(_storagePath, newFileName);
+ var fileMode = rewrite ? FileMode.Create : FileMode.CreateNew;
+
+ using (FileStream fs = GetStream(fullPath, fileMode, FileAccess.Write, FileShare.None))
+ {
+ await fs.WriteAsync(bytes, 0, bytes.Length);
+ }
+
+ return newFileName;
+ }
+
+ private FileStream GetStream(string path, FileMode mode, FileAccess access, FileShare share)
+ {
+ FileStream stream = null;
+ TimeSpan interval = new TimeSpan(0, 0, 0, 0, 50);
+ TimeSpan totalTime = new TimeSpan();
+
+ while (stream == null)
+ {
+ try
+ {
+ stream = File.Open(path, mode, access, share);
+ }
+ catch (IOException)
+ {
+ Thread.Sleep(interval);
+ totalTime += interval;
+
+ if (_waitTimeout.Ticks != 0 && totalTime > _waitTimeout)
+ {
+ throw;
+ }
+ }
+ }
+
+ return stream;
+ }
+
+ private string GetFreeFileName(string fileName)
+ {
+ var fullPath = Path.Combine(_storagePath, fileName);
+
+ if (!File.Exists(fullPath))
+ return fileName;
+
+ List dirFiles = Directory.GetFiles(_storagePath)
+ .Select(filePath => Path.GetFileName(filePath))
+ .ToList();
+
+ var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName);
+ var number = 1;
+ string fileNameCandidate;
+ do
+ {
+ string newFileName = $"{fileNameWithoutExtension} ({number})";
+ fileNameCandidate = fileName.Replace(fileNameWithoutExtension, newFileName);
+ number++;
+ } while (dirFiles.Contains(fileNameCandidate));
+
+ return fileNameCandidate;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET Web Forms/src/Core/Utils/PathUtils.cs b/Demos/ASP.NET Web Forms/src/Core/Utils/PathUtils.cs
new file mode 100644
index 000000000..f2a1b2341
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Core/Utils/PathUtils.cs
@@ -0,0 +1,54 @@
+using System;
+using System.IO;
+using System.Linq;
+
+namespace GroupDocs.Viewer.AspNetWebForms.Core.Utils
+{
+ public static class PathUtils
+ {
+ public static string GetRelativePath(string relativeTo, string path)
+ {
+ if (string.IsNullOrEmpty(relativeTo))
+ throw new ArgumentNullException(nameof(relativeTo));
+
+ if (string.IsNullOrEmpty(path))
+ throw new ArgumentNullException("path");
+
+ Uri fromUri = new Uri(AppendDirectorySeparatorChar(relativeTo));
+ Uri toUri = new Uri(AppendDirectorySeparatorChar(path));
+
+ if (fromUri.Scheme != toUri.Scheme)
+ return path;
+
+ Uri relativeUri = fromUri.MakeRelativeUri(toUri);
+ string relativePath = Uri.UnescapeDataString(relativeUri.ToString());
+
+ if (string.Equals(toUri.Scheme, Uri.UriSchemeFile, StringComparison.OrdinalIgnoreCase))
+ relativePath = relativePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
+
+ return relativePath;
+ }
+
+ private static string AppendDirectorySeparatorChar(string path)
+ {
+ // Append a slash only if the path is a directory and does not have a slash.
+ if (!Path.HasExtension(path) &&
+ !path.EndsWith(Path.DirectorySeparatorChar.ToString()))
+ {
+ return path + Path.DirectorySeparatorChar;
+ }
+
+ return path;
+ }
+
+ public static string RemoveInvalidFileNameChars(string path)
+ {
+ Path.GetInvalidFileNameChars().ToList().ForEach(ch =>
+ {
+ path = path.Replace(ch.ToString(), string.Empty);
+ });
+
+ return path;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET Web Forms/src/Core/ViewerType.cs b/Demos/ASP.NET Web Forms/src/Core/ViewerType.cs
new file mode 100644
index 000000000..b7e7ac83e
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Core/ViewerType.cs
@@ -0,0 +1,10 @@
+namespace GroupDocs.Viewer.AspNetWebForms.Core
+{
+ public enum ViewerType
+ {
+ HtmlWithEmbeddedResources,
+ HtmlWithExternalResources,
+ Png,
+ Jpg,
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET Web Forms/src/Core/Viewers/BaseViewer.cs b/Demos/ASP.NET Web Forms/src/Core/Viewers/BaseViewer.cs
new file mode 100644
index 000000000..cafcc5aef
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Core/Viewers/BaseViewer.cs
@@ -0,0 +1,175 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using GroupDocs.Viewer.AspNetWebForms.Core.Configuration;
+using GroupDocs.Viewer.AspNetWebForms.Core.Entities;
+using GroupDocs.Viewer.AspNetWebForms.Core.FileTypeResolution;
+using GroupDocs.Viewer.AspNetWebForms.Core.Licensing;
+using GroupDocs.Viewer.AspNetWebForms.Core.Viewers.Extensions;
+using GroupDocs.Viewer.Options;
+using GroupDocs.Viewer.Results;
+using Page = GroupDocs.Viewer.AspNetWebForms.Core.Entities.Page;
+
+namespace GroupDocs.Viewer.AspNetWebForms.Core.Viewers
+{
+ internal abstract class BaseViewer : IViewer, IDisposable
+ {
+ private readonly ViewerConfig _config;
+ private readonly IViewerLicenser _viewerLicenser;
+ private readonly IFileStorage _fileStorage;
+ private readonly IFileTypeResolver _fileTypeResolver;
+ private readonly IPageFormatter _pageFormatter;
+ private Viewer _viewer;
+
+ protected BaseViewer(ViewerConfig config,
+ IViewerLicenser viewerLicenser,
+ IFileStorage fileStorage,
+ IFileTypeResolver fileTypeResolver,
+ IPageFormatter pageFormatter)
+ {
+ _config = config;
+ _viewerLicenser = viewerLicenser;
+ _fileStorage = fileStorage;
+ _fileTypeResolver = fileTypeResolver;
+ _pageFormatter = pageFormatter;
+ }
+
+ public abstract string PageExtension { get; }
+
+ public abstract Page CreatePage(int pageNumber, byte[] data);
+
+ protected abstract Page RenderPage(Viewer viewer, string filePath, int pageNumber);
+
+ protected abstract ViewInfoOptions CreateViewInfoOptions();
+
+ public async Task GetDocumentInfoAsync(FileCredentials fileCredentials)
+ {
+ var viewer = await InitViewerAsync(fileCredentials);
+ var viewInfoOptions = CreateViewInfoOptions();
+ var viewInfo = viewer.GetViewInfo(viewInfoOptions);
+
+ var documentInfo = ToDocumentInfo(viewInfo);
+ return documentInfo;
+ }
+
+ public async Task GetPageAsync(FileCredentials fileCredentials, int pageNumber)
+ {
+ var viewer = await InitViewerAsync(fileCredentials);
+ var page = await RenderPageInternalAsync(viewer, fileCredentials, pageNumber);
+
+ return page;
+ }
+
+ public async Task GetPagesAsync(FileCredentials fileCredentials, int[] pageNumbers)
+ {
+ var viewer = await InitViewerAsync(fileCredentials);
+
+ var pages = new Pages();
+
+ foreach (var pageNumber in pageNumbers)
+ {
+ var page = await RenderPageInternalAsync(viewer, fileCredentials, pageNumber);
+ pages.Add(page);
+ }
+
+ return pages;
+ }
+
+ public async Task GetPdfAsync(FileCredentials fileCredentials)
+ {
+ var pdfStream = new MemoryStream();
+ var viewOptions = CreatePdfViewOptions(pdfStream);
+
+ var viewer = await InitViewerAsync(fileCredentials);
+ viewer.View(viewOptions);
+
+ return pdfStream.ToArray();
+ }
+
+ public abstract Task GetPageResourceAsync(FileCredentials fileCredentials, int pageNumber, string resourceName);
+
+ private PdfViewOptions CreatePdfViewOptions(MemoryStream pdfStream)
+ {
+ var viewOptions = new PdfViewOptions(() => pdfStream, _ => { /* NOTE: nothing to do here */ });
+
+ viewOptions.CopyViewOptions(_config.PdfViewOptions);
+
+ return viewOptions;
+ }
+
+ private async Task InitViewerAsync(FileCredentials fileCredentials)
+ {
+ if (_viewer == null)
+ {
+ _viewerLicenser.SetLicense();
+
+ var fileStream = await GetFileStreamAsync(fileCredentials.FilePath);
+ var loadOptions = await CreateLoadOptionsAsync(fileCredentials);
+ _viewer = new Viewer(fileStream, loadOptions);
+ }
+
+ return _viewer;
+ }
+
+ private async Task GetFileStreamAsync(string filePath)
+ {
+ byte[] bytes = await _fileStorage.ReadFileAsync(filePath);
+ MemoryStream memoryStream = new MemoryStream(bytes);
+ return memoryStream;
+ }
+
+ private async Task CreateLoadOptionsAsync(FileCredentials fileCredentials)
+ {
+ FileType loadFileType = FileType.FromExtension(fileCredentials.FileType);
+ if(loadFileType == FileType.Unknown)
+ loadFileType = await _fileTypeResolver.ResolveFileTypeAsync(fileCredentials.FilePath);
+
+ LoadOptions loadOptions = new LoadOptions
+ {
+ FileType = FileType.FromExtension(loadFileType.Extension),
+ Password = fileCredentials.Password,
+ ResourceLoadingTimeout = TimeSpan.FromSeconds(3)
+ };
+ return loadOptions;
+ }
+
+ private async Task RenderPageInternalAsync(
+ Viewer viewer, FileCredentials fileCredentials, int pageNumber)
+ {
+ var page = RenderPage(viewer, fileCredentials.FilePath, pageNumber);
+ page = await _pageFormatter.FormatAsync(fileCredentials, page);
+
+ return page;
+ }
+
+ private static DocumentInfo ToDocumentInfo(ViewInfo viewInfo)
+ {
+ var printAllowed = true;
+ if (viewInfo is PdfViewInfo info)
+ printAllowed = info.PrintingAllowed;
+
+ var fileType = viewInfo.FileType.Extension
+ .Replace(".", string.Empty);
+
+ return new DocumentInfo
+ {
+ FileType = fileType,
+ PrintAllowed = printAllowed,
+ Pages = viewInfo.Pages.Select(page => new PageInfo
+ {
+ Number = page.Number,
+ Width = page.Width,
+ Height = page.Height,
+ Name = page.Name
+ })
+ };
+ }
+
+ public void Dispose()
+ {
+ _viewer?.Dispose();
+ _viewer = null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET Web Forms/src/Core/Viewers/Extensions/ViewOptionsExtensions.cs b/Demos/ASP.NET Web Forms/src/Core/Viewers/Extensions/ViewOptionsExtensions.cs
new file mode 100644
index 000000000..b9727f39b
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Core/Viewers/Extensions/ViewOptionsExtensions.cs
@@ -0,0 +1,90 @@
+using GroupDocs.Viewer.Options;
+
+namespace GroupDocs.Viewer.AspNetWebForms.Core.Viewers.Extensions
+{
+ internal static class ViewOptionsExtensions
+ {
+ public static void CopyViewOptions(this HtmlViewOptions dst, HtmlViewOptions src)
+ {
+ dst.CopyBaseViewOptions(src);
+ dst.CopyHtmlViewOptions(src);
+ }
+
+ public static void CopyViewOptions(this PdfViewOptions dst, PdfViewOptions src)
+ {
+ dst.CopyBaseViewOptions(src);
+ dst.CopyPdfViewOptions(src);
+ }
+
+ public static void CopyViewOptions(this PngViewOptions dst, PngViewOptions src)
+ {
+ dst.CopyBaseViewOptions(src);
+ dst.CopyPngViewOptions(src);
+ }
+
+ public static void CopyViewOptions(this JpgViewOptions dst, JpgViewOptions src)
+ {
+ dst.CopyBaseViewOptions(src);
+ dst.CopyJpgViewOptions(src);
+ }
+
+ private static void CopyBaseViewOptions(this BaseViewOptions dst, BaseViewOptions src)
+ {
+ dst.RenderComments = src.RenderComments;
+ dst.RenderNotes = src.RenderNotes;
+ dst.RenderHiddenPages = src.RenderHiddenPages;
+ dst.DefaultFontName = src.DefaultFontName;
+ dst.ArchiveOptions = src.ArchiveOptions;
+ dst.CadOptions = src.CadOptions;
+ dst.EmailOptions = src.EmailOptions;
+ dst.OutlookOptions = src.OutlookOptions;
+ dst.PdfOptions = src.PdfOptions;
+ dst.ProjectManagementOptions = src.ProjectManagementOptions;
+ dst.SpreadsheetOptions = src.SpreadsheetOptions;
+ dst.WordProcessingOptions = src.WordProcessingOptions;
+ }
+
+ private static void CopyHtmlViewOptions(this HtmlViewOptions dst, HtmlViewOptions src)
+ {
+ dst.RenderResponsive = src.RenderResponsive;
+ dst.Minify = src.Minify;
+ dst.RenderToSinglePage = src.RenderToSinglePage;
+ dst.ImageMaxWidth = src.ImageMaxWidth;
+ dst.ImageMaxHeight = src.ImageMaxHeight;
+ dst.ImageWidth = src.ImageWidth;
+ dst.ImageHeight = src.ImageHeight;
+ dst.ForPrinting = src.ForPrinting;
+ dst.ExcludeFonts = src.ExcludeFonts;
+ dst.FontsToExclude = src.FontsToExclude;
+ dst.FontsToExclude = src.FontsToExclude;
+ }
+
+ private static void CopyPdfViewOptions(this PdfViewOptions dst, PdfViewOptions src)
+ {
+ dst.JpgQuality = src.JpgQuality;
+ dst.Security = src.Security;
+ dst.ImageMaxWidth = src.ImageMaxWidth;
+ dst.ImageMaxHeight = src.ImageMaxHeight;
+ dst.ImageWidth = src.ImageWidth;
+ dst.ImageHeight = src.ImageHeight;
+ }
+
+ private static void CopyPngViewOptions(this PngViewOptions dst, PngViewOptions src)
+ {
+ dst.ExtractText = src.ExtractText;
+ dst.Width = src.Width;
+ dst.Height = src.Height;
+ dst.MaxWidth = src.MaxWidth;
+ dst.MaxHeight = src.MaxHeight;
+ }
+ private static void CopyJpgViewOptions(this JpgViewOptions dst, JpgViewOptions src)
+ {
+ dst.Quality = src.Quality;
+ dst.ExtractText = src.ExtractText;
+ dst.Width = src.Width;
+ dst.Height = src.Height;
+ dst.MaxWidth = src.MaxWidth;
+ dst.MaxHeight = src.MaxHeight;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET Web Forms/src/Core/Viewers/HtmlWithEmbeddedResourcesViewer.cs b/Demos/ASP.NET Web Forms/src/Core/Viewers/HtmlWithEmbeddedResourcesViewer.cs
new file mode 100644
index 000000000..df59ee39b
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Core/Viewers/HtmlWithEmbeddedResourcesViewer.cs
@@ -0,0 +1,63 @@
+using System.IO;
+using System.Threading.Tasks;
+using GroupDocs.Viewer.AspNetWebForms.Core.Configuration;
+using GroupDocs.Viewer.AspNetWebForms.Core.Entities;
+using GroupDocs.Viewer.AspNetWebForms.Core.FileTypeResolution;
+using GroupDocs.Viewer.AspNetWebForms.Core.Licensing;
+using GroupDocs.Viewer.AspNetWebForms.Core.Viewers.Extensions;
+using GroupDocs.Viewer.Options;
+using Page = GroupDocs.Viewer.AspNetWebForms.Core.Entities.Page;
+
+namespace GroupDocs.Viewer.AspNetWebForms.Core.Viewers
+{
+ internal class HtmlWithEmbeddedResourcesViewer : BaseViewer
+ {
+ private readonly ViewerConfig _config;
+
+ public HtmlWithEmbeddedResourcesViewer(ViewerConfig config,
+ IViewerLicenser licenser,
+ IFileStorage fileStorage,
+ IFileTypeResolver fileTypeResolver,
+ IPageFormatter pageFormatter)
+ : base(config, licenser, fileStorage, fileTypeResolver, pageFormatter)
+ {
+ _config = config;
+ }
+
+ public override string PageExtension => HtmlPage.Extension;
+
+ public override Page CreatePage(int pageNumber, byte[] data)
+ => new HtmlPage(pageNumber, data);
+
+ public override Task GetPageResourceAsync(
+ FileCredentials fileCredentials, int pageNumber, string resourceName) =>
+ throw new System.NotImplementedException(
+ $"{nameof(HtmlWithEmbeddedResourcesViewer)} does not support retrieving external HTML resources.");
+
+ protected override ViewInfoOptions CreateViewInfoOptions() =>
+ ViewInfoOptions.FromHtmlViewOptions(_config.HtmlViewOptions);
+
+ protected override Page RenderPage(Viewer viewer, string filePath, int pageNumber)
+ {
+ var pageStream = new MemoryStream();
+ var viewOptions = CreateViewOptions(pageStream);
+
+ viewer.View(viewOptions, pageNumber);
+
+ var bytes = pageStream.ToArray();
+ var page = CreatePage(pageNumber, bytes);
+
+ return page;
+ }
+
+ private HtmlViewOptions CreateViewOptions(MemoryStream pageStream)
+ {
+ var viewOptions = HtmlViewOptions.ForEmbeddedResources(_ => pageStream,
+ (_, __) => { /*NOTE: Do nothing here*/ });
+
+ viewOptions.CopyViewOptions(_config.HtmlViewOptions);
+
+ return viewOptions;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET Web Forms/src/Core/Viewers/HtmlWithExternalResourcesViewer.cs b/Demos/ASP.NET Web Forms/src/Core/Viewers/HtmlWithExternalResourcesViewer.cs
new file mode 100644
index 000000000..ab93775e0
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Core/Viewers/HtmlWithExternalResourcesViewer.cs
@@ -0,0 +1,120 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Threading.Tasks;
+using GroupDocs.Viewer.AspNetWebForms.Core.Configuration;
+using GroupDocs.Viewer.AspNetWebForms.Core.Entities;
+using GroupDocs.Viewer.AspNetWebForms.Core.FileTypeResolution;
+using GroupDocs.Viewer.AspNetWebForms.Core.Licensing;
+using GroupDocs.Viewer.AspNetWebForms.Core.Viewers.Extensions;
+using GroupDocs.Viewer.Interfaces;
+using GroupDocs.Viewer.Options;
+using GroupDocs.Viewer.Results;
+using Page = GroupDocs.Viewer.AspNetWebForms.Core.Entities.Page;
+
+namespace GroupDocs.Viewer.AspNetWebForms.Core.Viewers
+{
+ internal class HtmlWithExternalResourcesViewer : BaseViewer
+ {
+ private readonly ViewerConfig _config;
+
+ public HtmlWithExternalResourcesViewer(
+ ViewerConfig config,
+ IViewerLicenser licenser,
+ IFileStorage fileStorage,
+ IFileTypeResolver fileTypeResolver,
+ IPageFormatter pageFormatter)
+ : base(config, licenser, fileStorage, fileTypeResolver, pageFormatter)
+ {
+ _config = config;
+ }
+
+ public override string PageExtension => HtmlPage.Extension;
+
+ public override Page CreatePage(int pageNumber, byte[] data)
+ => new HtmlPage(pageNumber, data);
+
+ protected override Page RenderPage(Viewer viewer, string filePath, int pageNumber)
+ {
+ var basePath = Constants.API_PATH;
+ var actionName = Constants.LOAD_DOCUMENT_PAGE_RESOURCE_ACTION_NAME;
+
+ var streamFactory = new MemoryPageStreamFactory(basePath, actionName, filePath);
+ var viewOptions = HtmlViewOptions.ForExternalResources(streamFactory, streamFactory);
+ viewOptions.CopyViewOptions(_config.HtmlViewOptions);
+ viewer.View(viewOptions, pageNumber);
+
+ var pageContents = streamFactory.GetPageContents();
+ var page = CreatePage(pageNumber, pageContents.GetPageData());
+ foreach (var resource in pageContents.Resources)
+ {
+ var pageResource = new PageResource(resource.Key, resource.Value.ToArray());
+ page.AddResource(pageResource);
+ }
+
+ return page;
+ }
+
+ protected override ViewInfoOptions CreateViewInfoOptions() =>
+ ViewInfoOptions.FromHtmlViewOptions(_config.HtmlViewOptions);
+
+ public override async Task GetPageResourceAsync(
+ FileCredentials fileCredentials, int pageNumber, string resourceName)
+ {
+ var page = await GetPageAsync(fileCredentials, pageNumber);
+ var resource = page.GetResource(resourceName);
+
+ return resource.Data;
+ }
+
+ private class MemoryPageStreamFactory : IPageStreamFactory, IResourceStreamFactory
+ {
+ private readonly string _basePath;
+ private readonly string _actionName;
+ private readonly string _filePath;
+ private readonly PageContents _pageContents;
+
+ public MemoryPageStreamFactory(string basePath, string actionName, string filePath)
+ {
+ _basePath = basePath;
+ _actionName = actionName;
+ _filePath = WebUtility.UrlEncode(filePath);
+ _pageContents = new PageContents();
+ }
+
+ public PageContents GetPageContents() =>
+ _pageContents;
+
+ public Stream CreatePageStream(int pageNumber) =>
+ _pageContents.GetPageStream();
+
+ public void ReleasePageStream(int pageNumber, Stream pageStream) { }
+
+ public Stream CreateResourceStream(int pageNumber, Resource resource) =>
+ _pageContents.GetResourceStream(resource.FileName);
+
+ public string CreateResourceUrl(int pageNumber, Resource resource) =>
+ $"/{_basePath}/{_actionName}?guid={_filePath}&pageNumber={pageNumber}&resourceName={resource.FileName}";
+
+ public void ReleaseResourceStream(int pageNumber, Resource resource, Stream resourceStream) { }
+ }
+
+ private class PageContents
+ {
+ private MemoryStream PageStream { get; } = new MemoryStream();
+
+ public Dictionary Resources { get; } = new Dictionary();
+
+ public byte[] GetPageData() => PageStream.ToArray();
+
+ public Stream GetPageStream() => PageStream;
+
+ public Stream GetResourceStream(string fileName)
+ {
+ var resourceStream = new MemoryStream();
+ Resources.Add(fileName, resourceStream);
+ return resourceStream;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET Web Forms/src/Core/Viewers/JpgViewer.cs b/Demos/ASP.NET Web Forms/src/Core/Viewers/JpgViewer.cs
new file mode 100644
index 000000000..e60cb22f2
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Core/Viewers/JpgViewer.cs
@@ -0,0 +1,63 @@
+using System.IO;
+using System.Threading.Tasks;
+using GroupDocs.Viewer.AspNetWebForms.Core.Configuration;
+using GroupDocs.Viewer.AspNetWebForms.Core.Entities;
+using GroupDocs.Viewer.AspNetWebForms.Core.FileTypeResolution;
+using GroupDocs.Viewer.AspNetWebForms.Core.Licensing;
+using GroupDocs.Viewer.AspNetWebForms.Core.Viewers.Extensions;
+using GroupDocs.Viewer.Options;
+using Page = GroupDocs.Viewer.AspNetWebForms.Core.Entities.Page;
+
+namespace GroupDocs.Viewer.AspNetWebForms.Core.Viewers
+{
+ internal class JpgViewer : BaseViewer
+ {
+ private readonly ViewerConfig _config;
+
+ public JpgViewer(ViewerConfig config,
+ IViewerLicenser licenser,
+ IFileStorage fileStorage,
+ IFileTypeResolver fileTypeResolver,
+ IPageFormatter pageFormatter)
+ : base(config, licenser, fileStorage, fileTypeResolver, pageFormatter)
+ {
+ _config = config;
+ }
+
+ public override string PageExtension => JpgPage.Extension;
+
+ public override Page CreatePage(int pageNumber, byte[] data) =>
+ new JpgPage(pageNumber, data);
+
+ public override Task GetPageResourceAsync(
+ FileCredentials fileCredentials, int pageNumber, string resourceName) =>
+ throw new System.NotImplementedException(
+ $"{nameof(JpgViewer)} does not support retrieving external HTML resources.");
+
+ protected override Page RenderPage(Viewer viewer, string filePath, int pageNumber)
+ {
+ var pageStream = new MemoryStream();
+ var viewOptions = CreateViewOptions(pageStream);
+
+ viewer.View(viewOptions, pageNumber);
+
+ var bytes = pageStream.ToArray();
+ var page = CreatePage(pageNumber, bytes);
+
+ return page;
+ }
+
+ protected override ViewInfoOptions CreateViewInfoOptions() =>
+ ViewInfoOptions.FromJpgViewOptions(_config.JpgViewOptions);
+
+ private JpgViewOptions CreateViewOptions(MemoryStream pageStream)
+ {
+ var viewOptions = new JpgViewOptions(_ => pageStream,
+ (_, __) => { /*NOTE: Do nothing here*/ });
+
+ viewOptions.CopyViewOptions(_config.JpgViewOptions);
+
+ return viewOptions;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET Web Forms/src/Core/Viewers/PngViewer.cs b/Demos/ASP.NET Web Forms/src/Core/Viewers/PngViewer.cs
new file mode 100644
index 000000000..e5d24cbe1
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Core/Viewers/PngViewer.cs
@@ -0,0 +1,63 @@
+using System.IO;
+using System.Threading.Tasks;
+using GroupDocs.Viewer.AspNetWebForms.Core.Configuration;
+using GroupDocs.Viewer.AspNetWebForms.Core.Entities;
+using GroupDocs.Viewer.AspNetWebForms.Core.FileTypeResolution;
+using GroupDocs.Viewer.AspNetWebForms.Core.Licensing;
+using GroupDocs.Viewer.AspNetWebForms.Core.Viewers.Extensions;
+using GroupDocs.Viewer.Options;
+using Page = GroupDocs.Viewer.AspNetWebForms.Core.Entities.Page;
+
+namespace GroupDocs.Viewer.AspNetWebForms.Core.Viewers
+{
+ internal class PngViewer : BaseViewer
+ {
+ private readonly ViewerConfig _config;
+
+ public PngViewer(ViewerConfig config,
+ IViewerLicenser licenser,
+ IFileStorage fileStorage,
+ IFileTypeResolver fileTypeResolver,
+ IPageFormatter pageFormatter)
+ : base(config, licenser, fileStorage, fileTypeResolver, pageFormatter)
+ {
+ _config = config;
+ }
+
+ public override string PageExtension => PngPage.Extension;
+
+ public override Page CreatePage(int pageNumber, byte[] data) =>
+ new PngPage(pageNumber, data);
+
+ public override Task GetPageResourceAsync(
+ FileCredentials fileCredentials, int pageNumber, string resourceName) =>
+ throw new System.NotImplementedException(
+ $"{nameof(PngViewer)} does not support retrieving external HTML resources.");
+
+ protected override Page RenderPage(Viewer viewer, string filePath, int pageNumber)
+ {
+ var pageStream = new MemoryStream();
+ var viewOptions = CreateViewOptions(pageStream);
+
+ viewer.View(viewOptions, pageNumber);
+
+ var bytes = pageStream.ToArray();
+ var page = CreatePage(pageNumber, bytes);
+
+ return page;
+ }
+
+ protected override ViewInfoOptions CreateViewInfoOptions() =>
+ ViewInfoOptions.FromJpgViewOptions(_config.JpgViewOptions);
+
+ private PngViewOptions CreateViewOptions(MemoryStream pageStream)
+ {
+ var viewOptions = new PngViewOptions(_ => pageStream,
+ (_, __) => { /*NOTE: Do nothing here*/ });
+
+ viewOptions.CopyViewOptions(_config.PngViewOptions);
+
+ return viewOptions;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET Web Forms/src/Default.aspx b/Demos/ASP.NET Web Forms/src/Default.aspx
new file mode 100644
index 000000000..04bfda6ce
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Default.aspx
@@ -0,0 +1,21 @@
+<%@ Page Title="Home Page" Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="GroupDocs.Viewer.AspNetWebForms._Default" %>
+<%@ Import Namespace="GroupDocs.Viewer.AspNetWebForms.Core" %>
+
+
+
+
+
+
+ GroupDocs.Viewer for .NET ASP.NET Web Forms Demo
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demos/WebForms/src/Viewer.aspx.cs b/Demos/ASP.NET Web Forms/src/Default.aspx.cs
similarity index 52%
rename from Demos/WebForms/src/Viewer.aspx.cs
rename to Demos/ASP.NET Web Forms/src/Default.aspx.cs
index 942130936..cfceb1837 100644
--- a/Demos/WebForms/src/Viewer.aspx.cs
+++ b/Demos/ASP.NET Web Forms/src/Default.aspx.cs
@@ -1,11 +1,13 @@
using System;
+using System.Web.UI;
-namespace GroupDocs.Viewer.WebForms
+namespace GroupDocs.Viewer.AspNetWebForms
{
- public partial class Viewer : System.Web.UI.Page
+ public partial class _Default : Page
{
protected void Page_Load(object sender, EventArgs e)
{
+
}
}
}
\ No newline at end of file
diff --git a/Demos/WebForms/src/Viewer.aspx.designer.cs b/Demos/ASP.NET Web Forms/src/Default.aspx.designer.cs
similarity index 74%
rename from Demos/WebForms/src/Viewer.aspx.designer.cs
rename to Demos/ASP.NET Web Forms/src/Default.aspx.designer.cs
index 5aae4f254..8f7abe8e4 100644
--- a/Demos/WebForms/src/Viewer.aspx.designer.cs
+++ b/Demos/ASP.NET Web Forms/src/Default.aspx.designer.cs
@@ -3,13 +3,15 @@
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
-// the code is regenerated.
+// the code is regenerated.
//
//------------------------------------------------------------------------------
-namespace GroupDocs.Viewer.WebForms {
-
-
- public partial class Viewer {
+namespace GroupDocs.Viewer.AspNetWebForms
+{
+
+
+ public partial class _Default
+ {
}
}
diff --git a/Demos/WebForms/src/Global.asax b/Demos/ASP.NET Web Forms/src/Global.asax
similarity index 56%
rename from Demos/WebForms/src/Global.asax
rename to Demos/ASP.NET Web Forms/src/Global.asax
index 64dcf44f2..139f4c71b 100644
--- a/Demos/WebForms/src/Global.asax
+++ b/Demos/ASP.NET Web Forms/src/Global.asax
@@ -1 +1 @@
-<%@ Application Codebehind="Global.asax.cs" Inherits="GroupDocs.Viewer.WebForms.Global" Language="C#" %>
+<%@ Application Codebehind="Global.asax.cs" Inherits="GroupDocs.Viewer.AspNetWebForms.Global" Language="C#" %>
diff --git a/Demos/ASP.NET Web Forms/src/Global.asax.cs b/Demos/ASP.NET Web Forms/src/Global.asax.cs
new file mode 100644
index 000000000..d25aa1273
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/Global.asax.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Web;
+using System.Web.Http;
+using Unity.WebApi;
+using Unity;
+using GroupDocs.Viewer.AspNetWebForms.Core.Caching;
+using GroupDocs.Viewer.AspNetWebForms.Core.Configuration;
+using GroupDocs.Viewer.AspNetWebForms.Core.FileTypeResolution;
+using GroupDocs.Viewer.AspNetWebForms.Core.Licensing;
+using GroupDocs.Viewer.AspNetWebForms.Core.PageFormatting;
+using GroupDocs.Viewer.AspNetWebForms.Core.Storage;
+using GroupDocs.Viewer.AspNetWebForms.Core.Viewers;
+using GroupDocs.Viewer.AspNetWebForms.Core;
+
+namespace GroupDocs.Viewer.AspNetWebForms
+{
+ public class Global : HttpApplication
+ {
+ void Application_Start(object sender, EventArgs e)
+ {
+ UnityContainer container = new UnityContainer();
+ ConfigureServices(container);
+
+ GlobalConfiguration.Configuration.DependencyResolver =
+ new UnityDependencyResolver(container);
+
+ GlobalConfiguration.Configure(WebApiConfig.Register);
+ }
+
+ private void ConfigureServices(UnityContainer container)
+ {
+ var viewerType = ViewerType.HtmlWithEmbeddedResources;
+
+ //Temporary license can be requested at https://purchase.groupdocs.com/temporary-license
+ var licensePath = Server.MapPath("~/GroupDocs.Viewer.lic");
+ var filesPath = Server.MapPath("~/Storage/Files");
+ var cachePath = Server.MapPath("~/Storage/Cache");
+
+ var uiConfig = UIConfig.Instance
+ .SetViewerType(viewerType);
+ var viewerConfig = ViewerConfig.Instance
+ .SetLicensePath(licensePath);
+
+ container.RegisterFactory(c => viewerConfig);
+ container.RegisterFactory(c => uiConfig);
+ container.RegisterFactory(c => new LocalFileStorage(filesPath));
+ container.RegisterFactory(c => new LocalFileCache(cachePath));
+ container.RegisterType();
+ container.RegisterType();
+ container.RegisterType();
+ container.RegisterType();
+
+ container.RegisterFactory(c =>
+ {
+ IViewer viewer;
+ switch (uiConfig.ViewerType)
+ {
+ case ViewerType.HtmlWithExternalResources:
+ viewer = c.Resolve();
+ break;
+ case ViewerType.Jpg:
+ viewer = c.Resolve();
+ break;
+ case ViewerType.Png:
+ viewer = c.Resolve();
+ break;
+ default:
+ viewer = c.Resolve();
+ break;
+ }
+
+ var fileCache = c.Resolve();
+ var asyncLock = c.Resolve();
+
+ return new CachingViewer(viewer, fileCache, asyncLock);
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/Demos/ASP.NET Web Forms/src/GroupDocs.Viewer.AspNetWebForms.csproj b/Demos/ASP.NET Web Forms/src/GroupDocs.Viewer.AspNetWebForms.csproj
new file mode 100644
index 000000000..aca54e276
--- /dev/null
+++ b/Demos/ASP.NET Web Forms/src/GroupDocs.Viewer.AspNetWebForms.csproj
@@ -0,0 +1,275 @@
+
+
+
+
+ Debug
+ AnyCPU
+
+
+ 2.0
+ {2DF2BE92-E5E8-49FC-A73F-6FA0098CCC1E}
+ {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}
+ Library
+ Properties
+ GroupDocs.Viewer.AspNetWebForms
+ GroupDocs.Viewer.AspNetWebForms
+ v4.8
+ true
+
+ 44349
+
+
+
+
+
+
+
+
+ true
+ full
+ false
+ bin\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ true
+ pdbonly
+ true
+ bin\
+ TRACE
+ prompt
+ 4
+
+
+
+ packages\GroupDocs.Viewer.22.9.0\lib\net40\GroupDocs.Viewer.dll
+
+
+ packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.3.6.0\lib\net45\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll
+
+
+
+ packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll
+
+
+
+
+
+
+
+
+
+ packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll
+
+
+ packages\Microsoft.AspNet.Cors.5.2.9\lib\net45\System.Web.Cors.dll
+
+
+
+ packages\Microsoft.AspNet.WebApi.Cors.5.2.9\lib\net45\System.Web.Http.Cors.dll
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+ packages\Microsoft.Web.Infrastructure.2.0.1\lib\net40\Microsoft.Web.Infrastructure.dll
+
+
+ packages\Unity.5.11.9\lib\net48\Unity.Abstractions.dll
+
+
+ packages\Unity.5.11.9\lib\net48\Unity.Container.dll
+
+
+ packages\Unity.WebAPI.5.4.0\lib\net45\Unity.WebApi.dll
+
+
+
+
+ packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll
+
+
+ packages\Microsoft.AspNet.WebApi.Client.5.2.9\lib\net45\System.Net.Http.Formatting.dll
+
+
+ packages\Microsoft.AspNet.WebApi.Core.5.2.9\lib\net45\System.Web.Http.dll
+
+
+ packages\Microsoft.AspNet.WebApi.WebHost.5.2.9\lib\net45\System.Web.Http.WebHost.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Default.aspx
+ ASPXCodeBehind
+
+
+ Default.aspx
+
+
+ Global.asax
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Web.config
+
+
+ Web.config
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 10.0
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+
+
+
+
+
+
+
+
+