Monday, September 18, 2017

Asp.net : how to implement digital signature

In this article I am going to explain how to implement digital signature in asp.net application.

Description:
Recently I have got a requirement to implement digital signature functionality in application. To fulfill this requirement I have use Signature pad jquery. It is HTML5 canvas based.

Implementation:
You can download Signature pad script from here. Download Javascript and add reference to web form.

HTML Markup of webform

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Signature.aspx.cs" Inherits="Signature" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>digital signature</title>
 <link rel="stylesheet" href="css/signature-pad.css" />
</head>
<body>
    <form id="form1" runat="server" style="width:100%;height:100%">
        <asp:HiddenField ID="hfimg" runat="server" />
       <div id="signature-pad" class="signature-pad">
    <div class="signature-pad--body">
      <canvas></canvas>
    </div>
    <div class="signature-pad--footer">
      <div class="description">Sign above</div>

      <div class="signature-pad--actions">
        <div>
          <button type="button" class="button clear" data-action="clear">Clear</button>
        </div>
        <div>
           <asp:Button ID="Button1" runat="server" Text="Save signature" data-action="save" OnClick="Button1_Click" />           
        </div>
      </div>
    </div>
  </div>

  <script src="js/signature_pad.js"></script>
  <script src="js/app.js"></script>
    </form>   
</body>
</html>


You need to make some changes in app.js file.
Code of App.js file after changes

var wrapper = document.getElementById("signature-pad");
var clearButton = wrapper.querySelector("[data-action=clear]");
var canvas = wrapper.querySelector("canvas");
var signaturePad = new SignaturePad(canvas);
// save button
var saveButton = wrapper.querySelector("[data-action=save]");

// Adjust canvas coordinate space taking into account pixel ratio,
// to make it look crisp on mobile devices.
// This also causes canvas to be cleared.
function resizeCanvas() {
  // When zoomed out to less than 100%, for some very strange reason,
  // some browsers report devicePixelRatio as less than 1
  // and only part of the canvas is cleared then.
  var ratio =  Math.max(window.devicePixelRatio || 1, 1);

  // This part causes the canvas to be cleared
  canvas.width = canvas.offsetWidth * ratio;
  canvas.height = canvas.offsetHeight * ratio;
  canvas.getContext("2d").scale(ratio, ratio);

  // This library does not listen for canvas changes, so after the canvas is automatically
  // cleared by the browser, SignaturePad#isEmpty might still return false, even though the
  // canvas looks empty, because the internal data of this library wasn't cleared. To make sure
  // that the state of this library is consistent with visual state of the canvas, you
  // have to clear it manually.
  signaturePad.clear();
}

// On mobile devices it might make more sense to listen to orientation change,
// rather than window resize events.
window.onresize = resizeCanvas;
resizeCanvas();

function download(dataURL, filename) {
    var blob = dataURLToBlob(dataURL);
    var url = window.URL.createObjectURL(blob);

    var a = document.createElement("a");
    a.style = "display: none";
    a.href = url;
    a.download = filename;

    document.body.appendChild(a);
    a.click();

    window.URL.revokeObjectURL(url);
}

// One could simply use Canvas#toBlob method instead, but it's just to show
// that it can be done using result of SignaturePad#toDataURL.
function dataURLToBlob(dataURL) {
    // Code taken from https://github.com/ebidel/filer.js
    var parts = dataURL.split(';base64,');
    var contentType = parts[0].split(":")[1];
    var raw = window.atob(parts[1]);
    var rawLength = raw.length;
    var uInt8Array = new Uint8Array(rawLength);

    for (var i = 0; i < rawLength; ++i) {
        uInt8Array[i] = raw.charCodeAt(i);
    }

    return new Blob([uInt8Array], { type: contentType });
}

clearButton.addEventListener("click", function (event) {
  signaturePad.clear();
});

saveButton.addEventListener("click", function (event) {
    if (signaturePad.isEmpty()) {
        alert("Please provide signature first.");
    } else {
        document.getElementById("hfimg").value = signaturePad.toDataURL();
    }
});

Add namespace
C# Code
using System.Drawing.Imaging;
using System.IO;
  
VB.net Code
Imports System.Drawing.Imaging
Imports System.IO

Save image to folder 
On button click write the below given code:

C# Code
  protected void Button1_Click(object sender, EventArgs e)
    {
        string signature = hfimg.Value;
        string concat = signature.Replace("data:image/png;base64,", "");
        byte[] imageBytes = Convert.FromBase64String(concat);
        MemoryStream ms = new MemoryStream(imageBytes, 0,imageBytes.Length);
        // Convert byte[] to Image
        ms.Write(imageBytes, 0, imageBytes.Length);
        System.Drawing.Image image = System.Drawing.Image.FromStream(ms, true);
        //save image
        image.Save(Server.MapPath("~/ScreenShot/"+Guid.NewGuid()+".png"), ImageFormat.Png);
    }


VB.net Code
   Protected Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Dim signature As String = hfimg.Value
        Dim concat As String = signature.Replace("data:image/png;base64,", "")
        Dim imageBytes As Byte() = Convert.FromBase64String(concat)
        Dim ms As New MemoryStream(imageBytes, 0, imageBytes.Length)
        ' Convert byte[] to Image
        ms.Write(imageBytes, 0, imageBytes.Length)
        Dim image As System.Drawing.Image = System.Drawing.Image.FromStream(ms, True)
        'save image
        image.Save(Server.MapPath("~/ScreenShot/" + Guid.NewGuid.ToString() + ".png"), ImageFormat.Png)
    End Sub


11 comments:

  1. Hi there, thanks for the source code. However, I'm getting an exception at the following line:

    System.Drawing.Image image = System.Drawing.Image.FromStream(ms, true);

    The exception is System.ArgumentException: Parameter is not valid.

    Could you please help? Thanks!

    ReplyDelete
    Replies
    1. Code is tested and working fine.

      Delete
    2. Im having this problem too...
      it said "Parameter is not valid". Can you help us? Btw thanks for the code

      Delete
  2. System.Drawing.Image image = System.Drawing.Image.FromStream(ms, true);

    Parameter is not valid.

    Could you please help? Thanks!

    ReplyDelete
  3. funciona solo tuve que hacer ajustes en el codigo JS:
    saveButton.addEventListener("click", function (event) {
    if (signaturePad.isEmpty()) {
    alert("Please provide signature first.");
    } else {
    //document.getElementById("hfimg").value = signaturePad.toDataURL(); reemplace esto
    var base64 = signaturePad.toDataURL();
    $("[id$=TextBox1]").val(base64 ); //por esto, coloque un textbox para verlo

    }
    });

    el problema que no carga el hfimg(hidden) porque no lo encuentra de esa forma net.
    de la forma que lo hice funcionó.
    Saludos desde Argentina

    ReplyDelete
  4. funciona solo hay que retocar el Javascript en la parte de guardar como llamar el objeto que guardara el valor en base 64:
    document.getElementById("<%=hfimg.ClientID%>").value = signaturePad.toDataURL();//cambiar esto.

    var base64 = signaturePad.toDataURL();
    $("[id$=TextBox1]").val(base64); // por esto Textbox1 cambiarlo por el que ustedes usen.

    ReplyDelete
  5. can you update it in razor view

    ReplyDelete
  6. In hidden filed control set client id mode static.

    ReplyDelete
  7. Add client id mode to Static for hidden filed control.

    ReplyDelete
  8. unable to draw signature on canvas. Please help...

    ReplyDelete
  9. Any body please share the code or link of how we can add signatures in pdf using ASP .Net Core MVC.
    Thanks!

    ReplyDelete