Friday, March 7, 2008

User Interaction, Behavior, and Feedback

Warning : Non technical stuff content ;)

I stumbled upon Jeff Atwood's blog post on UsWare vs. ThemWare 2 days ago. The terms was originally from Eric Sink's blog post on Yours, Mine and Ours. It is a good advice and a very relevant on what we do today as a software developer.

User Interaction
In the first project as a software developer, I was indeed developing software as ThemWare. Why? Because I only knew how to code and test the software with my very own perspective which was more less as long as this page is working (able to display, save, edit, and delete), I'm done with it :p
Then as the program went live, I was there sitting beside the user using the application to make sure everything is going fine.
Well, guess what, the application caused a very long queue of the customers in peak hours and i could see from their faces that they were not tolerant for waiting that long :p
Reason : it was a new application for the users, even though they had the trainings but the load are obviously different compared to peak hours, "panic" mode is ON and made things worse when you can't do things as fast as before using the new application.
At the end of the day, I got some "major" feedbacks from the users which i can clearly understand because i was sitting with them whole day :
1. The page navigation was bad, too many steps to follow in order to do a transaction
2. All have to be done by mouse, they need keyboard short cuts and use tabs so they can do one transaction faster.
3. Too many post backs in the page and the page is too long, so they have to scroll down again and again.

So I did a quick major changes based on those things and that really helped the users afterwards and i was really glad that my program can really help to increase their productivity, that is why we create softwares right? To increase job productivity and efficiency :)

From those experiences, i keep trying to also see things from a user perspective when developing applications, making it as easy as possible to do things.

So don't just make the things work, but make them USABLE as well by creating the UsWare software ;)

User Behavior
I believe that the assumption that the user will use the program as we expect is totally WRONG. For example, as the developer of the program X, I certainly know that after I do (A), I won't do (B). But that may not apply to the users, if we don't put any validations or clear steps to prevent that, it is always a big chance that they will actually do that ;p
It is always hard to predict the user behavior, but adding some thoughts of the variation of possible use case scenarios will be very helpful :) It will be much more costly to handle problems when it happens in production later.

User Feedback
I feel that it is very important to listen to user feedback, because they are the one who are using the software. However, do make a deep consideration or have some discussions first before following and making any changes.
Reasons :
1. There are some cases where after a change has been done, then turns out that it makes things worse or at least not helping.
2. YAGNI (You ain't gonna need it) or you ain't gonna use it
But in any cases, if you receive feedbacks from the users, that means a good thing, they are really using the application :)

For me, I always feel really happy when a user come back to me and said "Thank you, you just make my life easier with that" :)

Monday, March 3, 2008

Regular Expression / RegEx with Expresso

I'm not a RegEx expert :P I'm using RegEx for some simple validations, creating scripts and editing some string.

I remembered the first time i read a RegEx expression a couple of years ago, i said to my self, "WOW, this really looks like an encrypted string rather than an expression" :P

What i want to share here is that several months ago, I stumbled upon Expresso, it is a development tool for regex, this is a powerful stuff, there are tons of features and library resources inside and the authors from Ultrapico is kind enough to give away the tool for free :)

So nowadays, every time i need to create a RegEx expression, i always open up Expresso, test that my RegEx expression (plus the replacement string) is working as expected, then i deploy the expression into the code. Testing the results is much easier and faster than ever ;)

If you do need to work with RegEx, i really suggest you check it out ;)

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 :(