Friday, February 29, 2008

Application (Message) Based Routing in BizTalk Orchestration

This may not be a common scenario, but a few weeks ago, I was given this requirement :

If a message comes from Application 1, please send it to Web Service A in Server X, if it comes from Application 2, please send it to Web Service A in Server Y, so on and so forth.


Notes : Web Services A is a same webservices deployed on both Server X and Server Y

What are the purposes of having this requirement?

  1. Development :
    • We have numbers of projects which may be developed in different development servers but for the BizTalk parts, they are all deployed in a single BizTalk Development box. When we're calling the a particular web services, it is possible that Project A will use Development Server A and Project B will use Development Server B.
  2. Production :
    • For Load management purpose, we can point Application 1 calls to Web Server A and other Application calls to Web Server B.

In BizTalk, we can do it this way below, however since Web Service A in Server X and Server Y are actually a same web services, BizTalk will not be able to process the messages because they will have same namespace and same schema information. Unless you want to use different pipelines for each send port, imagine if you have 10 or more applications, do you really want to create additional custom pipelines for each of them :P

To solve the namespace and schema issue, we can create a common orchestration which can be shared / called by the applications. But wait, there's only 1 send port can be defined in this solution, we still want Application 1 to call Web Services A in Server X and Application 2 to call Web Services A in Server Y.


Now, this is where Role Link comes to play. If you see the diagram below, between the Orchestration and the send ports, there is a Role Link Provider. This is just like an additional routing layer which we can use instead of correlating the orchestration and the send port directly.


How to use Role Link is quite similar to regular Send port, you may notice when clicking on the port surface, there is New Role Link option ;) and you will need to choose between Provider & Consumer which is basically to define whether you will be sending message through this port or receiving message through this port.


The next question will be how to route the message?
  1. We will need to have a value in the message to differentiate where this message comes from and should be routed into
  2. We define the destination party before we send the message, e.g :
    varDestinationPartyName = HelperClass.GetDestinationPartyName(partyName);
    send_port1(Microsoft.XLANGs.BaseTypes.DestinationParty) = new Microsoft.XLANGs.BaseTypes.Party(
    varDestinationPartyName,"OrganizationName");
  3. We create new Party with the Organization name that we define, e.g : Application 1, Application 2, etc.
  4. After deployed the orchestration, we should be able to find the provider we defined in the orchestration in the Role Links folder under Orchestrations folder (BizTalk Administration Console)
  5. Click on the provider to open the the window. In this window, we can bind the party and the send ports. For illustration :
    • Party : Application 1, Logical Port : send_port1, Physical Port : sp_WSA_ServerX
    • Party : Application 2, Logical Port : send_port1, Physical Port : sp_WSA_ServerY
    • Party : Default, Logical Port : send_port1, Physical Port : sp_WSA_ServerY
Notes : I provide a default party as well, so in case if i have a new application 3 and i haven't added it to the party list, it will use the default party :)

How to check whether the party exists?

public static string GetDestinationPartyName(string partyName)
{
string result = Constant.DefaultDestinationPartyName;
BtsCatalogExplorer catalog = null;

try
{
// Create the root object and set the connection string
catalog = new BtsCatalogExplorer();
catalog.ConnectionString = ConfigurationHelper.BizTalkMgmtDBConnectionString;

// Get the requested party from the collection
Microsoft.BizTalk.ExplorerOM.Party party = catalog.Parties[applicationUserID];

if (party == null)
{
//Log as warning that the party could not be found
}
else
{
result = party.Name;
}
}
catch (Exception ex)
{
//Log the exception
}
finally
{
if (catalog != null)
catalog.Dispose();
}

return result;
}


Notes : Party List is not available at WMI

If you're getting this exception "Microsoft.BizTalk.ExplorerOM.BtsException: The database or the database version is incompatible with the installed version of this product.", you can just use sql objects to retrieve the party name from database instead of using the BizTalkCatalogExplorer :

cmd = new SqlCommand("select nvcName from bts_party where nvcName = @partyname", cn);
SqlParameter prmPartyName = new SqlParameter("@partyname", applicationUserID);
cmd.Parameters.Add(prmPartyName);


Hopefully, this interests you as it is for me ;)

Tuesday, February 26, 2008

Date Serialization Issue (GMT) in BizTalk Orchestration?

I just posted a question in the microsoft.public.biztalk.orchestration a few minutes ago.

I found Paul's post and Nigel's post in the net related about this date problem as well, someone mentioned that this is a .Net Serialization for DateTime. But the thing that i have yet to understand, why this only happens in BizTalk Orchestration? When the message is sent to the web services, it is also serialized right?

Nigel's solution is to handle the time offset difference in the Orchestration by ourselves, but is this a right way to do? What I'm afraid of is that if i follow this now, what if later in future, the date serialization is suddenly working fine (because of hotfix or whatever) then i find my self with bunch of wrong dates again :P

For the time being, we decided that we will pass the date in string format where we already have a current standard string format to follow. Why? Because currently this is not the only problem that we have, we're also having a different DateTime behavior with Oracle Adapter in Development and Production server aarrgghhh :(

I'll update this post with the solution when i find what, why, and how ;)
If you do have any existing solution, please let me know.

Problem :
Date becomes GMT in the BizTalk orchestration which cause wrong results (input <> output)

Scenario :
Web Application <-> Exposed Web Services By BizTalk Orchestration <-> BizTalk Orchestration <-> Consume Another Web Services

<-> : Request - Response communication.

BizTalk Orchestration will construct a message to consume another web services based on the request information from web application

Below message will be passed all the way in the process :
Note : DateInfo = xs:datetime, DateOnly = xs:date

Request Message :
<request xsi="http://www.w3.org/2001/XMLSchema-instance" xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://MyBizTalkApp.Response">
<message>Calling BizTalk Web Services at 2/26/2008 1:35:47 PM</message>
<dateinfo>2008-02-26T13:35:47.1828125+08:00</dateinfo>
<dateonly>2008-02-26</dateonly>
</request>

Web Service Message :
message : <string>Calling BizTalk Web Services at 2/26/2008 1:35:47 PM</string>
dateInfo : <datetime>2008-02-26T05:35:47.1828125Z</datetime>
dateOnly : <datetime>2008-02-25T16:00:00Z</datetime>

Trace Results :
WebApplication: DateInfo : 2008-02-26T13:35:47.1828125+08:00
WebApplication: DateOnly : 2008-02-26T13:35:47.1828125+08:00

ExposedWebServices: DateInfo : 2008-02-26T13:35:47.1828125+08:00
ExposedWebServices: DateOnly : 2008-02-26T00:00:00.0000000

BizTalkOrchestration: Request DateInfo : 2/26/2008 5:35:47 AM -> Date becomes GMT
BizTalkOrchestration: Request DateOnly : 2/25/2008 4:00:00 PM -> Date becomes GMT
BizTalkOrchestration: ConsumedWS Request DateInfo : 2008-02-26T05:35:47.1828125Z -> Get From Request DateInfo
BizTalkOrchestration: ConsumedWS Request DateOnly : 2008-02-25T16:00:00.0000000Z -> Get From Request DateOnly

ConsumedWebServices: DateInfo : 2008-02-26T05:35:47.1828125Z
ConsumedWebServices: DateOnly : 2008-02-25T16:00:00.0000000Z

Monday, February 18, 2008

GridView CheckBox Adding Javascript Event

My colleague asked me whether it is possible to add a JavaScript event to CheckBox row in GridView.

The scenario is :

  1. There are 5 checkboxes in each of the row in GridView.
  2. Total checks is the number of checkboxes which are being checked in the row
  3. Frequency is the limit number of total checkboxes which will be allowed to be checked in the row
This should be able to be done in the backend / postback, but unless you're using ajax to do this, it would be bad if page always postbacks everytime the user selects a checkbox ;)

Steps :
  1. Add OnRowDataBound="gvTest_OnRowDataBound" event to the gridview
  2. Add onclick attributes on the checkboxes in the event
    if (e.Row.RowType == DataControlRowType.DataRow)
    {
    CheckBox chkW1 = e.Row.FindControl("chkW1") as CheckBox;
    CheckBox chkW2 = e.Row.FindControl("chkW2") as CheckBox;
    CheckBox chkW3 = e.Row.FindControl("chkW3") as CheckBox;
    CheckBox chkW4 = e.Row.FindControl("chkW4") as CheckBox;
    CheckBox chkW5 = e.Row.FindControl("chkW5") as CheckBox;

    Label lblTotalChecks = e.Row.FindControl("lblTotalChecks") as Label;
    Label lblFrequency = e.Row.FindControl("lblFrequency") as Label;

    string validationScript = string.Format("return ValidateCheckAtRow('{0}', '{1}', '{2}', '{3}', '{4}', '{5}', '{6}');"
    , chkW1.ClientID
    , chkW2.ClientID
    , chkW3.ClientID
    , chkW4.ClientID
    , chkW5.ClientID
    , lblTotalChecks.ClientID
    , lblFrequency.ClientID);

    chkW1.Attributes.Add("onclick", validationScript);
    chkW2.Attributes.Add("onclick", validationScript);
    chkW3.Attributes.Add("onclick", validationScript);
    chkW4.Attributes.Add("onclick", validationScript);
    chkW5.Attributes.Add("onclick", validationScript);
    }

  3. Add javascript function ValidateCheckAtRow
    function ValidateCheckAtRow(w1, w2, w3, w4, w5, totalchecks, frequency)
    {
    try
    {
    var chkW1 = document.getElementById(w1).checked;
    var chkW2 = document.getElementById(w2).checked;
    var chkW3 = document.getElementById(w3).checked;
    var chkW4 = document.getElementById(w4).checked;
    var chkW5 = document.getElementById(w5).checked;
    var lblFrequency = document.getElementById(frequency);
    var lblTotalChecks = document.getElementById(totalchecks);
    var limit = ReadSpanElementText(lblFrequency);
    var total = 0;

    if (chkW1)
    {
    total += 1;
    }
    if (chkW2)
    {
    total += 1;
    }
    if (chkW3)
    {
    total += 1;
    }
    if (chkW4)
    {
    total += 1;
    }
    if (chkW5)
    {
    total += 1;
    }

    if (total > limit)
    {
    alert('You have exceed the limit for the row!');
    return false;
    }
    else
    {
    WriteSpanElementText(lblTotalChecks, total);
    return true;
    }
    }
    catch(e)
    {
    alert(e);
    return false;
    }
    }

    function ReadSpanElementText(sp)
    {
    if (window.ActiveXObject)
    {
    return sp.innerText;
    }
    // code for Mozilla, Firefox, Opera, etc.
    else if (document.implementation && document.implementation.createDocument)
    {
    return sp.firstChild.nodeValue;
    }
    }

    function WriteSpanElementText(sp, val)
    {
    // code for IE
    if (window.ActiveXObject)
    {
    sp.innerText = val;
    }
    // code for Mozilla, Firefox, Opera, etc.
    else if (document.implementation && document.implementation.createDocument)
    {
    sp.firstChild.nodeValue = val;
    }
    }


Notes :
  • The ClientID is only available on the OnRowDataBound event, if you try to use this in the OnRowCreated event, it will render the raw "chkW1" id instead of something like "gvTest_ctl02_chkW1"
  • When the checkboxes are being rendered, you may notice that it will actually render checkbox input inside of a span element, the javascript event will also be rendered in the span element, instead of the checkbox input control. That's why we use onclick event to get it works (onchange will work on FireFox but not on IE) and if validate function return false, the checkbox will automatically unchecked as well ;)
Of course there are other workarounds such as using javascript to parse the control id, but i find this way is more straight forward by using the id directly ;)

Just another thoughts of mine ;)

** Updates: The old location is not available anymore, I have uploaded a newer one here

Saturday, February 16, 2008

ASP.Net 3.5 Extension Dynamic Data, SQL Compact Edition 3.5, Linq, IIS7

It's been a while since my last blog, I went back to Indonesia visiting our families while celebrating Chinese New Year 2008 ;) It's never enough for holiday and i got the side effect of post holiday syndrome (need more holidays) :P Btw, happy chinese new year for you guyz, wish all good things happen this year of 2008 ;)

Back into the reality and the subject ;)

I have been tracking all my family financial and expense information in an excel file until last december 2007 where I thought that it will be great if i can create some .Net apps for this, while i can learn some new stuffs as well.
At that time, SQL Compact Edition 3.5 just came out so i downloaded and installed it, i got some problems with it but finally managed to use it and migrated all my data from excel file to the SQL CE (created a small app for this).

After the database is done, the next thing to do is the User Interface app.
I read Scott Gu's blog post about the ASP.Net Dynamic Data some time ago which is quite perfect for me since i only need a simple app to manage the data. So i downloaded the extension and watched the introduction video. You have to check it out to see what it can do.

Steps :

  1. Install the ASP.Net 3.5 extension
  2. Create a new project using a new template project for website : Dynamic Data Website, it will generate all the files needed for the site.
  3. Since i'm using SQL CE, the linq to sql file has to be generated manually using SqlMetal.exe (use VS 2008 Command prompt and type sqlmetal.exe to see the user guide). Include the generated dbml files to the project
  4. Open the [dbml file name].designer.cs file and add a parameterless constructor with the database connection string for the sdf file or get it from web.config file :
    public MyData() : base (@"Data Source='D:\MyDatabase.sdf';Password='mypassword'")
    {
    OnCreated();
    }

  5. Since by default SQL CE is not intended for web application, you will need to add a global.ascx file and add this line below in the application_start to get it working
    AppDomain.CurrentDomain.SetData("SQLServerCompactEditionUnderWebHosting", true);
  6. Compile the app and run ;)
The result is quite amazing, it displays all the tables and their relationships in the database (generated by sqlmetal) and it provides most of the functionalities such as edit, delete, insert that I need :D

When deploying the app into IIS 7, i encountered some problems and found the solutions below :
1. Configuring ASP.Net 3.5 into IIS7
2. Precompilation problem with Dynamic Data

The name "Dynamic Data" said it all, it is able to display your data dynamically no matter how many tables with different structures by using only few template files and also very extensible for your own customization.

Give it a try and see for your self ;)

Monday, February 4, 2008

Microsoft.XLANGs.Core.UnexpectedMessageTypeException: Received unexpected message type '' does not match expected type

Just drop a quick note on this.

If you ever find this BizTalk exception where you so sure that you didn't change anything and it just happens after you deploy your orchestration :
Check your pipeline settings in your send port, it might have changed into PassThruTransmit/PassThruReceive instead of XmlTransmit/XmlReceive.

Not sure why it's changed, but this sometime happens after re-deploying orchestration.

Another 2 hours wasted because of this :(

BizTalk Oracle Database Adapter First Load Performance

I found this performance problem with BizTalk Oracle DB Adapter a month ago when developing some orchestrations using this adapter.

When re-deploying my solution and restarting the host instances, i need to wait about 15 - 20 minutes, you can imagine how difficult to wait that long, especially after only changing one or two lines of code then re-deploying the codes for testing.

After doing some investigations and tracing the process, i found that actually BizTalk itself was loading very quickly, but it stopped at the send port to the Oracle DB. My orchestration receives messages from web services and inserts information to the oracle database.
Then i traced the database process and finally i found these results below :


SELECT /*+ RULE */ '', a.owner, decode (b.object_type, 'PACKAGE', CONCAT( CONCAT (b.object_name, '.'), a.object_name), b.object_name),decode(a.position, 0, 'RETURN_VALUE', a.argument_name), decode(a.position, 0, 5, decode(a.in_out, 'IN', 1, 'IN/OUT', 2, 'OUT', 4)), 0, a.data_type, a.data_precision, a.data_length, a.data_scale, a.radix, 2, '' , '', 0, 0, '', a.position, '' FROM ALL_ARGUMENTS a, ALL_OBJECTS b WHERE ( b.object_type = 'PROCEDURE' OR b.object_type = 'FUNCTION' ) AND b.object_id = a.object_id AND a.data_level = 0 AND a.OBJECT_NAME LIKE '[Procedure / Function name]' ESCAPE '\' AND b.OWNER = '[User Account]' ORDER BY 2,3, a.overload, 18

When loading the send port, BizTalk will try to load all the oracle procedures & functions schema information which are accessible by the specified database user account in the send port. The user account which i was using has access to all 2000+ procedures the current database where i'm actually only using less than 20 of them :(

Moral of the story :
Use or create a specific application user account which only has access to the required oracle procedures in the send port.

Below is the sql query and please note that you will need to have access to the v$sql to run this :

Select sql_text, module, first_load_time, last_load_time, users_opening, users_executing
From v$SQL
Where last_load_time != ' '
AND Upper(MODULE) = 'RUNTIMEAGENT.EXE'
Order By last_load_time DESC;