Friday, July 31, 2009

Unlocking PureEdge forms

I was creating an inprocessing web application. It had to serve up many official legal forms. So I couldn't mess with their layout or content much without making them invalid. One of the forms was a locked XFDL PureEdge form. I really needed to get some hardcoded data off the form. The form was base64 encoded and had a lock protection thing set on it.

A normal base64 decoder wouldn't work because of some unicode character problems so I wrote a custom decoder. Once the form was decoded I found the text on and switched it to off and the form was unlocked. Now I could modify it using the pureEdge developer.

This was around a year ago. I've been digging through my emails trying to find all the specific details so I can log them here for myself and for others but I can't find it. So for now I will just write this until I have to re-write the custom decoder later and can better document the steps taken (many frustrating days of work).

GlassFish IIS passthrough SSO

JCIFS only does NTLMv1. Our security policies here dictate that only NTLMv2 should be used. JCIFS does NTLMv1 through a man-in-the-middle way of passing the authentication request from the browser to the web server then to the active directory controller then back.

There is documentation about this at: http://jcifs.samba.org/src/docs/ntlmhttpauth.html

One solution is to put IIS in front of GlassFish to handle the Single Sign On.

So I setup an IIS server with the isapi sun-passthrough.dll with integrated security enabled. I pointed it to our development server stand-alone (no cluster, no load balancer, no node agents). Request.getRemoteUser() isn't being populated but I am able to pull the authenticated username by base64 decoding the NTLM type 3 message in request.getHeader("Authorization") that is passed along from IIS.

I don't want to do this in every web application though. So a better solution is needed.

Google Voice, VOIP, SIP, MagicJack, Gizmo, XLite, WRTP54G

Google Voice will only allow you to do PSTN to PSTN (public switched telephone network) calls. There is no way to dial out from a computer or directly receive calls on a computer. I am moving to Japan and will need to call back to the US often.

There are a few services that provide PSTN dial in to SIP forwarding. Ipkall and Gizmo offer free dial in to SIP phone numbers. Gizmo makes money on outgoing phone calls. IpKall.. doesn't make money and only have dial in lines in washington.

GV does offer forwarding to PSTN phone numbers or Gizmo SIP accounts. So I can use my GV California number forwarded to Gizmo SIP and answer on my computer. I can't call out though.

A good friend gave me his unused Magic Jack. MJ is a USB to PSTN (RJ11) device plus unlimited service for a year for $40. Having a windows computer running 24/7 to be able to receive phone calls on MJ is annoying. If any computer was going to be running 24/7 at my house it wouldn't be running windows, it would be running Linux. MJ doesn't support Linux. Having a usb dongle always connected is slightly annoying. MJ forces advertising onto the screen when calls are made or received. Annoying.

So I ran some software to export the MJ SIP account information. Gizmo wouldn't allow me to put that SIP info in. Gizmo software only seems to support it's own SIP service.

I put the MJ SIP info into XLite and it didn't work. I changed the settings 20 times. Then it did suddenly work. I closed it and reopened it. No go. Changed the settings 20 times and suddenly it worked again. No settings seemed to work right away. Eventually I figured out that the first connect was failing but changing settings forced a re-attempt. I'll go more into details on that later. I found that the older X-Lite 2.0 didn't have this problem. So I got that setup on Linux and Windows.

Now I can dial out and receive calls on the computer from anywhere yeahhh! Now I want to hook up a wireless phone. I can't use the MJ hardware anymore. There are some old undocumented drivers to get MJ hardware working with XLite but I couldn't get them to work.

So I ordered a WRTP54G-ER Wireless Router w/ Earthlink TrueVoice for $30! Wifi and VOIP SIP in one router with VOIP priority. These things sell for around $200 but noone wants to use EarthLink VOIP. The device is locked to EarthLink. So I will have to hack it and make it use the MJ SIP account information. I've been waiting for 3 weeks for it to show up. More info on that when it shows up.

I seem to do everything the hard way to save a few bucks. I enjoy most of the process but in the end it sometimes ends up not being worth it. This blog helps add some value to my hard work. Maybe someday someone will read it.

Java GlassFish JCIFS access logging NULL-AUTH-USER

If you are using JCIFS for java web application SSO authentication in GlassFish you will not get usernames logged in the access log.

I posted this at: http://forums.java.net/jive/thread.jspa?messageID=358412 but this solution took 7 hours to develop and should help some other people so I am reposting it here.

I ran into the same problem. We are using jcifs.http.NtlmHttpFilter for authentication. The access logs show NULL-AUTH-USER. I saw your solution, copying the remoteUser into request scope in every application and logging using %attribute.%. We have around 20 web apps though. So this did not seem like a reliable solution. I considered modifying jcifs source but jcifs is a library in every webapp currently, so all would need to be modified. I found that JCIFS puts the username in a session attribute called NtlmHttpAuth. Glassfish doesn't have any documented method to log session variables. So I searched through the glassfish source code for any undocumented features. No session logging.

I found that with adding one line of code to glassfish I could make JCIFS authenticated user access logging work.

I added:

if (user == null && hreq.getSession(false) != null && hreq.getSession(false).getAttribute("NtlmHttpAuth") != null) { user =
hreq.getSession(false).getAttribute("NtlmHttpAuth").toString(); }

in com\sun\enterprise\web\accesslog\DefaultAccessLogFormatterImpl.java appendAuthUserName method (line 388).

I compiled it and put it in:
C:\Sun\AppServer\lib\appserv-rt.jar

We are running Sun GlassFish Enterprise Server v2.1 (9.1.1). I only compiled and added that one java file to appserv-rt.jar. I used source from:
GlassFish Project - v2.1 FinalBuild (also known as v2.1 b60e Promoted Build).

This isn't the best way to patch glassfish but it is working.

Wednesday, July 8, 2009

Google Voice

Ok this isn't coding related. Many of my posts are more about hacking things than coding specifically.

I just got my google voice invite today! This is perfect. Hopefully I can port my cell number to it soon because I will be moving to Japan in under 2 months and need some kind of VOIP solution and a place to port my cell phone numbers to.

I'll post again when I've actually played with google voice. I'm at work right now. I signed up many many months ago soon after google took over from grandcentral.

Thursday, July 2, 2009

ROT 13 encoding and computer basics for an 8 year old


I showed my daughter how to write secret messages using ROT13.

If you haven't heard of ROT 13, imagine cutting an empty toilet roll in half
(vertically, so you still get 2 cylinders).
You write: ABCDEFGHIJKLMNOPQRSTUVWXYZ on one.
Then: ABCDEFGHIJKLMNOPQRSTUVWXYZ on the other.
Line up A on the top one with A on the bottom one.
ROT is short for rotate.
Now rotate the bottom cylinder 13 letters ahead so that A is above and N is below.
Flat on paper this now looks like:
ABCDEFGHIJKLMNOPQRSTUVWXYZ.
NOPQRSTUVWXYZABCDEFGHIJKLM.
You can now us this to encode and decode messages.
HELLO would become:
URYYB
http://en.wikipedia.org/wiki/Rot13

I also showed her letters to numbers:
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26.

So here HELLO would become:
8 5 12 12 15.

Next I'll teach her that computers have character maps. ASCII.
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z.
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90.

Hold down ALT and press 72 and you get H.
ALT 72 ALT 69 ALT 76 ALT 76 ALT 79 would become:
HELLO

Eventually I'll explain binary and how data is stored using electrical ons and offs.

.NET gridview not showing header is no data

I've been fighting with empty girdviews not showing the header for a while and this is the solution I came up with:


public static void gridViewShowHeader(GridView gridView)
{
// Show header row (if none)
Table table = new Table();
if (gridView.Controls.Count != 0) table = (Table)gridView.Controls[0];
if (table.Rows.Count != 0 && ((GridViewRow)table.Rows[0]).RowType
== DataControlRowType.Header) return;
GridViewRow headerRow = new GridViewRow(-1, -1,
DataControlRowType.Header, DataControlRowState.Normal);
for (int i = 0; i < gridView.Columns.Count; i++)
{
TableHeaderCell headerCell = new TableHeaderCell();
headerCell.Text = gridView.Columns[i].HeaderText;
headerRow.Controls.Add(headerCell);
}
table.Rows.AddAt(0, headerRow);
gridView.Controls.Add(table);
}



I have another routine that adds dropdown lists as column filters.
So this routine doesn't just test for an empty gridview, it tests
for a gridview without a header. So this can be run before or after
my gridViewShowFilters routine without a problem.

.NET asp:BoundField DataFormatString problem


To apply a DataFormatString on a BoundField like DataFormatString="{0:MM/dd/yyyy}
in older versions of .NET you need to set HtmlEncode="false". It doesn't seem to
be needed in 3.5. If you are going to allow editing also add
ApplyFormatInEditMode="true".

I decided to do this programmaticly because I am dealing with many reports (~8)
with lots of fields (28-53) that change often. There is one method for gridViews
and another for detailsView.

public static void gridViewFormatDates(GridView gv)
{
for (int i = 0; i < gv.Columns.Count; i++)
{
if (gv.Columns[i].GetType() == typeof(BoundField))
{
BoundField d = (BoundField)gv.Columns[i];
SqlDataSource sds = (SqlDataSource) gv.DataSourceObject;
if (gv.Columns[i].SortExpression.Contains("date"))
{
d.ApplyFormatInEditMode = true;
d.DataFormatString = "{0:MM/dd/yyyy}";
}
}
}
}

public static void detailsViewFormatDates(DetailsView dv)
{
for (int i = 0; i < dv.Fields.Count; i++)
{
if (dv.Fields[i].GetType() == typeof(BoundField))
{
BoundField d = (BoundField)dv.Fields[i];
if (dv.Fields[i].SortExpression.Contains("date"))
{
d.ApplyFormatInEditMode = true;
d.DataFormatString = "{0:MM/dd/yyyy}";
}
}
}
}

Ok you may have noticed that the fieldname (sortExpression) must have "date"
in it for this to work. Yeah it's ugly. So I came up with another solution
which still isn't perfect either.

I previously created a method to shorten long gridview columns. So I added
this automatic date formatting to it. Call this from your onrowcreated method
like gridViewShortenColumns(myGridView, e.Row, 30);

public static void gridViewShortenColumns(GridView gv, GridViewRow gvr, int width)
{
DataRowView dr = (DataRowView)gvr.DataItem;
for (int i = 0; i < gvr.Cells.Count; i++)
{
DataControlFieldCell d = (DataControlFieldCell)gvr.Cells[i];
String name = d.ContainingField.SortExpression;
if (!name.Equals("") && dr[name].GetType() == typeof(DateTime))
((BoundField)gv.Columns[i]).DataFormatString = "{0:MM/dd/yyyy}";
if (dr != null && name != null && !name.Equals("") &&
gvr != null && dr[name] != null && dr[name].ToString().Length > width)
{
gvr.Cells[i].ToolTip = dr[name].ToString();
dr[name] = dr[name].ToString().Substring(0, width) + " ...";
}
}
}

Monday, June 8, 2009

VS2008 Error connecting to undo manager

In VS2008 do you get "Error connecting to undo manager of source file 'SOMEFILE.aspx.designer.cs'"? Delete SOMEFILE.aspx.designer.cs, right click on the project and choose "Convert to Web Application". It will regenerate that SOMEFILE.aspx.designer.cs and the error will go away.

I'm not sure what is causing it but I'm seeing this error come up every few days. It might be because I tried out Web Developer Express and now I'm back to using Visual Studio 2008.

Maybe it has something to do with: Visual Local History 2005 (VLH2005) that I started using:
http://vlh2005.codeplex.com

Tuesday, June 2, 2009

Yikes! This morning I found out our main application server ssl certificate expired. We have to request a new ssl
cert from the Army and it may take a few weeks. The warnings the users get aren't too bad. Everyone ignores
warnings anyways. Some web services are failing though.

So I need to add some code to make the web applications trust invalid ssl certificates.

So in java I put in:

public static void installAllTrustingAuthority()
{
// Create a trust manager that does not validate certificate chains
TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager()
{
public X509Certificate[] getAcceptedIssuers() { return null; }
public void checkClientTrusted(X509Certificate[] certs, String authType) {}
public void checkServerTrusted(X509Certificate[] certs, String authType) {}
} };
try
{
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, null);
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
}
catch (Exception e) { e.printStackTrace(); }
}


In .NET we are getting this error "AuthenticationException : The remote certificate is invalid according to the validation procedure."

So I found this website: http://saftsack.fs.uni-bayreuth.de/~dun3/archives/ignoring-https-related-authenticationexception-when-using-a-webclient/307.html
and added:

public static void SetBypassSslCertificateValidation()
{
ServicePointManager.ServerCertificateValidationCallback += new RemoteCertificateValidationCallback(BypassSslCertificateValidation);
}

private static bool BypassSslCertificateValidation(object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors error)
{ return true; }

Wednesday, May 20, 2009

Disable Sprint One-Click Carousel

Here are instructions on how to disable/enable Sprint One-Click Carousel which is on the Samsung Rant phones and probably many others:

Push in ##3282# as if it was a phone number (it is also ##DATA#).
Go down to *.XUI and turn it on/off using XUI on/off.

The phone will run much better with it disabled. Mine used to freeze ocassionally while one-click was fetching updates or something. This was especially annoying when I was pressing * back to unlock the phone and it would just sit there doing nothing for 45 seconds while I repeatedly entered * back, * back, * back.

Tuesday, May 19, 2009

4 Javascript functions - Post 1


<html>
<head>
<title>Javascript examples</title>
<style>
.c1 {} .c2{} .c3 {} .c4{} .c5 {} .c6{} .c7 {} .c8{} .c9 {} .c10{}
.LockOff { display: none; visibility: hidden; }
.LockOn
{
display: block;
visibility: visible;
position: absolute;
z-index: 998;
top: 0px;
left: 0px;
width: 105%;
height: 105%;
font-size: 4em;
background-color: #ccc;
text-align: center;
padding-top: 20%;
padding-bottom: 40%;
filter: alpha(opacity=55);
opacity: 0.55;
}
</style>
<script>
var previousRow;
var previousRowColor;
function highlightRow(row, newColor)
{
if(previousRow != null) previousRow.style.backgroundColor = previousRowColor;
if(previousRow != row)
{
previousRow = row;
previousRowColor = row.style.backgroundColor;
row.style.backgroundColor = newColor;
}
else previousRow = null;
}

function getRadioValue(radioObj)
{
if (!radioObj) return "";
if(radioObj.checked) return radioObj.value;
for (var i = 0; i < radioObj.length; i++) { if (radioObj[i].checked) return radioObj[i].value; }
return "";
}

function hideClass(className)
{
for(i=0; i<document.styleSheets.length; i++)
{
rules = document.styleSheets[i].rules;
if(rules == null) rules = document.styleSheets[i].cssRules;

for(j=0; j<rules.length; j++)
{
if(rules[j].selectorText == '.'+className) rules[j].style.display='none';
}
}
}

function w()
{
var lock = document.getElementById('pleaseWaitPane');
if(lock) lock.className = 'LockOn';
}

function no_w()
{
var lock = document.getElementById('pleaseWaitPane');
if(lock) lock.className = 'LockOff';
}
</script>
</head>
<body>
<h2>Here are examples of 4 useful javascript functions</h2>
<div id="pleaseWaitPane" class="LockOff">
Please Wait   
<button onclick="no_w(); document.execCommand('stop');">Cancel</button>
</div>

<a href="https://webapp1.tamc.amedd.army.mil/phonelist/slowLoading.jsp" onclick="w();">Wait example link</a>

<form method="get" action="https://webapp1.tamc.amedd.army.mil/phonelist/slowLoading.jsp" onsubmit="w();">
<input type="submit" value="Wait example form"/>
</form>

<input type="radio" name="radio1" value="value1"/>value1
<input type="radio" name="radio1" value="value2"/>value2
<input type="radio" name="radio1" value="value3"/>value3
<input type="radio" name="radio1" value="value4"/>value4
   
<button onclick="var str=getRadioValue(radio1); if(str == '') alert('Please choose a value'); else alert('You choose '+str); ">
getRadioValue example
</button><br/><br/>

<b>HideColumn example</b>: click an x to temporarily hide a column.<br/>
<b>HighlightRow example</b>: click a row to see it highlighted.<br/>
<table border="1">
<tr>
<th class="c1">col1 <a href="#" onclick="hideClass('c1');">x</a></th>
<th class="c2">col2 <a href="#" onclick="hideClass('c2');">x</a></th>
<th class="c3">col3 <a href="#" onclick="hideClass('c3');">x</a></th>
<th class="c4">col4 <a href="#" onclick="hideClass('c4');">x</a></th>
<th class="c5">col5 <a href="#" onclick="hideClass('c5');">x</a></th>
<th class="c6">col6 <a href="#" onclick="hideClass('c6');">x</a></th>
</tr>
<tr onclick="highlightRow(this,'pink');"><td class="c1">test1</td><td class="c2">test2</td><td class="c3">test3</td><td class="c4">test4</td><td class="c5">test5</td><td class="c6">test6</td></tr>
<tr onclick="highlightRow(this,'pink');"><td class="c1">test1</td><td class="c2">test2</td><td class="c3">test3</td><td class="c4">test4</td><td class="c5">test5</td><td class="c6">test6</td></tr>
<tr onclick="highlightRow(this,'pink');"><td class="c1">test1</td><td class="c2">test2</td><td class="c3">test3</td><td class="c4">test4</td><td class="c5">test5</td><td class="c6">test6</td></tr>
<tr onclick="highlightRow(this,'pink');"><td class="c1">test1</td><td class="c2">test2</td><td class="c3">test3</td><td class="c4">test4</td><td class="c5">test5</td><td class="c6">test6</td></tr>
<tr onclick="highlightRow(this,'pink');"><td class="c1">test1</td><td class="c2">test2</td><td class="c3">test3</td><td class="c4">test4</td><td class="c5">test5</td><td class="c6">test6</td></tr>
<tr onclick="highlightRow(this,'pink');"><td class="c1">test1</td><td class="c2">test2</td><td class="c3">test3</td><td class="c4">test4</td><td class="c5">test5</td><td class="c6">test6</td></tr>
</table>

</body>
</html>

Friday, May 15, 2009

Sprint Cell Phone customization - Post 2

Changing the XUI URL and XUI FEED URL did nothing! It didn't even break any of the XUI features (that I've noticed yet). Nothing showed up on the tcpdump. I pointed both at my own server and watched for any traffic from my phone.

Here are some browser version and browser strings from my old phone and my new phone:

Samsung Upstage:
Default: Samsung-SPHM620 AU-MIC-M620/2.0 MMP/2.0 Profile/MIDP-2.0 Configuration/CLDC-1.1
Opera: Mozilla/5.0 (compatible; OpenWeb 5.6.1.3-03) Opera 8.54
Unknown: Mozilla/5.0 (compatible; OpenWeb 5.7.4-09)

Samsung Rant:
Polaris (Default): Samsung-SPHM540 Polaris/6.0 MMP/2.0 Profile/MIDP-2.0 Configuration/CLDC-1.1
BOLT: Mozilla/5.0 (X11; 78; CentOS; US-en) AppleWebKit/527+ (KHTML, like Gecko) Bolt/0.920 Version/3.0 Safari/523.15
Opera: Opera/8.01 (J2ME/MIDP; Opera Mini/3.1.10423/1724; en; U; ssr)

.NET annoyance 6 - asp:DropDownList


asp:DropDownList departmentDD has 0 elements.
In the code-behind I set departmentDD.SelectedIndex = 9;
In the debugger I see that departmentDD.SelectedIndex stays at 0.
I would expect it to throw an error, stay at zero, or switch to 9.
It stayed at 0. I'm ok with that.
Further down in the codebehind I do push some data into
departmentDD and departmentDD.databind.
Suddently departmentDD.SelectedIndex is equal to 9!
It remembered and set the value later once it could do it.
That's nice of it. I don't expect or want to object to be nice.
I want it to be consistant and predictable.




How many other weird quirks are in the web component objects?




So I ran another test.
asp:DropDownList departmentDD has 2 elements.
In the code-behind I set departmentDD.SelectedIndex = 1;
In the debugger I see that departmentDD.SelectedIndex is now 1.
I don't like this.
If it's going to some lame late update of selectedIndex when databound,
it should do it that way everytime.

Thursday, May 14, 2009

Sprint Cell Phone customization

I just got a Sprint Samsung Rant cell phone for free. It is a big bulky thing but has a nice keyboard.

I got the MSL service code, copied the primary NAI to the tethered NAI using bitpm and QPST, and other fun stuff I'm not going to explain in detail. If you enter ##DATA# as phone number you can get at a lot of extra settings. Also going into settings, display, press # has more settings but you need your MSL.

So anyways.. I want to customize the user interface. Using ##DATA# I can disable XUI making the One-Click Carousel go away. That leaves the phone in safe mode. It seems to be fully functional and faster in safe mode. Safe mode is probably how the phone manufacturer wanted the phone to work. so now we have One-Click or Safe Mode.

I found that ##BREW# brings up a whole different user interface. Now we have Brew also as a UI.

I see in ##DATA# an XUI URL and XUI FEED URL. They are set to tileservice.sprint.com/xui/live and feedservice.sprint.com. I've been searching google and I can't find anything about those settings or those urls! Has no one else wanted to change the tiles available in the One-Click interface? That can't be! Maybe sprint is killing off everyone mentioning it. I'll start saying my goodbyes soon.

Those urls are not accessable from my computer's browser. They don't seem to work from the default browser, Bolt, or Opera Mini 3 either. Maybe I will try some tests while using the phone as a modem (tethered). Maybe those services aren't on port 80.

Later I'll have to point those urls at my hosting server and use a packet sniffer to see what the phone sends. Maybe I will find some neat ways to customize the One-Click XUI.

Monday, May 11, 2009

.NET annoyance 5 - asp:CheckBox tag

HTML allows you to check a checkbox and submit any value.
Web developers get used to that type of thing.
.NET has an asp:CheckBox that only allows you to submit "on" or nothing.

I have a search page that has 10 checkboxes. In java I would just simply put those checkbox submitted values into a query (after sql escaping them). The checkboxes are type1-5 and skill1-5.

<input type="checkbox" name="type1" value="AD"/>Active Duty
<input type="checkbox" name="type2" value="CIV"/>Civilian
...

select name, department, type, skill from personTable where type in (@type1,@type2,@type3,@type4, @type5) and skill in (@skill1, @skill2, @skill3, @skill4, @skill5)

Push the request values into that sql query and Bing bang bong done. Not in .NET using asp:checkbox tags though. I would have to have code behind with the value of each of those checkboxes. I hate to put things that could change in two places.

<asp:CheckBox ID="type1" runat="server" Text="Active Duty" />
<asp:CheckBox ID="type2" runat="server" Text="Civilian" />
...

(In this example I am using a asp:SqlDataSource peopleDS) Then in the codebehind (in
peopleDS_Selecting) I would have to put code in to modify the sql query.
if (!type1.Checked) e.Command.Parameters["@type1"].Value = "AD";
if (!type2.Checked) e.Command.Parameters["@type2"].Value = "CIV";
...

Very annoying. Now I have the sql query split between .. Nevermind. I'll setup a better example later.

.NET annoyance 4 - Lack of conditional tags

There are no conditional tags in .NET web components.

So instead of a nice jstl tag in java like:

Java/JSP/JSTL/EL Solution #1:
<c:if test="${row.name == ''}"%gt;BLANK%lt;/c:if>

In .NET I would have to create a code-behind wrapper method like:

Solution #2:
protected String testEmpty(String stringToCheck, String trueReturn, String falseReturn)
{
if("".Equals(stringToCheck)) return(trueReturn);
else return(falseReturn);
}

Then use this in the form view:
<%# testEmpty(DataBinder.Eval(Container.DataItem, "name"),
"BLANK", DataBinder.Eval(Container.DataItem, "name")) %>

Or Solution #3:
protected void OnRowCreated(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
DataRowView dataRow = e.Row.DataItem as DataRowView;
Object dataValue = dataRow["name"];
if("".Equals(dataValue)) e.Row.Cells[2].Text = "BLANK";
}
}

In solution 2 I am essentially creating my own conditional tag. Why shouldn't there be one like this by default? In solution 3 I am creating codebehind that will break if I reorder the fields in the formview.

.NET annoyance 3

If you use masterpages expect all of your form variables to be renamed for you.
<input type="yourName" value=""/>
Is submitted as ctl00$body$yourName.
http://forums.asp.net/t/647306.aspx

.NET annoyance 2

Make sure you don't use # signs in html comments. Master Pages choke on those. Example: <!-- #BeginEditable "sidebar" -->
http://www.vbforums.com/showthread.php?t=547336

.NET annoyance 1

I am converting web applications from Java to .NET C#.
Many things bother me about .NET.
One of them is that I can't reuse a Master Pages content ContentPlaceHolder.
Here is someone describing the exact lame problem:
http://stackoverflow.com/questions/760334/contentplaceholders-repeated-content