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.
@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; }
}
}