teisipäev, 27. detsember 2022

OAuth2 tokeni lisamine WSDL veebipäringule

Alustuseks nii, et kui on tehtud WSDL-iga Dynamics veebiteenusest mingi valmis klass selle teenuse kasutamiseks, siis vaikimis on see klass ilma OAuth2 toeta.

OAuth2 jaoks vaja, et saaks päringule Bearer tokeni lisada

Asja päästab see, et need Dynamics klassid pärinevad SoapHttpClientProtocol, ehk seal tuleb GetWebRequest üle kirjutada, lisada juurde ka public string muutuja selle OAuth2 tokeni lisamiseks

Ehk seda WSDL-iga genereeritud klassi tuleb natuke muuta lisa

public string token = "";  //siin hakkad OAuth2 tokenit hoidma

ja kirjuta GetWebRequest üle

protected override WebRequest GetWebRequest(Uri uri)
        {

            WebRequest req = base.GetWebRequest(uri);
            req.Headers.Add("Authorization", "Bearer " + this.token);
            return req;
        }

Kasutamiseks tuleb lihtsalt enne päringu tegemist token klassile sisse anda

TYK_CustomerLedgerEntries_Service klass on WSDL-iga genereeritud, mida just muutsime

Lood teenuse klassi

TYK_CustomerLedgerEntries_Service tes = new TYK_CustomerLedgerEntries_Service();

ja omistad tokeni

tes.token = getAccessToken();

ja siis teed päringu

 

OAuth2 tokeni saamiseks on mitu võimalust.Kui on kasutada clientId, TenantId ja ClientSecret saab seda teha nii

        private static string getAccessToken()
        {
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
            TokeniKysimine lr = new TokeniKysimine();

            Task<AuthenticationResult> resultThing = Task.Run<AuthenticationResult>(async () => await new TokeniKysimine().KysiTokenit());

            resultThing.Wait();
            AuthenticationResult result = resultThing.Result;
            return resultThing.Result.AccessToken;
        }

 

Klass, mis tokenit küsib

 

    public class TokeniKysimine
    {
        public async Task<AuthenticationResult> KysiTokenit()
        {

            string tenantId = System.Configuration.ConfigurationManager.AppSettings["DynamicsTenantId"];
            string clientId = System.Configuration.ConfigurationManager.AppSettings["DynamicsClientId"];
            string clientSecret = System.Configuration.ConfigurationManager.AppSettings["DynamicsClientSecret"];

            string[] scopes = new string[] { "https://api.businesscentral.dynamics.com/.default" };

            IConfidentialClientApplication app = ConfidentialClientApplicationBuilder.Create(clientId)            .WithClientSecret(clientSecret) 
           .WithAuthority(new Uri(@"https://login.microsoftonline.com/" + tenantId + @"/v2.0/oauth2/token"))
           .Build();
            AuthenticationResult result = null;
            try
            {
                result = await app.AcquireTokenForClient(scopes)
                                 .ExecuteAsync();
            }
            catch (MsalUiRequiredException ex)
            {
                Console.WriteLine(ex.Message);
                throw ex;
            }
            catch (MsalServiceException ex) //when (ex.Message.Contains("AADSTS70011"))
            {
                Console.WriteLine(ex.Message);
                throw ex;
            }
            return result;
        }
    }

 

 



esmaspäev, 1. juuli 2019

Java streams SQL laadne GROUP BY

Kui vaja teha Java mingi class listidega SQL laadne GROUP BY arvutus

InventoryPositionsViewRate on algne klass mis on vaja summeerida tInventorySummaryViewRate listiks

private void calculateSummary(final List<InventoryPositionsViewRate> ipwr) {
        // Calculates sum GROUP BY branch, currency
        this.listInventorySummaryViewRate = ipwr.parallelStream()
                .collect(Collectors.groupingBy(Function.identity(),
                        () -> new TreeMap<>(Comparator.<InventoryPositionsViewRate, String> comparing(InventoryPositionsViewRate::getBranchDisplayName)
                                .thenComparing(InventoryPositionsViewRate::getCurrencyCode)
                                .thenComparing(InventoryPositionsViewRate::getRate)), //getRate is nessesary for localAmount calculating
                        Collectors.summingDouble(foo -> foo.getAmount().doubleValue())))
                .entrySet().stream()
                .map(iswr -> new InventoryPositionsSummaryRate(iswr.getKey().getBranchDisplayName(),
                        iswr.getKey().getCurrencyCode(), BigDecimal.valueOf(iswr.getValue()), iswr.getKey().getRate()))
                .collect(Collectors.toList());
    }

esmaspäev, 25. märts 2019

Viga 403.16 Forbidden ID-kaardiga autentimisel

Kui kasutatakse rakenduses ID-kaardiga autentmist, ehk ASP.NET koodis on

HttpClientCertificate sert = Request.ClientCertificate;
if (!sert.IsPresent)
...

ning oma arust on veebilehe autentimine õigesti seadistatud (IIS, WebSite - SSL Settings - Client Certificates (Accept)

võib ikkagi juhtuda, et tuleb sirviku ekraanile viga 403 Forbidden

Et asjale pihta saada, säti veebelehel peale Failed Request Tracing



seadistada sobivad märkeruudud ja jätta meelde kataloog, kuhu logifailid tekitatakse (XML fail on veateade ise ja XSL on selleks, et Internet Exploreriga viga vaadata)

Kui veateade sisaldab endast 403.16 ja kirjeldust A certificate chain processed, but terminated in a root certificate which is not trusted by the trust provider. (0x800b0109)



siis on midagi valesti Windows Serveri sertifikaatides, mis alates Windows Server 2012 nõutakse, et juurhoidlas oleks ainult self-signed sertifikaadid. Kui seal on mõni mittesobiv, siis tulemuseks ongi 403 Forbidden

PowerShelli käsuga saab need sertifikaadid ülesse leida
Get-Childitem cert:\LocalMachine\root -Recurse |   Where-Object {$_.Issuer -ne $_.Subject}


edasi siis juba vaadata, mida nende sertifikaatidega teha, kas kustutada või liigutada vahepealsesse sertifikaadi hoidlasse



Get-Childitem cert:\LocalMachine\root -Recurse |  Where-Object {$_.Issuer -ne $_.Subject} | 
    Move-Item -Destination Cert:\LocalMachine\CA








reede, 15. veebruar 2019

Sertifikaatide tegemine Telia XTee keskkonnaga liitumiseks

XTeele andmeid edastava serveri ühendamiseks Telia turvaserveriga on vaja genereerida sertifikaadid ja parooliga võti, seda saab teha OPENSSL utiliidi kaudu

Windows keskkonnas näiteks https://slproweb.com/products/Win32OpenSSL.html

Käsud on järgmised

Sertifikaadi genereerimine

openssl req -new -newkey rsa:4096 -x509 -sha256 -days 1826 -nodes -out TartuYliopilaskyla.crt -keyout TartuYliopilaskyla.key

-days 1826  määrab kaua päev sertifikaat kehtib
Common Name pane rakenduse URL aadress

KAMPUS rakenduse jaoks Organization Unit Name väärtuse sisse kirjuta kindlasti parameetri XTEE_CLIENTMEMBERCODE - väärtus (enamasti on see 12678538 ettevõtte äriregistri number )


TartuYliopilaskyla.crt on siis serveri sertifikaat, mis tuleb saata Teliasse ja paigaldada ka Windows Serveri Trusted Root sertifikaadihoidlasse

Privaatvõtme tegemine, parool jäta meelde, seda läheb importmisel vaja. Kui pärast sertifikaadi importimisel Personal sertifikaadi hoidlasse pistab kisama, et salakood on vale , siis tee nii, et impordi ta koos eksportimisvõimekusega selle arvuti Personal sertifkaadihoidlasse, kus privaatvõti genereeriti ja see järel ekspordi see ning proovi seda eksporditud sertifikaati sisse importida.

openssl pkcs12 -export -out personalprivat.pfx -inkey TartuYliopilaskyla.key -in TartuYliopilaskyla.crt



personalprivat.pfx on nüüd parooliga sertifikaat mis paigaldada Windows Serveri Personal sertifikaadihoidlasse. Paigaldamisel küsitakse parooli, mis genereerimisel sai sisestatud.



Kui kõik sobib siis Personal sertifikaadihoidlas olev Certification Path peab olema vigadeta



Selleks, et C# meetodid saaksid seda sertifikaati kasutada tuleb rakenduste kontodele anda ka ligipääsu õigused.

KAMPUS rakendus loeb kokku kõik Personal sertifkaadihoidlas olevad sertifikaadid, kuid näitab ainult neid mille Organization Unit Name väärtuse sisse on kirjutatud parameetri XTEE_CLIENTMEMBERCODE väärtus




Õiguste andmine rakenduste kontodele



teisipäev, 12. veebruar 2019

Lehekülgede pagineerimine ORACLE-s ühe SELECT lause kasutamisega ainult


Eriti REST rakenduste puhul tuleb sageli pagineerimine teha andmebaasi poolel.

Läbi tuleb teha selleks kaks etappi
1. Saada teade kirjete koguarv
2. Võtta vajalik vahemik välja

ORACLE WITH konstruktsiooni tehes saab selle tehe ühe SELECT lausega ainult, mis juhtudel, kui SELECT lause ise võtab kaua aega annab märgatava ajalise kokkuhoiu

Näide, kus annad ette lehekülje suuruse ja mitmendat lehekülge tahad

SET SERVEROUTPUT ON
DECLARE i_page_size INTEGER;  --lehekülje suurus
i_page_no INTEGER; --mitme lehekülg kuvada
BEGIN
i_page_size:=5;
i_page_no:=2;
FOR ss IN (
 WITH Tu AS
        (  SELECT ROWNUM AS jrk, dde.ID, dde.SUBJECT_ID, dde.OCCUP_NAME
                , CEIL((ROWNUM)/i_page_size) AS PAGE_NUMBER --arvutab lehekülje numbri
            FROM
            (  SELECT ID, SUBJECT_ID, OCCUP_NAME FROM SUB_EMPLOYER ORDER BY OCCUP_NAME DESC ) dde
     )       
     SELECT Tu.jrk, Tu.ID, Tu.SUBJECT_ID, Tu.OCCUP_NAME,Tu.PAGE_NUMBER
       ,(SELECT MAX(Tu.jrk) FROM Tu ) AS kokku_kirjeid
       FROM Tu
         WHERE Tu.PAGE_NUMBER = i_page_no  --pagineerimine
         ORDER BY Tu.jrk)
LOOP
    DBMS_OUTPUT.PUT_LINE ( SS.JRK || ' ' || ss.OCCUP_NAME || ' ' || ss.SUBJECT_ID  || ' ' || ss.kokku_kirjeid);
END LOOP;
END;

WITH konstruktsiooni osa, saab kätte kirje järjekorra numbri ja CEIL funktsiooniga arvutab lehekülje numbri

ROWNUM AS jrk, CEIL((ROWNUM)/i_page_size) AS PAGE_NUMBER
      
 hilisemas välises päringus saab eraldi tulbast kätte ka kirjete koguarvu
(SELECT MAX(Tu.jrk) FROM Tu ) AS kokku_kirjeid



Mõnikord vaja saada lisaks eraldi teada ka kirjete ja lehekülgede koguarv siis kasutab CURSOR-i kuhu lisab ette esimeseks lehekülgede ja ridade koguarvuga rea ning FETCH-ib selle välja enne tagastamist. Esimese rea väljatõmbamisel saad lehekülgede ja kirjete koguarvu teada
o_totalPages:=nvl(ceil(o_totalRows/i_page_size),0); --arvutame lehekülgede arvu
o_totalRows:=nvl(o_totalRows,0); --nvl et saaks ridade puudumisel number 0-i välja



SET SERVEROUTPUT ON
DECLARE i_page_size INTEGER;  --lehekülje suurus
i_page_no INTEGER; --mitmes lehekülg kuvada
o_cursor sys_refcursor;  --siia paneme tulemused
o_totalPages INTEGER; --mitu lehekülge kokku
o_totalRows INTEGER; --mitu rida kokku

v_int integer; --abimuutuja
v_int2 integer; --abimuutuja
v_lehekulg integer; --abimuutuja
v_subject_id varchar2(8000); --abimuutuja
v_occup_name varchar2(8000); --abimuutuja
BEGIN
i_page_size:=5;
i_page_no:=2;

open o_cursor for
 WITH Tu AS
        ( 
        SELECT dde2.jrk, dde2.ID, dde2.SUBJECT_ID, dde2.OCCUP_NAME, dde2.PAGE_NUMBER
        FROM (
        SELECT ROWNUM AS jrk, dde.ID, dde.SUBJECT_ID, dde.OCCUP_NAME
                , CEIL((ROWNUM)/i_page_size) AS PAGE_NUMBER --arvutab lehekülje numbri
            FROM
            (  SELECT ID, SUBJECT_ID, OCCUP_NAME FROM SUB_EMPLOYER ORDER BY OCCUP_NAME DESC ) dde ) dde2
     )       
     SELECT Tu.jrk, Tu.ID, Tu.SUBJECT_ID, Tu.OCCUP_NAME,Tu.PAGE_NUMBER
       ,(SELECT MAX(Tu.jrk) FROM Tu ) AS kokku_kirjeid
       FROM Tu
         WHERE Tu.PAGE_NUMBER = i_page_no  --pagineerimine
       
    UNION --tekitame juurde esimese rea, sellekaudu saab fetch ridade koguarvu teada väljastuseks
        SELECT 0 as jrk, Tu.ID, Tu.SUBJECT_ID, Tu.OCCUP_NAME,Tu.PAGE_NUMBER
       ,(SELECT MAX(Tu.jrk) FROM Tu ) AS kokku_kirjeid FROM Tu WHERE Tu.jrk =1
         ORDER BY 1;  --et 0 as jrk rida esimeseks tuleks, mille FETCH-iga välja võtame
        
    --Esimese rea kursorist võib ära raisata, küsib sealt lehekülgede koguarvu ja kirjete koguarvu
     FETCH o_cursor INTO v_int, v_int2, v_subject_id, v_occup_name, v_lehekulg,o_totalRows;
             o_totalPages:=nvl(ceil(o_totalRows/i_page_size),0); --arvutame lehekülgede arvu
             o_totalRows:=nvl(o_totalRows,0); --nvl et saaks ridade puudumisel number 0-i välja
     IF o_totalRows=0 THEN   --anname tühja kursori välja, kui kirjeid üldse pole, tuleb viga ORA-01002: fetch out of sequence
        CLOSE o_cursor;
        OPEN o_cursor FOR
         SELECT 0 AS jrk , 0 AS ID, '' AS SUBJECT_ID, '' AS OCCUP_NAME, 0 AS PAGE_NUMBER, 0 AS kokku_kirjeid FROM dual;
     END IF;
     DBMS_OUTPUT.PUT_LINE ( 'Kokku kirjeid: ' || o_totalRows  || '   Kokku lehekülgi: ' || o_totalPages);
    
    
     LOOP
          FETCH o_cursor INTO  v_int, v_int2, v_subject_id, v_occup_name, v_lehekulg,o_totalRows;
             EXIT WHEN o_cursor%NOTFOUND;
          DBMS_OUTPUT.PUT_LINE ('Jrk: ' ||  v_int || ' ' || v_occup_name || ' ' || v_subject_id);
     END LOOP;
    
END;

Konstruktsioon 
.. UNION .. WHERE Tu.jrk =1 .. ORDER BY 1  teeb selle, et "tühi rida" mille võib ära visata tekib CURSOR-isse esimeseks, kust loeb välja kirjete ja lehekülgede koguarvu.

Kui kirjeid pole IF o_totalRows=0 THEN siis tekitab tühja üherealise CURSOR-i REST kliendi jaoks juhuks kui REST kliendile ei meeldi, et andmebaasist mitte kui midagi ei tagastata.

esmaspäev, 11. veebruar 2019

Validate of viewstate MAC failed viga IIS HTTPS ühenduse korral

Kui oled IISi peale paigaldanud veebirakenduse ja seadistanud ta ka HTTPS protokolli kasutama siis võib käivitamisel ilmuda viga koos soovitustes antud lingiga


Üks asi mida veel vaadata on web.config failis httpCookies seadistus, millele saab lisada turvalisust tõstvaid atribuute requireSSL="true" ja domain="www.veebileht.ee".
Jälgida tuleb, et domain parameeter langeks kokku veebilehe URL aadressiga. Selle kaudu kontrollitakse, et ViewState tuleb õigelt veebilahelt

pühapäev, 10. veebruar 2019

ASP.NET digiallkirjastamine IIS veebiserveris

Mõnikord siis vaja ka ASP.NET veebirakendusse digiallkirjastamine lisada, üks võimalus siis nii, et teed digidoc4j põhjal Java Spring Boot rakenduse mille käivitad Windows Serveris kui Wrapper teenuse.

Sellest juhendist Windows Service Wrapper täitsa töötab
https://www.baeldung.com/spring-boot-app-as-a-service

Idee siis selline, et näidisrakendust https://github.com/rvillido/digidoc4j-hwcrypto-demo
on muudetud nii, et Java osas toimub signeerimine ja valideerimine aga andmete salvestamine on Java Spring Boot rakendusest välja toodud ja tehakse ASP.NET ja MS SQL SERVER-i sees
https://github.com/open-eid/digidoc4j/wiki/Examples-of-using-it

Java Spring Boot rakendus asub siin, kõigepealt tuleb serverisse Java 1.8 paigaldada
https://github.com/kuidokylm/KampusBdoc

Kui saad selle Windows Service Wrapperi abil paigaldatud siis ava Windows Serveris sirvik ja vaata, mis seal http://localhost:8083 vastu vaatab, peaks tulema selline pilt


Java Spring Boot töötab GET ja POST päringute töötlemisega URL-i pihta, ehk serveri peal
sirviku http://localhost:8083/port päring peaks andma sellise vastuse


Kõik test URL-id leiab  Java @RestController public class TestController-ist


Digiallkirjastamise jaoks tuleb sõlmida SK ID Solutions leping Ajatempliteenuse kehtivuskinnituse kasutamiseks, kui see on tehtud ja IP aadress avatud siis serveri peal http://localhost:8083/refreshtsl


peab tagastama ok, ehk tehakse TSL(trusted sources list) nimekirja värskendus. Edasi on juba ASP.NET koodi osa, ehk lisad projekti ASMX veebiteenuse, näites on nimi bdoc.asmx

ja tema code-behind koodi päisesse

[System.Web.Script.Services.ScriptService]
public class bdoc : System.Web.Services.WebService

ehk lubad JavaScriptil/jQueryl ASP.NET veebiteenusega suhelda.

ASMX veebiteenuse meetod Port, mis tootab nii, et ASP.NET koodis veebilehel pöördutakse jQuery kaudu ASMX meetodi Port poole, mis teeb päringu Java Spring Boot Service Wrapperisse http://localhost:8083/port, saab sealt vastuse ja tagastab tagasi veebilehele

[WebMethod]
[ScriptMethod(UseHttpGet = true)]
    public void Port()
    {
        string responseFromServer = "";
        string url = "http://localhost:8083/port";     
        try
        {
            WebRequest request = WebRequest.Create(url);
            ((HttpWebRequest)request).UserAgent = "Kampus Web";
            WebResponse response = request.GetResponse();
            Stream dataStream = response.GetResponseStream();
            StreamReader reader = new StreamReader(dataStream);
            responseFromServer = reader.ReadToEnd();

            reader.Close();
            dataStream.Close();
            response.Close();

        }
        catch (WebException ex)
        {
            responseFromServer = ex.Message;
            if (ex.InnerException != null)
            {
                responseFromServer += (" " + ex.InnerException.Message);
            }
        }
        catch (SystemException ex)
        {
            responseFromServer = ex.Message;
        }
        Digest vastus = new Digest();
        vastus.result = responseFromServer;
        vastus.hex = responseFromServer;
        responseFromServer = Newtonsoft.Json.JsonConvert.SerializeObject(vastus);
        Context.Response.Clear();
        Context.Response.ContentType = "application/json;charset=UTF-8";  //see peab olema, muidu ei hakka jquery front-endis tööle, vajab JSON formaati
        Context.Response.AddHeader("content-length", responseFromServer.Length.ToString());
        Context.Response.Flush();
        Context.Response.Output.Write(responseFromServer);
    }

jQuery väljakutse ASPX lehe peal olevast HTML ButtonPort input nupust

input type="button" id="ButtonPort" class="actionButton" value="Kontrolli ASMX BDOC"

Päring töötab nii, ehk

1. Sirvikust jQuery pöördumine ASP.NET /bdoc.asmx/Port meetodisse
2. ASMX meetod Port teeb serveris päringu Java Spring Boot http://localhost:8083/port aadressil
3. Saadud vastuse tagastab ASMX meetod Port sirviku jQueryle


$("#ButtonPort").on("click", function () {
 console.log("Port ");                 
 $.get("/bdoc.asmx/Port", function (data, status) {
 console.log("Port " + data.hex);
 $("#<%=LabelBdoc.ClientID %>").text(status);
 alert("Data: " + data.hex + "\nStatus: " + status);
});
});



Sama Java Spring Boot http://localhost:8083/port väljakutse serveri poolel, ehk ASP.NET code-behind nupu Click meetod kus serveri back-end pöördub otse Java Spring Boot Windows Service wrapperi poole

protected void ButtonBDOC_Click(object sender, EventArgs e)
    {
        string url = "http://localhost:8083/port";
        this.LabelBdoc.Visible = true;
        //https://docs.microsoft.com/en-us/dotnet/framework/network-programming/how-to-request-data-using-the-webrequest-class
        try
        {
            WebRequest request = WebRequest.Create(url);
            ((HttpWebRequest)request).UserAgent = "Kampus WebAdmin ButtonBDOC_Click";
            WebResponse response = request.GetResponse();
            Stream dataStream = response.GetResponseStream();
            StreamReader reader = new StreamReader(dataStream);
            string responseFromServer = reader.ReadToEnd();
            this.LabelBdoc.Text = responseFromServer;
            reader.Close();
            dataStream.Close();
            response.Close();

        }
        catch (WebException ex)
        {
            this.CustomValidator1.ErrorMessage = ex.Message;
            this.CustomValidator1.IsValid = false;
            if (ex.InnerException != null)
            {
                this.LabelBdoc.Text = url + " " + ex.InnerException.Message;
            }
        }
        catch (SystemException ex)
        {
            this.CustomValidator1.ErrorMessage = url + " " + ex.Message;
            this.CustomValidator1.IsValid = false;
        }
    }


Kaks võimalust, kuidas Java Spring Boot Windows Service wrapperi kaudu suhelda, kas front-endis kus pöördutakse läbi ASMX veebimeetodi või server-side kus minnakse otse.

Kui on vaja ASP.NET koodist ASMX veebimeetodile Session -i kaudu väärtust edasi anda
siis ASMX meetodi päisesse lisad [WebMethod(EnableSession = true)]

[WebMethod(EnableSession = true)]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public void generateFileHash(string certInHex)
{
int failiId = Convert.ToInt32(HttpContext.Current.Session["bdocid"].ToString());
}

nii saad ASP.NET code-behind seatud Session muutuja väärtuse kätte ASMX veebimeetodis

Kui näiteks on GridView nimekiri failidest mida vaja digiallkirjastada




asp:Button ID="BtnSigneeri"  Text="Digiallkirjasta leping" CausesValidation="false" CommandName="Signeeri" CommandArgument='<%# Eval("ID") %>' runat="server"


protected void GridViewFailid_RowCommand(object sender, GridViewCommandEventArgs e)
    {
        if (e.CommandName.Equals("Signeeri") )
        {
            this.Session["bdocid"] = e.CommandArgument.ToString();  //faili ID
            Button butt = (Button)e.CommandSource;
            if (!this.Page.ClientScript.IsClientScriptBlockRegistered("signeeriDokument()"))
            {
                //kuna oleme updatepaneli sees, tuleb seda ScriptManager.RegisterStartupScript meetodit kasutada
                //kutsume välja selle nupuga seotud javascripti
                ScriptManager.RegisterStartupScript(butt, typeof(Button), butt.ClientID, "signeeriDokument()", true);         
            }
        }

Ülal olevas koodis on ka näidatud, kuidas saab serveri poolt code-behind välja kutsuda JavaScripti meetodit
ScriptManager.RegisterStartupScript(butt, typeof(Button), butt.ClientID, "signeeriDokument()", true);

javascripti meetod oma korda kutsub välja jQuery funktsioonide promised

function signeeriDokument() {
    console.log("signeeriDokument klikiti");
    signfile();
}

signfile() on jQuery funktsioonide komplekt promiseid, kus post funktsiooni sees url: "/bdoc.asmx/" + url määratakse ära väljakutsutav ASMX veebiteenuse meetod

$(function () {

    post = function (url, data) {
        console.log("post query:" + url);
        return new Promise(function (resolve, reject) {
            $.ajax({
                url: "/bdoc.asmx/" + url,
                type: "POST",
                data: data
            }).done(function (data) {
                console.log("post done data.result " + data.result);
                if (data.result !== "ok") {
                    $("#LabelBdoc").text(data.result);
                    reject(Error(data.result))
                } else {
                    resolve(data);
                }
            }).fail(function () {
                reject(Error("Post operation failed"));
            });
        });
    };


    //olemasolevalt konteinerilt räsi küsimine
    getContainerToSign = function (certInHex) {
        console.log("getContainerToSign " + certInHex);
        return post("getContainerToSign", { certInHex: certInHex })
    };

    fetchFileHash = function (certInHex) {
        console.log("generateFileHash ");
        return post("generateFileHash", { certInHex: certInHex })
    };

    createContainer = function (signatureInHex) {
        console.log("createContainer " + signatureInHex);
        return post("createContainer", { signatureInHex: signatureInHex });
    };

    addSignToContainer = function (signatureInHex) {
        console.log("addSignToContainer " + signatureInHex);
        return post("addSignToContainer", { signatureInHex: signatureInHex });
    };


    signfile = function () {
        var cert;
        naitanupp("LabelBdoc");
        $("#LabelBdoc").text("Loen ID-kaardilt sertifikaati");
        console.log("enne signfile window.hwcrypto.getCertificate");
        window.hwcrypto.getCertificate({ lang: 'en' }).then(function (certificate) {
            cert = certificate;
            $("#LabelBdoc").text("Koostan räsi");
            console.log("getCertificate päring ");
            return fetchFileHash(certificate.hex);
        }).then(function (digest) {              
            console.log("digest " + digest.hex);
            $("#LabelBdoc").text("Signeerin");
            return window.hwcrypto.sign(cert, { type: 'SHA-256', hex: digest.hex }, { lang: 'en' });
        }).then(function (signature) {
            console.log("createcontainer");
            $("#LabelBdoc").text("Loon konteinerit");
            return createContainer(signature.hex);
        }).then(function (result) {
            console.log("container is ready for download " + result.result);                         
            $("#LabelBdoc").text(result.result);              
            window.location = window.location;
        }).catch(err => {
            $("#LabelBdoc").text(err);              
        });
    };
});

ASP.NET Labelil nimega LabelBdoc peab ClientIDMode="Static" olema määratud, et tema ID ei muutuks kompileerimise käigus jQuery kirjutab sinna sisse infot protsessi edenemise kohta $("#LabelBdoc").text("Signeerin");

asp:Label ID="LabelBdoc" CssClass="globalNote" ClientIDMode="Static" runat="server"

Kogu protsessi juhib jQuery signfile funktsioon, küsib ID kaardilt sertifikaadi, käib ASMX veebimeetoditelt küsimas faili räsi, signeerib selle ja lõpuks saadab päringuga andmed konteineri tegemiseks.

ASMX meetodid mis käivad JavaSpring Boot Windows Service wrapperisse andmeid saatmas ja salvestavad vastuvõetu SQL SERVER-i tabelitesse


[WebMethod(EnableSession = true)]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
    public void generateFileHash(string certInHex)
    {
        Digest vastus = new Digest();
        byte[] byteArray = null;
        string fileName = "";
        int failiId =  Convert.ToInt32(HttpContext.Current.Session["bdocid"].ToString());     
        string contype = "";
        try
        {
            String connection = Configuration.ConnectionString;
            using (SqlConnection konn = new SqlConnection(connection))
            {
                SqlCommand komm = new SqlCommand("[dbo].[CONTRACT_FILE_KUIDO_S]", konn);
                komm.CommandType = CommandType.StoredProcedure;
                komm.Parameters.AddWithValue("@fileid", failiId);
                konn.Open();
                SqlDataReader red = komm.ExecuteReader();
                while (red.Read())
                {
                    fileName = red["FILE_NAME"].ToString();
                    contype = red["ADVERTISING_FILE_CONTENT_TYPE"].ToString();
                    byteArray = (byte[])red["FILE_CONTENT"];
                    break;
                }
                red.Close();
                konn.Close();
            }
            string requestURL = "http://localhost:8083/uploadforhash";
            WebClient wc = new WebClient();
            Dictionary<string, object> postParameters = new Dictionary<string, object>();
            // Add your parameters here
            postParameters.Add("file", new BdocUpload.FileParameter(byteArray, Path.GetFileName(fileName), contype));
            postParameters.Add("certInHex", certInHex);
            string userAgent = "KAMPUS generateFileHash";
            HttpWebResponse webResponse = BdocUpload.MultipartFormPost(requestURL, userAgent, postParameters, null, null);
            // Process response
            StreamReader responseReader = new StreamReader(webResponse.GetResponseStream());
            string responseFromServer = responseReader.ReadToEnd();
            webResponse.Close();
            DigestObject djson = JsonConvert.DeserializeObject<DigestObject>(responseFromServer);
            vastus.hex = djson.hex;
            vastus.result = djson.result;
            using (SqlConnection konn = new SqlConnection(connection)) //salvestame Conteineri sisu ajutiselt ära
            {
                SqlCommand komm = new SqlCommand("DBO.CONTRACT_FILE_BDOC_TEMP_U", konn);
                komm.CommandType = CommandType.StoredProcedure;
                komm.Parameters.AddWithValue("@fileid", Convert.ToInt32(this.Session["bdocid"].ToString()));
                komm.Parameters.AddWithValue("@tempdatatosign", djson.datatosign);
                komm.Parameters.AddWithValue("@tempcontainersisu", djson.container);
                konn.Open();
                komm.ExecuteNonQuery();
                konn.Close();
            }
        }
        catch (System.Web.HttpException ex)
        {
            vastus.result = ex.Message;
        }
        catch (SystemException ex)
        {
            vastus.result = ex.Message;
        }
        string jsonString = Newtonsoft.Json.JsonConvert.SerializeObject(vastus);  //teeme vastuse json stringiks

        Context.Response.Clear();
        Context.Response.ContentType = "application/json;charset=UTF-8";  //see peab olema, muidu ei hakka jquery front-endis tööle, vajab JSON formaati
        Context.Response.AddHeader("content-length", jsonString.Length.ToString());
        Context.Response.Flush();
        Context.Response.Output.Write(jsonString);
    }

    [WebMethod(EnableSession = true)]
    [ScriptMethod(ResponseFormat = ResponseFormat.Json)]
    public void createContainer(string signatureInHex)
    {
        int failiId = Convert.ToInt32(HttpContext.Current.Session["bdocid"].ToString());
     
        byte[] datatosign = null;
        byte[] container = null;
        String connection = Configuration.ConnectionString;
        using (SqlConnection konn = new SqlConnection(connection))
        {
            SqlCommand komm = new SqlCommand("DBO.CONTRACT_FILE_KUIDO_S", konn);
            komm.CommandType = CommandType.StoredProcedure;
            komm.Parameters.AddWithValue("@fileid", failiId);
            konn.Open();
            SqlDataReader red = komm.ExecuteReader();
            while (red.Read())
            {
                container = (byte[])red["TEMPCONTAINERSISU"];
                datatosign = (byte[])red["TEMPDATATOSIGN"];
                break;
            }
            red.Close();
            konn.Close();
        }
        Digest vastus = new Digest();
        string requestURL = "http://localhost:8083/createContainer";
        WebClient wc = new WebClient();
        Dictionary<string, object> postParameters = new Dictionary<string, object>();
        // Add your parameters here  "tempcontainer", "bytearray" on koha täiteks
        postParameters.Add("file", new BdocUpload.FileParameter(container, "tempcontainer", "bytearray"));
        postParameters.Add("dfile", new BdocUpload.FileParameter(datatosign, "tempdatatosign", "bytearray"));
        postParameters.Add("signatureInHex", signatureInHex);
        string userAgent = "KAMPUS createContainer";
        HttpWebResponse webResponse = BdocUpload.MultipartFormPost(requestURL, userAgent, postParameters, null, null);
        // Process response
        StreamReader responseReader = new StreamReader(webResponse.GetResponseStream());
        string responseFromServer = responseReader.ReadToEnd();
        webResponse.Close();
        DigestObject djson = JsonConvert.DeserializeObject<DigestObject>(responseFromServer);
        vastus.result = djson.result;
        try
        {
            using (SqlConnection konn = new SqlConnection(connection)) //salvestame BDOC Conteineri sisu
            {
                SqlCommand komm = new SqlCommand("DBO.CONTRACT_FILE_BDOC_U", konn);
                komm.CommandType = CommandType.StoredProcedure;
                komm.Parameters.AddWithValue("@fileid", Convert.ToInt32(failiId));
                komm.Parameters.AddWithValue("@bdocfailisisutyyp", djson.hex);
                komm.Parameters.AddWithValue("@bdocfailisisu", djson.container);
                //komm.Parameters.AddWithValue("@tempcontainersisu", djson.datatosign);
                konn.Open();
                komm.ExecuteNonQuery();
                konn.Close();
            }
        }
        catch (SystemException ex)
        {
            vastus.result = ex.Message;
        }
        string jsonString = Newtonsoft.Json.JsonConvert.SerializeObject(vastus);  //teeme vastuse json stringiks
        Context.Response.Clear();
        Context.Response.ContentType = "application/json;charset=UTF-8";  //see peab olema, muidu ei hakka jquery front-endis tööle, vajab JSON formaati
        Context.Response.AddHeader("content-length", jsonString.Length.ToString());
        Context.Response.Flush();
        Context.Response.Output.Write(jsonString);
    }

Java Spring Boot @RestController public class SigningController teeb ära digiallkirjastamise osad.


ASMX saadab faile Java Spring Boot Service Wrapperile multipart form data-na
https://www.c-sharpcorner.com/article/upload-any-file-using-http-post-multipart-form-data/

Selleks, et sirvikus jQuery saaks üldse teha päringuid ASMX veebimeetodite vastu tuleb web.config failis lubada vastavad veebiteenuste protokollid.



JSON andmevahtuseks ASMX ja Java Spring Booti vahel tuleb Java Spring Boot klassid kloonida ASP.NET rakendusse, Newtonsoft.Json package kasutamiseks

public class Signatuur
{
    public Signatuur()
    {
    }
    public string SubjectName { get; set; }
    public DateTime ClaimedSigningTime { get; set; }
    public String issuerName { get; set; }

    public String errors { get; set; }
}

public class Signatuurid
{
    public Signatuurid()
    {
    }
    public string result { get; set; }
    public List<Signatuur> signatuurid { get; set; }
}


public class DigestObject
{
    public DigestObject()
    {
    }

    public string result { get; set; }
    public string hex { get; set; }
    public byte[] datatosign { get; set; }
    public byte[] container { get; set; }
}