Process instantiation

Hello, 

I have created bpm and bpm on Bonita studio. I am working on .net and able to query data from the database which is H2. The process uses form that is generated from the JSON contract form schema. I have problem posting data HTTP post using web API clients. On the client-side, all went fine and all statuses are Ok and data are serialized . I have created DTO for transfering data and when i insatiate process I go step by step debugging and everything is fine. Does Bonita community have a restriction with external API posting data? 

Below you can see protion of the code what i have done so far and on console i have everuthing as it should be 

 [HttpPost("[action]")]

        public async Task<IActionResult> Complexi([FromBody]RequestViewModel obj)
        {


            var id = (string)TempData["id"];
         
            var url = await _bonitaClient.GetData();
            using (var handler = new HttpClientHandler { UseCookies = false })
            {
                using (var client = new HttpClient(handler))
                {
                    var formContent = new Dictionary<string, string>
                            {
                              {"username", "william.jobs"},
                              {"password", "bpm"},
                              {"redirect", "false"}

                            };
                    client.BaseAddress = new Uri(url);

                    client.DefaultRequestHeaders.Clear();
                    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));   
                    string urls = "API/bpm/process/" + id + "/instantiation";
                    string urlt = "API/bpm/process/" + id + "/contract";
                     var setCookie = string.Empty;
                    var result = client.PostAsync("loginservice", new FormUrlEncodedContent(formContent)).Result;                

                    var message = new HttpRequestMessage(HttpMethod.Get, urlt);
                    var st = "";
                    foreach (var header in result.Headers.Where(header => header.Key == "Set-Cookie"))
                    {
                        foreach (var value in header.Value)
                        {
                            st += value + ";";
                        }
                        break; // We only care about the first match.
                    }

                    message.Headers.Add("Cookie", st);

                    var Res = await client.SendAsync(message);

                    if (Res.IsSuccessStatusCode)
                    {
                        var json = JsonConvert.SerializeObject(obj);
                        var contacts = new StringContent(json, Encoding.UTF8, "application/json");
                        var t =  await client.PostAsync(urls, contacts);

                    }

                }
            }

            return Ok("Data has been sent");
        }

 

Hi,

I tried to implement the same using Postman, i.e. log in, list processes, start a process and check that it worked.

I was able to do it on Bonita Community 7.10.3 without any trouble.

What I figured out is that I was careful to set the X-Bonita-API-Token header with the value retrieved from the login request. You can have a look to this documentation page for more details: Bonita REST API Authentication

From what I can see of your code snippet, I am not sure you do the same. Maybe you could check this.

 

Hope this helps,

Captain Bonita

Hi Burim,

I've made a bit more of investigation. It appears that when using the Fetch API & CORS, it is not possible to read the cookies as a result. See the Fetch documentation for more details

That is why the code snippet I gave you does not work in your case (I personally wrote it and tested it, on the same Tomcat server than Bonita). As there was no CORS involved in my testing process the code was working. 

But in your case, the getCookie() method will always return an empty string, hence will not allow the following calls to the Bonita APIs. I hence had to adapt the code to retrieve the X-Bonita-API-Token from the session.

Here is a code snippet fully tested on Chrome against a Bonita 7.10.3 Tomcat bundle configured with CORS enabled. This code starts a process by providing the data required as per expected in the process contract:

    function loginToBonita() {
      var myHeaders = new Headers();
      myHeaders.append("Content-Type", "application/x-www-form-urlencoded");

      var urlencoded = new URLSearchParams();
      urlencoded.append("password", "install");
      urlencoded.append("redirect", "false");
      urlencoded.append("username", "install");

      var requestOptions = {
        method: 'POST',
        headers: myHeaders,
        body: urlencoded,
        redirect: 'follow',
        credentials: 'include'
      };

     return fetch("http://127.0.0.3:18219/bonita/loginservice", requestOptions)
     .then(
        result => {
          return getAuthToken();
        })
      .catch(error => console.log('Login error:', error));
    }


    function getAuthToken() {
      var myHeaders = new Headers();
      var requestOptions = {
        method: 'GET',
        headers: myHeaders,
        credentials: 'include'
      };  

      return fetch("http://127.0.0.3:18219/bonita/API/system/session/1", requestOptions)
          .then(response => {
            return response.headers.get('x-bonita-api-token');
          })
          .catch(error => console.log('Unable to retrieve authentication token from session:', error));
    }

    function startProcess(token) {

        var formData = {"checkListInput": [{"name" : "Grocery shopping", "description" : "Buy some stuff", "dueDate" : "2020-04-03T00:00:00.000Z"}, {"name" : "House cleaning", "description" : "Put some order in the house before guests arrive", "dueDate" : "2020-04-03T00:00:00.000Z"}]};

        var myHeaders = new Headers();
        myHeaders.append("X-Bonita-API-Token", token);
        myHeaders.append("Content-Type", 'application/json');
        
        var requestOptions = {
          method: 'POST',
          headers: myHeaders,
          credentials: 'include',
          body: JSON.stringify(formData)
        };


       return fetch("http://127.0.0.3:18219/bonita/API/bpm/process/6948611233195621493/instantiation", requestOptions)
          .then(response => response.text())
          .then(result => console.log('Process started:', result))
          .catch(error => console.log('Unable to start process:', error));
    }

    loginToBonita().then(authToken => startProcess(authToken));

Notes:

  • Please adapt the URL to your server (I used 127.0.0.3:18219) 
  • Change the process ID (mine was 6948611233195621493)
  • Provide the required data to respect your process contract (mine was checkListInput that represented a todoList)
  • Headers and request options are really important, if you face CORS errors, I invite you to double check those to make sure you did not forget any of them.

But again, if you could avoid CORS at all, it would still be my favorite option.

PS: if you want to test this code with the corresponding process, you can import the .bos project from my GitHub: CheckList App - example

 

I believe with that you are all set to succeed in your implementation! :-)

Captain Bonita

Hi,

There is no restriction on Bonita Community regarding external client using REST APIs.

You say that everything is fine and works as expected, so what is the problem you are facing actually?

Captain Bonita

* I don't see data on database .  If i run the process from Bonita studio everything is fine. On my application. I use  the url 

 string urls = "API/bpm/process/" + id + "/instantiation";

* then i login with the code below where everything is fine 

var result = client.PostAsync("loginservice", new FormUrlEncodedContent(formContent)).Result;

var message = new HttpRequestMessage(HttpMethod.Get, urlt);
var st = "";
foreach (var header in result.Headers.Where(header => header.Key == "Set-Cookie"))
{
foreach (var value in header.Value)
{
st += value + ";";
}
break; // We only care about the first match.
}

message.Headers.Add("Cookie", st);

var Res = await client.SendAsync(message);

after this i make check like this and send data 

if (Res.IsSuccessStatusCode)
{
var json = JsonConvert.SerializeObject(obj);
var contacts = new StringContent(json, Encoding.UTF8, "application/json");
var t = await client.PostAsync(urls, contacts);

}

Everything goes fine but no data on database . On chrome i have status 200

 

 

Hello, 

First i want to thank you for the responses , I am trying hard to figure out what really happening. 

When i go debugging line by line i see that i am able to login and the X-Bonita-API-Token is stored on my code snippet variable st. When i proceed the following code passes and everything is ok

message.Headers.Add("Cookie", st);

var Res = await client.SendAsync(message);

if (Res.IsSuccessStatusCode)
{

}

. Let me explain the whole scenario 

I want to send data from html generated from the contract json based on bonita model contract named RequestModel (id, name, comment)).

On my application i Have RequestViewModel that has same attributes like model in bonita RequestModel.

I am able to query process, generate form , get json from contract , but the problem occurs when i post data , instantiation.

 

It will be more than help, if you can find any code snippet in JavaScript for process instantiation.

 

 

Thanks 

Burim Ameti

 

 

Hi,

Here is a snippet in Javascript.

var myHeaders = new Headers();
myHeaders.append("X-Bonita-API-Token", "f5145526-22ff-44a2-a1f7-bb296bf14372");
myHeaders.append("Cookie", "bonita.tenant=1; JSESSIONID=AB5D856CBC62BC4AE1AA20139EA11DF2);

var requestOptions = {
  method: 'POST',
  headers: myHeaders,
  redirect: 'follow'
};

fetch("http://localhost:8080/bonita/API/bpm/process/6108774665660467324/instantiation", requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));

 

See that the X-Bonita-API-Token is given as a dedicated header (not through cookies). You have to retrieve it from the cookies after login and make sure it is sent on all "write" API request calls (i.e. POST, PUT, DELETE). (This is a CSRF security check mechanism.)

Can you make sure that both the Cookie and the X-Bonita-API-Token header are sent and with a valid value? From what I can read from your code example you only send the Cookie, but not the X-Bonita-API-Token header.

 

I hope this helps,

Captain Bonita

Hello Captain, 
I see you  have added as hard coded but how to I cast dynamic those values. Which url are responsible for getting the Cockie and X-Bonita-API-Token, is it post or get method ,  please give me idea which url you are retrieving this token and session. Bellow i will provide snippet and please check if i am doing wrong something , always tokens are null in this code snippet

Please check this where I am doing wrong 


       $(document).ready(function () {
        var formData = {
            username: "walter.bates",
            password: "bpm",
            redirect: false
        };       

         var url = (window.location).href;
        var id = url.substring(url.lastIndexOf('/') + 1);
        console.log(id);
   
        var instantiation = "http:/localhost:8080/bonita/API/" + id + "/instantiation";
        var contract = "http:/localhost:8080/bonita/API/system/session/1";


        $("#form1").on('submit', function (e) {
            e.preventDefault();
            $.ajax({
                url: "http:/localhost:8080/bonita/loginservice",
                type: "POST",
                data: formData,
                xhrFields: { withCredentials: true },

                success: function (data, textStatus, jqXHR) {
                    console.log(data),
                        $.ajax({
                        url: "http://localhost:8080/API/bpm/process/" + id + "/contract",
                        type: "GET",
                        xhrFields: { withCredentials: true },
                        success: function (data, textStatus, jqXHR) {
                            console.log('success getting session' + jqXHR);
                            var apiToken =  jqXHR.getResponseHeader('X-Bonita-API-Token');
                            console.log('X-Bonita-API-Token: ' + apiToken);
                            var formDatas = $('#form1').serialize();
                            $.ajax({
                                url: "http://localhost:8080/API/bpm/process/" + id + "/instantiation",
                             
                                type: "POST",
                                contentType: "application/x-www-form-urlencoded",
                                /*passing the X-Bonita-API-Token for the CSRF security filter*/
                                headers: { 'X-Bonita-API-Token': apiToken },
                               
                                data: JSON.stringify(formDatas),
                                xhrFields: { withCredentials: true },
                                success: function (data, textStatus, jqXHR) {
                                    console.log('tokennnnn', +apiToken);
                                    console.log('success inserting data');
                                    console.log(data);
                                },
                                error: function (jqXHR, textStatus, errorThrown) {
                                    console.log('error updating user info');
                                }
                            });
                        },
                        error: function (jqXHR, textStatus, errorThrown) {
                            console.log('error getting session');
                        }
                    });
                },
                error: function (jqXHR, textStatus, errorThrown) {
                    console.log('error login');
                }
            });
        });
    });

Hi,

When calling the login API, the server adds a cookie with the X-Bonita-API-Token in it. You are supposed to extract it from the cookie and add it as a dedicated header on all other future call.

Here is an example on how to do it:

var myHeaders = new Headers();
myHeaders.append("Content-Type", "application/x-www-form-urlencoded");

var urlencoded = new URLSearchParams();
urlencoded.append("password", "install");
urlencoded.append("username", "install");
urlencoded.append("redirect", "false");

var requestOptions = {
  method: 'POST',
  headers: myHeaders,
  body: urlencoded,
  redirect: 'follow'
};

function getCookie(cname) {
  var name = cname + "=";
  var decodedCookie = decodeURIComponent(document.cookie);
  var ca = decodedCookie.split(';');
  for(var i = 0; i!=ca.length; i++) {
    var c = ca[i];
    while (c.charAt(0) == ' ') {
      c = c.substring(1);
    }
    if (c.indexOf(name) == 0) {
      return c.substring(name.length, c.length);
    }
  }
  return "";
}
fetch("http://localhost:8080/bonita/loginservice", requestOptions)
  .then(result => console.log(getCookie('X-Bonita-API-Token')))
  .catch(error => console.log('error', error));


In this example after logging in, I print on the console the value of the token, of course what you want is to store it and use it on all queries done on the REST API.

Let me know if it helped,
Captain Bonita

Hello, are you able to provide complet code snipet that gets the tokens and sends data let data be hardcoded in javascript. I tried but i have undefined send . i have this code snippet does not work 

$(document).ready(function () {
        $("#form1").on('submit', function (e) {
            e.preventDefault();
            var myHeaders = new Headers();
            myHeaders.append("Content-Type", "application/x-www-form-urlencoded");
             var urlencoded = new URLSearchParams();
               urlencoded.append("username", "walter.bates");
               urlencoded.append("password", "bpm");
               urlencoded.append("redirect", "false");
            var formData = $('#form1').serialize();
            //console.log(formData);
            function getCookie(cname) {
            var name = cname + "=";
            var decodedCookie = decodeURIComponent(document.cookie);
            var ca = decodedCookie.split(';');
            for (var i = 0; i++;) {
                console.log(getCookie('X-Bonita-API-Token'));

                //catch(error => console.log('error', error));
            };
        };
            $.ajax({
                url: '/Contract/Complexi',
                headers: myHeaders,
                type: "POST",
                data: formData,
                redirect: 'follow',
                success: function (data) {
                    alert(data.message);
                    console.log(data);
                },
                error: function (xhr, resp, text) {
                    console.log(xhr, resp, text);
                }
            });
        });


        });
 

Hello, are you able to provide complete code snippet that gets the tokens and sends data to let data be hardcoded in JavaScript. I tried but I have undefined send. i have this code snippet does not work 

$(document).ready(function () {
        $("#form1").on('submit', function (e) {
            e.preventDefault();
            var myHeaders = new Headers();
            myHeaders.append("Content-Type", "application/x-www-form-urlencoded");
             var urlencoded = new URLSearchParams();
               urlencoded.append("username", "walter.bates");
               urlencoded.append("password", "bpm");
               urlencoded.append("redirect", "false");
            var formData = $('#form1').serialize();
            //console.log(formData);
            function getCookie(cname) {
            var name = cname + "=";
            var decodedCookie = decodeURIComponent(document.cookie);
            var ca = decodedCookie.split(';');
            for (var i = 0; i++;) {
                console.log(getCookie('X-Bonita-API-Token'));

                //catch(error => console.log('error', error));
            };
        };
            $.ajax({
                url: '/Contract/Complexi',
                headers: myHeaders,
                type: "POST",
                data: formData,
                redirect: 'follow',
                success: function (data) {
                    alert(data.message);
                    console.log(data);
                },
                error: function (xhr, resp, text) {
                    console.log(xhr, resp, text);
                }
            });
        });


        });
 

Hi,

By combining my 2 code snippets I have the following code working when executed in my browser (Google Chrome DevTool):

var myHeaders = new Headers();
myHeaders.append("Content-Type", "application/x-www-form-urlencoded");

var urlencoded = new URLSearchParams();
urlencoded.append("password", "install");
urlencoded.append("redirect", "false");
urlencoded.append("username", "install");

var requestOptions = {
  method: 'POST',
  headers: myHeaders,
  body: urlencoded,
  redirect: 'follow'
};

function getCookie(cname) {
  var name = cname + "=";
  var decodedCookie = decodeURIComponent(document.cookie);
  var ca = decodedCookie.split(';');
  for(var i = 0; i!=ca.length; i++) {
    var c = ca[i];
    while (c.charAt(0) == ' ') {
      c = c.substring(1);
    }
    if (c.indexOf(name) == 0) {
      return c.substring(name.length, c.length);
    }
  }
  return "";
}

fetch("http://localhost:8080/bonita/loginservice", requestOptions)
 .then(
  result => {
    var myHeaders = new Headers();
    myHeaders.append("X-Bonita-API-Token", getCookie('X-Bonita-API-Token'));
    
    var requestOptions = {
      method: 'POST',
      headers: myHeaders,
      redirect: 'follow'
    };

    fetch("http://localhost:8080/bonita/API/bpm/process/6108774665660467324/instantiation", requestOptions)
      .then(response => response.text())
      .then(result => console.log(result))
      .catch(error => console.log('error', error));
 })
  .catch(error => console.log('error', error));

 

The console then displays something like:

{"caseId":4002}

Where the caseId is the id of the newly started process instances. Which I can see when I access the Bonita Portal.

I hope this helps,

Captain Bonita

this seems good solution.  but i have dynamic data forms div , where do you cast those data. I see uou use new headers .What kind of data you post ?  name ?

When I instantiate I don't see data on a database,  tried still not see data on database data I post is JSON in this format and that is generated from the button. I am able to post from the postman . This is my contract

 

{
     "requestMemebershipInput" : {
        "name": "test",
        "reason": "111"
     }

Hello Captain Bonita, 

I am trying by your code something is missing here. I am trying to instantiate but I have instantiation form with two fields, I am getting 401. what I see on console Cookies are not the same with login and instantiation fetch, In your code, I don't see that you are passing some data, I tried with my code passing some data but getting 401  I have form instantiation contract with two fields name and reason,  I am in need pass those values by calling instantiation to fetch, You can see my code below. 

  var requestMemebershipInput = $('#form1').serialize();

        var formData =( { requestMemebershipInput });
     
           var urls = (window.location).href;
            var id = urls.substring(urls.lastIndexOf('/') + 1);
            var myHeaders = new Headers();
            myHeaders.append("Content-Type", "application/x-www-form-urlencoded");
         
            var urlencoded = new URLSearchParams();
            urlencoded.append("password", "install");
            urlencoded.append("redirect", "false");
            urlencoded.append("username", "install");

            var requestOptions = {
                method: 'POST',
                headers: myHeaders,
                body: urlencoded,
                redirect: 'follow'
            };
            
           
            function getCookie(cname) {
                var name = cname + "=";
                var decodedCookie = decodeURIComponent(document.cookie);
                var ca = decodedCookie.split(';');
                for (var i = 0; i != ca.length; i++) {
                    var c = ca[i];
                    while (c.charAt(0) == ' ') {
                        c = c.substring(1);
                    }
                    if (c.indexOf(name) == 0) {
                        return c.substring(name.length, c.length);
                    }
                }
                return "";
            }

            fetch("http://localhost:8080/bonita/loginservice", requestOptions)
                .then(
                    result => {
                        var myHeaders = new Headers();
                       myHeaders.append("X-Bonita-API-Token", getCookie('X-Bonita-API-Token'));
                 
                        var requestOptions = {
                            method: 'POST',
                            body: formData,
                            headers: myHeaders,
                            redirect: 'follow'
                        
                        };

                        fetch("http://localhost:8080/bonita/API/bpm/process/" + id + "/instantiation?username=walter.bates&password=bpm&redirect=false", requestOptions)
                            .then(response => response.text())
                            .then(result =>  console.log(result))
                            .catch(error => console.log('error', error));
                    })
                .catch(error => console.log('error', error));

        });

      

 

Hi,

CORS is always a pain. You are facing this because your Javascript script is not loaded from the same server than Bonita. This is a security mechanism implemented by web browsers.

So now the question is, are you trying to make your script working for the sake of a test, or do you plan to use this code in production?

If you plan to use this in production, then I would strongly advice to change the approach:

Let your front-end submit the form toward your back-end (.NET if I well understood) and make your back-end call the Bonita APIs from the server. This way you won't face CORS restriction AND you will not have to manage username/password of Bonita on your front-end (you do not want to have your username/password in your front-end, for obvious security reasons)

If this is for the sake of testing you have 2 solutions:

  1. Disable CORS on your web browser 
  2. Configure CORS on the Bonita server (more secure option but sometime time consuming): see Documentation: Enable CORS in Tomcat Bundle

By the way in the documentation page I point to, there is an example on how a web page could use the APIs to check the CORS settings, it might be helpful for you to look at.

Captain Bonita

Hi Captain Bonita, 

After some additional settings about cors and other types of settings i achieved to start instantiation from your example and i appreciate it. 

I am in need not using hardcoded formdata  I need to cast contract name , and data from form.

 

Thanks , I appreciate it very much for the code snippet you gave me  

Burim

 

 

 

Hello, Mr Captain Bonita 

I don't see your today's answers or comments here please make them visible 

 

Burim 

Hi, I've just made changes in the question to make sure Answers are correctly identified, as there are lots of comments. That's why you've received a notification today. FYI I will update your answer to a comment, as it is not providing any new answer to your question. Please make sure that you use comments to ask for more information or give more information to the members answering your question. Thank you.