Spreadsheet Document Signature

This demo illustrates how to use the Office File API to add a digital signature to an XLSX document. Specify signing information on the Options pane and click Sign and Download to save the signed document.

Options
Reason
Role
Comments
Country
State
City
Address 1
Address 2
Postal Code
Hash algorithm
TSA server
@model AspNetCoreDemos.OfficeFileAPI.SpreadsheetDocumentSignatureModel
@using DevExtreme.AspNet.Mvc

@{ Html.BeginForm("SpreadsheetDocumentSignature", "DocumentProtection", FormMethod.Post); }

<div class="demo-view-container">
    @await Html.PartialAsync("SpreadsheetPreviewPartial", Model.PreviewModel)
</div>

@{
    List<DevExpress.Office.DigitalSignatures.CommitmentType> reasons = new List<DevExpress.Office.DigitalSignatures.CommitmentType>();
    reasons.Add(DevExpress.Office.DigitalSignatures.CommitmentType.ProofOfApproval);
    reasons.Add(DevExpress.Office.DigitalSignatures.CommitmentType.ProofOfCreation);
    reasons.Add(DevExpress.Office.DigitalSignatures.CommitmentType.ProofOfOrigin);
}

<div class="options no-right-padding">
    @(Html.DevExtreme().ScrollView()
                                       .ID("scrollview")
                                       .ScrollByContent(true)
                                       .ScrollByThumb(true)
                                       .ShowScrollbar(ShowScrollbarMode.OnHover)
                                       .Direction(ScrollDirection.Both)
                                       .Height("424px")
                                       .Content(@<div id="scrollview-content" class="scrollable-content-right-padding scrollable-content-width">
        <div class="caption">Options</div>
        <div class="option-buttons">
            @(Html.DevExtreme().Button()
                .Text("Sign and Download")
                .Type(ButtonType.Default)
                .StylingMode(ButtonStylingMode.Contained)
                .UseSubmitBehavior(true)
                .OnClick("SignClick")
            )
        </div>
        <div class="option">
            <div class="label">Reason</div>
            @(Html.DevExtreme().SelectBoxFor(m => m.ReasonId)
            .DataSource(reasons.Select(i => new { Value = i.Id, Text = i.Description }))
            .ValueExpr("Value")
            .DisplayExpr("Text"))
        </div>
        <div class="option">
            <div class="label">Role</div>
            @(Html.DevExtreme().TextBoxFor(m => m.Role))
        </div>
        <div class="option">
            <div class="label">Comments</div>
            @(Html.DevExtreme().TextBoxFor(m => m.Comments))
        </div>
        <div class="option">
            <div class="label">Country</div>
            @(Html.DevExtreme().TextBoxFor(m => m.Country))
        </div>
        <div class="option">
            <div class="label">State</div>
            @(Html.DevExtreme().TextBoxFor(m => m.State))
        </div>
        <div class="option">
            <div class="label">City</div>
            @(Html.DevExtreme().TextBoxFor(m => m.City))
        </div>
        <div class="option">
            <div class="label">Address 1</div>
            @(Html.DevExtreme().TextBoxFor(m => m.Address1))
        </div>
        <div class="option">
            <div class="label">Address 2</div>
            @(Html.DevExtreme().TextBoxFor(m => m.Address2))
        </div>
        <div class="option">
            <div class="label">Postal Code</div>
            @(Html.DevExtreme().TextBoxFor(m => m.PostalCode))
        </div>
        <div class="option">
            <div class="label">Hash algorithm</div>
            @(Html.DevExtreme().SelectBoxFor(m => m.HashAlgorithm)
            .DataSource(Html.GetEnumSelectList<DevExpress.Office.DigitalSignatures.HashAlgorithmType>()
            .Select(i => new { Value = int.Parse(i.Value), Text = i.Text }))
            .ValueExpr("Value")
            .DisplayExpr("Text"))
        </div>
        <div class="option">
            <div class="label">TSA server</div>
            @(Html.DevExtreme().TextBoxFor(m => m.TSAServer)
            .Mode(TextBoxMode.Url)
            .ID("tsaServerTextBox")
            .ValidationStatus(ViewBag.ErrorMessage != null ? ValidationStatus.Invalid : ValidationStatus.Valid))
        </div>
        <div id="error" class="error-message scrollable-content-width">
            <span>@ViewBag.ErrorMessage</span>
        </div>
    </div>)
    )
</div>

<script type="text/javascript">
    function SignClick() {
        $("#tsaServerTextBox").dxTextBox("instance").option("validationStatus", 'valid');
        document.getElementById("error").hidden = true;
    }
</script>
@{ Html.EndForm(); }
@model AspNetCoreDemos.OfficeFileAPI.SpreadsheetPreviewModel

<iframe id="previewFrame" src="@Url.Action(Model.PreviewDocumentAction, Model.ControllerName)" class="demo-preview-border" style="width: 100%; height: @Model.HeightInPixels;box-sizing:border-box"></iframe>

<script type="text/javascript">
    SpreadsheetPreview = {
        basePath: '@Url.Action(Model.PreviewDocumentAction, Model.ControllerName)',
        Update: function (param) {
            var iframeElementName = "previewFrame";
            var iframeElement = document.getElementById(iframeElementName);
            if (!iframeElement)
                return;
            var additionalParams = "&" + new Date().valueOf();
            if (param)
                additionalParams = param;
            iframeElement.src = this.basePath + "?" + additionalParams;
        }
    };
</script>

using Microsoft.AspNetCore.Hosting;

namespace AspNetCoreDemos.OfficeFileAPI {
    public partial class DocumentProtectionController : OfficeDemoController {
        public DocumentProtectionController(IWebHostEnvironment hostingEnvironment)
            : base(hostingEnvironment) {
        }
    }
}
using System.IO;
using DevExpress.Spreadsheet;
using Microsoft.AspNetCore.Mvc;

namespace AspNetCoreDemos.OfficeFileAPI {
    public partial class DocumentProtectionController {
        public IActionResult SpreadsheetDocumentSignature() {
            SpreadsheetDocumentSignatureModel model = new SpreadsheetDocumentSignatureModel();
            return View(model);
        }   
        [HttpPost]
        public IActionResult SpreadsheetDocumentSignature(SpreadsheetDocumentSignatureModel model) {
            try {
                return CreateFileStreamResult(model.Sign(HostingEnvironment), "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "xlsx", "SignedDocument");
            } catch {
                ViewBag.ErrorMessage = "ERROR: TSA server URI is invalid or server doesn't support SHA-256 hashing algorithm";
                return View("SpreadsheetDocumentSignature", model);
            }
        }

        public IActionResult SpreadsheetDocumentSignaturePreview(SpreadsheetDocumentSignatureModel model) {
            SpreadsheetPreviewModel previewModel = model.PreviewModel;

            IWorkbook workbook = new Workbook();
            string documentsFolderPath = HostingEnvironment.ContentRootPath + "/Documents/";
            workbook.LoadDocument(documentsFolderPath + "ProfitAndLoss.xlsx");
            previewModel.Workbook = workbook;

            return GenerateHtmlPreview(previewModel);
        }
    }
}
using DevExpress.Office.DigitalSignatures;
using DevExpress.Office.Tsp;
using Microsoft.AspNetCore.Hosting;
using System;
using System.IO;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;

namespace AspNetCoreDemos.OfficeFileAPI {
    public class SpreadsheetDocumentSignatureModel : SpreadsheetModelBase {
        public string ReasonId { get; set; }
        public string Role { get; set; }
        public string Comments { get; set; }

        public string Country { get; set; }
        public string State { get; set; }
        public string City { get; set; }
        public string Address1 { get; set; }
        public string Address2 { get; set; }
        public string PostalCode { get; set; }

        public HashAlgorithmType HashAlgorithm { get; set; }
        public Uri TSAServer { get; set; }
        public string ErrorText { get; set; }

        public SpreadsheetDocumentSignatureModel() {
            PreviewModel.PreviewDocumentAction = "SpreadsheetDocumentSignaturePreview";

            ReasonId = CommitmentType.ProofOfApproval.Id;
            Role = "Sales Representative";
            Comments = "Demo Digital Signature";

            Country = "USA";
            State = "WA";
            City = "Seattle";
            Address1 = "507 - 20th Ave. E.";
            Address2 = "Apt. 2A";
            PostalCode = "98122";

            HashAlgorithm = HashAlgorithmType.SHA256;
            TSAServer = new Uri("https://freetsa.org/tsr");
        }
        public Stream Sign(IWebHostEnvironment hostingEnvironment) {
            string documentsFolderPath = hostingEnvironment.ContentRootPath + "/Documents/";
            using (Stream inputStream = new FileStream(documentsFolderPath + "ProfitAndLoss.xlsx", FileMode.Open, FileAccess.Read, FileShare.Read)) {
                Stream stream = new MemoryStream();
                DocumentSigner documentSigner = new DocumentSigner();
                documentSigner.Sign(inputStream, stream, CreateSignatureOptions(hostingEnvironment), CreateSignatureInfo());
                return stream;
            }
        }
        SignatureOptions CreateSignatureOptions(IWebHostEnvironment hostingEnvironment) {
            string documentsFolderPath = hostingEnvironment.ContentRootPath + "/Documents/";
            X509Certificate2 certificate = new X509Certificate2(documentsFolderPath + "Pdf/SignDemo.pfx", "dxdemo");
            SignatureOptions options = new SignatureOptions();
            options.Certificate = certificate;

            if(TSAServer != null)
                options.TsaClient = new TsaClient(TSAServer, HashAlgorithmType.SHA256);

            X509ChainPolicy policy = new X509ChainPolicy();
            policy.RevocationMode = X509RevocationMode.NoCheck;
            policy.RevocationFlag = X509RevocationFlag.ExcludeRoot;
            policy.VerificationFlags |= X509VerificationFlags.AllowUnknownCertificateAuthority | X509VerificationFlags.IgnoreCertificateAuthorityRevocationUnknown;
            options.CertificatePolicy = policy;
            options.TimestampCertificatePolicy = policy;
            options.CertificateKeyUsageFlags = X509KeyUsageFlags.None;
            options.DigestMethod = this.HashAlgorithm;
            return options;
        }
        SignatureInfo CreateSignatureInfo() {
            SignatureInfo signatureInfo = new SignatureInfo();
            signatureInfo.CommitmentType = GetCommitmentType(ReasonId);
            signatureInfo.Time = DateTime.UtcNow;
            signatureInfo.ClaimedRoles.Clear();
            signatureInfo.ClaimedRoles.Add(this.Role);
            signatureInfo.Country = this.Country;
            signatureInfo.City = this.City;
            signatureInfo.StateOrProvince = this.State;
            signatureInfo.Address1 = this.Address1;
            signatureInfo.Address2 = this.Address2;
            signatureInfo.PostalCode = this.PostalCode;
            signatureInfo.Comments = this.Comments;
            return signatureInfo;
        }
        CommitmentType GetCommitmentType(string id) {
            if (String.IsNullOrEmpty(id))
                return CommitmentType.ProofOfApproval;
            PropertyInfo[] properties = typeof(CommitmentType).GetProperties(BindingFlags.Static | BindingFlags.Public);
            if (properties == null || properties.Length <= 0)
                return CommitmentType.ProofOfApproval;

            foreach (PropertyInfo pi in properties) {
                CommitmentType commitmentType = pi.GetValue(null) as CommitmentType;
                if (commitmentType != null && commitmentType.Id == id)
                    return commitmentType;
            }

            return CommitmentType.ProofOfApproval;
        }
    }
}
namespace AspNetCoreDemos.OfficeFileAPI {
    public class SpreadsheetModelBase {
        public SpreadsheetModelBase() {
            PreviewModel = new SpreadsheetPreviewModel();
            PreviewModel.OwnerPropertyName = "PreviewModel";
            FileFormat = SpreadsheetFileFormat.Xlsx;
        }

        public SpreadsheetFileFormat FileFormat { get; set; }
        public SpreadsheetPreviewModel PreviewModel { get; internal set; }
    }
}