Thursday, August 12, 2010
Fix for Extremely Slow Development Browser Performance
After coming across this post and doing what it recommended, the problem went away. I've no idea why that configuration change in Firefox would affect IE, but all my problems vanished.
Maybe this will help some other poor chap; it got to the point where I just couldn't work at all.
Friday, April 23, 2010
Detecting Session Timeout in ASP.Net WebForms
Disregard all this crap. It's simply not possible to detect session timeout using cookieless sessions. Detecting a post with a redirect does in fact trigger the condition in Application_EndRequest below (in my defense, it seemed to work in a test project and was deceiving).
[/Edit]
Everyone is starting new projects with MVC these days, but for all those maintenance projects in ASP.Net WebForms, here is what I've settled on as the best way to detect session timeout. I've had 1 or 2 other methods in production that didn't cause problems, but were not quite correct.
I was quite surprised when initially investigating this issue to see how many ways people had come up with to do it.
I had a completely different version of this up on this post for quite awhile that didn't work when using cookieless sessions.
Here's the assumption: if a user's session has timed out, but they've issued an HTTP POST, that indicates a session timeout. If you're not using cookieless sessions, that's all there is to it; check this somewhere in a page base class:
Consequently, the way I handled this problem is by detecting the above stated condition (user's session has timed out, current request is a post) in Global.asax. A Response.Redirect or Server.Transfer works fine with this. The problem that will occur with this approach is that the flag for session timeout will be set, but before the user with the timeout round trips back to the server with a GET, another user will hit Session_Start and receive an incorrect session timeout condition. Also, the user who timed out might, for whatever reason, not make the round trip back to the server. In this case, again, a user issuing his first request will see a session timeout. I'm willing to accept that, on rare occasion, this happens. Knowing users, he will simply try again, get the page he requested, and think little of it.
Global.asax code:
public class Global : System.Web.HttpApplication
{private bool _sessionTimedOutCookieless = false ;
void Application_EndRequest( object sender, EventArgs e )
{ /// This condition occurs when using cookieless sessions (but perhaps not with "AutoDetect" - that mode is evil) /// The redirect will occur within the context of this same application instance, so the private variable will still /// be alive. if ( Request.HttpMethod.Equals( "POST" ) && (Response.StatusCode == 302) )
{_sessionTimedOutCookieless = true ;
} }void Session_Start( object sender, EventArgs e )
{if ( _sessionTimedOutCookieless || (SessionTimedOutWithCookies()) )
{_sessionTimedOutCookieless = false ;
Session.Add( "timeout" , true );
} }private bool SessionTimedOutWithCookies()
{return Request.HttpMethod.Equals( "POST" ) && (Session.IsNewSession);
} }In a base page class:
protected void Page_PreInit()
{if ( Session["timeout" ] != null )
{_timedout = true ; // do something about this wherever
Session.Remove( "timeout" );
} }Friday, February 19, 2010
EF4 & SQL Server Date / DateTime Columns
System.Data.SqlClient.SqlException: The conversion of a datetime2 data type to a datetime data type resulted in an out-of-range value
I had 2 date columns in the table I was attempting to insert into to begin with. One was just a LastDateModified column with a default of GetDate() so I was mentally filtering that column out and focusing on the other column. I changed that column from date to datetime which didn't fix the problem. I then changed that column to DateTime(2,7) and it still didn't fix it. Running a trace on the SQL that EF4 was attempting to insert revealed the problem. EF4 isn't smart enough to see that my LastDateModified column has a default and shouldn't bother sending a value when the programmer doesn't set one. It sent some huge min date that didn't fit into a datetime column.
Thus, the workaround is to either set any columns I won't be explicitly setting in code to type date(2,7) or to explicitly set the value of those columns in code to DateTime.Now. In any case, you can't use the SQL Server 2008 date column data type (which sucks).
Saturday, December 12, 2009
Entity Framework IEnumerable Lambda Join Sample
var parent_child = entities.Parent.Join
(
entities.Child, // child entity
parent => parent.ParentId, // function providing parent key
child => child.ChildId, // function providing child key
( parent, child ) => new // projection (anonymous object returning what you want)
{
ChildId = child.ChildId,
WhateverElseFromChild = child.WhateverElse,
WhateverElseFromParent = parent.WhateverElse,
ParentId = parent.ParentId
}
// pc here is anonymous object created above
).Where( pc => pc.Condition == "some condition" ).FirstOrDefault();
Tracing this against SqlServer showed very clean SQL. The exact same SQL produced by this linq equivalent:
var parent_child = ( from parent in entities.Parent
join child in entities.Child on parent.ParentId equals child.ChildId
where parent.Condition == "some condition"
select new
{
ChildId = child.ChildId,
WhateverElseFromChild = child.WhateverElse,
WhateverElseFromParent = parent.WhateverElse,
ParentId = parent.ParentId
}).FirstOrDefault();
Thursday, November 19, 2009
Handling Extensionless URIs in a WebForms Application
ASP.Net MVC is all the rage, but I just needed to add extensionless functionality to a WebForms application. I'm going to host this application in IIS7, so that's how I'm describing it; there is more work involved for IIS6.
This all seems obvious now, but it took me awhile to figure out.
I started by adding a class that supported IHttpHandler. I need to have a handler (rather than a module) because I want to set a value in session, and that's not yet available within a module (at that phase in the IIS pipeline).
public class OnDemandHandler : IHttpHandler, System.Web.SessionState.IRequiresSessionState
{
public void ProcessRequest( System.Web.HttpContext context )
{
; // add something to session
}
public bool IsReusable { get { return true; } }
}
<httphandlers>
<add verb="GET" path="*" type="MyNamespace.OnDemandHandler, MyDll" validate="false">
That works fine, but it handles everything. How do I handle aspx pages, or images, or anything else? Copy in all the base handlers from the web.config at the machine level to my web.config? Move my httpHandler config element from my web.config to the machine web.config so it can fall last in order there and only run after all the base handlers have run? Use a module instead of a handler? I was having a hard time figuring out what to do until I decided to go check out a config of an MVC project. After all, they are doing what I want to do:
<httphandlers>
<add verb="*" path="*.mvc" validate="false" type="System.Web.Mvc.MvcHttpHandler, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
</httphandlers>
<httpmodules>
<add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
</httpmodules>
Again, it seems obvious now; use a module that checks for extensionless URIs, tack on my own pre-determined extension (just like they're using *.mvc), and send it off to my handler. For URIs with extensions, let them pass on normally.
So here's how it looks in the end:
public class OnDemandModule: IHttpModule
{
#region IHttpModule Members
public void Dispose()
{
}
public void Init( HttpApplication app )
{
app.BeginRequest += new EventHandler( OnBeginRequest );
}
#endregion
private void OnBeginRequest( object src, EventArgs e )
{
HttpContext context = ( src as HttpApplication ).Context;
var uriPath = context.Request.Path;
if ( UriIsExtensionless( uriPath ) )
{
/// Add the .inst extension so our handler will process it
context.Response.Redirect( context.Response.ApplyAppPathModifier( uriPath + ".inst" + context.Request.Url.Query));
}
}
/// <param name="uriPath">like /blah or /something.aspx. Does not contain possible query params</param>
/// <returns></returns>
private bool UriIsExtensionless( string uriPath )
{
var result = false;
if ( uriPath[0].Equals( '/' ) )
{
var restOfURI = uriPath.Substring( 1 );
if ( restOfURI.Length > 0 )
{
return !restOfURI.Contains( '.' );
}
}
return result;
}
}
public class OnDemandHandler : IHttpHandler, System.Web.SessionState.IRequiresSessionState
{
public void ProcessRequest( System.Web.HttpContext context )
{
context.Response.Write("OnDemandHandler fired");
context.Response.End();
}
public bool IsReusable { get { return true; } }
}
<!-- for cassini -->
<system.web>
<httphandlers>
<add verb="GET">
path="*.inst"
type="MyNamespace.OnDemandHandler, MyAssembly"
validate="false" />
</httphandlers>
<httpmodules>
<add name="OnDemandModule" type="MyNamespace.OnDemandModule, MyAssembly">
</httpmodules>
</system.web>
<!-- for IIS7 -->
<system.webserver>
<modules runallmanagedmodulesforallrequests="true">
<add name="OnDemandModule">
type="MyNamespace.OnDemandModule, MyAssembly" />
</modules>
<handlers>
<add name="OnDemandSurvey">
verb="GET"
path="*.inst"
preCondition="integratedMode"
type="MyNamespace.OnDemandModule, MyAssembly" />
</handlers>
</system.webServer>
Monday, November 16, 2009
Getting the SQL (and other goodness) from a running transaction
WTF? How could I have failed to commit a transaction yet testing all checked out? Well that's a mystery I haven't yet figured out, but I did find a good query (that I had to alter somewhat to make work) for getting the text of the command that had not yet been committed:
SELECT
st.Session_id, req.Blocking_Session_ID [Blocker], req.Wait_Type,
req.Wait_Time [WaitTimeMS], req.Wait_Resource, req.open_transaction_count,
req.percent_complete, dt.transaction_id, dt.database_transaction_begin_time,
case when dt.database_transaction_type = 1 then 'RW'
when dt.database_transaction_type = 2 then 'R'
when dt.database_transaction_type = 3 then 'Sys'
else 'Unknown'
end [TranType],
case when dt.database_transaction_state = 1 then 'Not Initialized'
when dt.database_transaction_state = 3 then 'Initialized, but no logs'
when dt.database_transaction_state = 4 then 'Generated logs'
when dt.database_transaction_state = 5 then 'Prepared'
when dt.database_transaction_state = 10 then 'Committed'
when dt.database_transaction_state = 11 then 'Rolled Back'
when dt.database_transaction_state = 12 then 'In process of committing'
else 'Unknown'
end [TranState],
req.Status, req.Command, stxt.objectid [ExecObjID],
(SUBSTRING(stxt.text, req.statement_start_offset/2,( CASE WHEN req.statement_end_offset = -1 then LEN(CONVERT(nvarchar(max), stxt. text)) * 2
ELSE req.statement_end_offset
end -req.statement_start_offset)/2)) [SubText],
stxt.text, req.statement_start_offset
FROM
sys.dm_tran_database_transactions dt (nolock)
inner join sys.dm_tran_session_transactions st (nolock) on dt.transaction_id = st.transaction_id
inner join sys.dm_exec_requests req (nolock) on st.transaction_id = req.transaction_id
CROSS APPLY sys.dm_exec_sql_text(req.sql_handle) [stxt]
where
dt.database_id = db_id() and st.is_user_transaction = 1
This query wouldn't work in my situation (would barf "Incorrect syntax near '.'" at the second to last dot), but it will work on a database with compatibility level 90 or better.
Friday, October 09, 2009
IE8 Text Alignment Bug
![]() | |
|---|---|
| 36 | This sentence should left align with bob below. |
![]() | |
|---|---|
| 41 | bob |
Wednesday, September 30, 2009
F# Forward composition operator
let squares wrist =
List.map (fun x -> x*x) wrist
let sum wrist =
List.fold (+) 0 wrist
// Forward composition operator
// don't need to declare any value: it's implied.
let functionComp = squares >> sum
// Equivalent function as above using pipe operator.
// Note how above is more terse
let pipage wrist = wrist |> squares |> sum
let wrist = [1;2;3]
// results are the same
let fc = functionComp wrist
let p = pipage wrist
Friday, June 19, 2009
The Question for .Net Web Developers
Silverlight, or MVC?
To me, silverlight turns the browser into a desktop application; when I worked on silverlight, I no longer thought about GET/POST whatsoever. Yes, I still get data from services, but I'm almost completely unaware of HTTP. Moreover, it seemed to lend itself to very clean applications from a pure OO perspective; same exact OO style on client and server.
With MVC, I'm thinking GET/POST over HTTP all the while. I'm getting back to basics in that regard (stepping back to the way things were before MS took desktop development and jammed it into the Internet with WebForms).
IMO, blending silverlight with MVC is a bastardization (don't take it personally if you're doing it). URLs need only have meaning in silverlight applications as a shortcut to start your application. In any case, I consider WebForms legacy for non-trivial applications. Still very useful when needed, but legacy.
So this is my question for today's .Net developer: "should we think about our new application like we did with desktop apps, or should we consider it RESTful?" Obviously, there are myriad other considerations; I just think this may be the most pertinent.
Wednesday, April 15, 2009
Workaround for changes to DataSet.Merge
DataSet.Merge changed between .Net frameworks 1.1 and 2.0 when DataSets have namespaces causing the merge to fail. There is a workaround, but the place where it's described on MS docs is totally freaking hosed and makes no sense.
Here is what we did which works fine for us (we're not about to go making big changes in this monster of a system):
string cachedNamespace = dataSet1.Namespace;
dataSet1.Namespace = string.Empty;
string cachedNamespaceChild = dataSet2.Namespace;
answerDataSet.Namespace = string.Empty;
dataSet1.Merge( dataSet2 );
dataSet1.Namespace = cachedNamespace;
dataSet2.Namespace = cachedNamespaceChild;
Wednesday, February 11, 2009
A Functional Deployment Template
Anyhow, spreadsheets were not up to the task for a couple reasons:
- We wanted to be able to have plain written documentation (paragraphs) in addition to some kind of "steps" checklist (we needed to start making complete deployment documents). It's possible to to this in Excel, but certainly not optimal.
- Editing large scripts in Excel cells can prove clunky. A line length limit makes it nearly impossible (if you try to paste in a script with a line too long nothing happens - no error and no pastey) to edit some large scripts.
I searched for a fair amount of time and came up with absolutely nothing. I did find a few deployment templates, but nothing with any functionality to them. So I went ahead and implemented such a beast using VSTO over a Word template. Perhaps someone will stumble upon this and find it useful. Implementing it was more challenging than one might think. Just like anything else, if you're not very familiar with VSTO, even small things are going to be difficult, but dynamically creating and persisting WinForms controls is actually sorta tricky.
Anyway, here's what it looks like:
Thursday, February 05, 2009
Stranahan's Colorado Whiskey [off topic]
I have gift cards from Christmas, and I keep going back to Tipsy's (the mega-liquor-store near my house) to attempt to get some Leopold's (whose flavored whiskeys are still awesome) but they never have it - Leopold's production is low and demand high. So I asked an employee if I could get a rain-check or something. He asked if I wanted Stranahan's explaining that some just came in but it's going fast. He directed me to the front desk where I asked for a little form to place a request and the next employee said, "for Stranahan's?" Then a customer came up with a bottle of Stranahan's Colorado Whiskey. The manager and customer started raving about it. I asked them what it compares to, and they couldn't tell me. The manager said, "it tastes like something that costs way, way more." I said, "it already costs $60." He just said, "I know" and then said you can't believe how good it is. The customer said he'd open it and let me try if he could.
So I figured what the heck, I got a gift card, I'll try some. They were right. I've never had 250 y/o whiskey, but I'd imagine it tastes something like this stuff. Toward the middle of my glass I swear there was no burn on the tongue whatsoever. And it doesn't compare with anything (the first sip tasted completely foreign). It just tastes... clean and fresh. It's amazing. Apparently, there's much more to making smooth whiskey than age.
IMO this stuff is like the next level. Nothing I've had is anything close to as good.
Friday, January 30, 2009
Silverlight the Right Choice for Web Applications
I couldn't be more strongly in favor of using Silverlight for web applications (as opposed to web sites which only provide content) wherever possible. Of course even content can be a good use of Silverlight as the NYT reader shows. If an MS shop's target audience doesn't include IT goons who somehow put Silverlight in a different category than Acrobat Reader, I don't think you have any other choice for new applications.
My coworkers are spending many hours trying to make one AJAX codebase work on different browsers. After awhile it just gets demoralizing (and my employer is wasting tons of maintenance time). My mind is boggled by all the hackage necessary to make AJAX popup "windows" drag/drop via HTML (sure Javascript makes it happen, but it's still just HTML). With Silverlight you're working on a platform designed for this kind of thing. It's designed for dragging and dropping and animations and all the other things needed for applications (not to mention streaming).
Yes, you can make all those things happen with Javascript, and I fully understand the power of functional languages, but who would rather maintain a line of business application written in Javascript in favor of C#? Not to mention the aforementioned ever growing browser compatibility issues.
How do loosely typed Javascript objects compare with full featured Silverlight controls with UI and logic contained in one distinct entity combined with design time configuration? Not very well on the maintainability scale.
That's how I cast my vote, anyway. Long live Silverlight. I doubt it will be a technology that fades away, but I hope it's user base grows and it continues to stay healthy in the future.
SSIS Gotchas
- Restoring a database renders connections in packages unusable (even though everything looks good and testing the connection is fine). Thus, every time you restore a database that a package is pointing to (which I was doing repeatedly during testing), you must go into the package, add a new connection from a data source (by right clicking in the connection manager area of each package) and add in the same source that’s already being used (giving it a different name). Then you must go into each task item in the package and point to the new connection. If that task had mappings (as from a flat file to a table), you must re-map everything. Enjoy!
- If you’re executing a package from .Net code, only the .Net connection types will run.
- There are 3 different forms of essentially the same connection type: “Native OLE DB”, “.Net providers for OleDb”, and “.Net Providers”
- The “.Net Providers” didn’t seem to work at all; the “Native OLE DB” providers would not execute when attempting to run the packages from a web page on a different server; both the “Native OLE DB” and “.Net providers for OleDb” worked from my local machine.
- It seems the proper choice for calling packages from code is “.Net providers for OleDb”.
- The “.Net providers for OleDb” will not execute from code directly from within visual studio (i.e., you can’t right click on the package and click execute) so you can't debug directly.
- The .Net connections ignore their timeout property. They simply won't work when running a long query.
- Enjoy!
- You can't include a package that deals with a table that doesn't yet exist.
- So you can’t have a package that refers to a table that hasn’t yet been created. The package will give errors all over the place. So, for instance (IIRC), you can’t deploy a package that refers to a table that will be created by another package b/c it won’t validate on deployment.
- Deployment of packages is a nightmare. There are myriad issues surrounding the demonic property of packages, “ProtectionLevel”.
- Under most circumstances, you would want to make your connection string properties configurable at the server. That way, you can move the packages around or point them to different servers w/o having to redeploy them. In my case, I had no end of problems with this (and the packages need only run annually) so I just deployed and ran this way:
- For deployment, I would just do file | Save copy of [package] as and copy the package to the server running integration services. This would just embed the connection settings and not rely on any package configuration files. Then, for executing the packages from code, I would just refer to the server I copied the packages to.
- At one point, I had a package that had a bad path in one of its connection’s properties. This seemed to prevent all packages from working.
- Most destructive: Changing the connections and re-deploying (as described above) doesn't overwrite the connections. So I changed the connections from production back to QA, verified everything multiple times, redeployed (which prompts for confirmation whether I want to overwrite the existing packages), and ran a package. Much to my surprise & chagrin, the package executed against production! In this case, the existing packages must be deleted. In Reporting Services, there is some “overwrite data sources” property, but I couldn’t find this in SSIS.
- Another disturbing issue: packages run where they’re called from. If, in code, you execute some SQL against a particular server, that SQL will execute on the server. Confusingly (b/c I was importing and exporting data), when you execute a package, it executes on the machine where it was called, not on the server where the package exists!
- Warnings on package objects show up all over the place. There's no figuring out why they don't consistently work. E.G., login failure when there is nothing wrong with the connection. I think setting the property “BypassPrepare” to true helped.
- Unlike the predecessor technology, DTS, there is no way to switch a connection vis-a-vis data flow objects w/o losing mappings. Enjoy!
- Names of all new packages are Package1 – you must rename them and delete file Package1 manually.
- If you have an extra line break at the end of a script task, it crashes. You can copy/paste that script into management studio and it will work fine.
- Closing / reopening the project sometimes causes connection references from tasks to be lost.
- Yet another disturbing issue: I was not able to get it down to a reproducible set of steps, but the project was reverting my script changes. I found that I had to check my script changes into sourcesafe immediately after saving every time (I suspect that the reverting took place somewhere in deploying).
Thursday, January 08, 2009
Best way to get record count using ADO.Net?
I was modifying a stored procedure which needed to run a query and also return the row count of that query. So the same (fairly long) query was pasted again using select count instead of select by which it set the value of an out parameter. I didn't like this for a few reasons:
- Dual maintenance of the query.
- The stored procedure is twice as long.
- Inefficiency of calling the query twice. The count is already known by SqlServer after running the first query.
- Having another parameter to configure and hard-code the name of in the calling code.
So I looked at the properties of the SqlDataReader. It has a "RecordsAffected" property. Sounds a lot like "rows affected" no? Well, in order to get that count you must iterate through all the rows in the reader and close it. Obviously, not what I'm looking for.
Next I looked at the SqlCommand object. When you call ExecuteNonQuery, you get records affected! Cool, must be what I'm looking for. Nope - when this object says "records affected" it means just that (unlike what SqlServer means by "rows affected"). It will only return the count of rows updated, deleted, inserted.
So what seems to be the best way is to include a secondary recordset in the stored procedure: "select @@rowcount." My coworker verified that this count is connection specific (you don't get someone else's count). It's the count that management studio displays.
Accessing this count from a reader goes like yay:
_reader.NextResult();
_reader.Read();
_reader[0];
The call to NextResult advances the reader to the next result set (the set that only includes the result of "select @@rowcount"). We know that result set contains only the count so we don't need any explicit column names.
Tuesday, August 12, 2008
ASP.Net Ajax PopupControlExtender gotchas
- Technically, the first gotcha is that my user control cannot include a ScriptManager since there can only be one per page, and since the user control is repeated many could be created. Thus, the host page must own the ScriptManager effectively rendering my user control with my PopupControlExtender non-self containing. I think this is a clumsy limitation of the current state of ajax.
- The second gotcha applies regardless of the fact that there are more than one instance of the PopupControlExtender on the page: I don't want the object that pops up to be triggered when the referenced TargetControlID control receives focus (I can hardly imagine a scenario when someone would want this), I want it to pop up on a button click. Thus, I must pop it up manually. But when I do so, I must know (or learn by trial and error) that the client framework captures page events and hides my popup (so when you click on other controls outside your popup it closes). My pop function must look like the following. Without "event.cancelBubble" my popup won't pop up.
{
event.cancelBubble = true;
var popExtender = $find(popExtenderID);
// this one is the next gotcha
popExtender.set_OffsetY(popExtender.get_OffsetY() + document.body.scrollTop);
popExtender.showPopup();
}
- This next one is also irrespective of the fact that my control is in a repeater. The PopupControlExtender doesn't seem to account for scroll position. Thus the call to set_OffsetY in the snippet above. Even so, sometimes the position of the popup will be at the very top of the page. I haven't been able to figure out why it doesn't always work; it doesn't seem to be consistent.
- This one is certainly repeater related. The function above takes as a parameter the ID of a PopupControlExtender. If there are more than one, we have to accept the right one (the function is part of my user control so it will be repeated causing only the last one written to be the function that is used for all of my popups). So in the code behind of my user control, I had to add the following in order to pass in the "current" extender. Note that I had to ensure that the BehaviourID of the extender was unique. The replace call is to make the ID conformable to HTML rules.
this.PopupControlExtender1.BehaviorID = ExtenderBehaviourID;
this.btnPop.Attributes.Add( "onclick", "pop('" + ExtenderBehaviourID + "');" );
- This one was caused by the fact that I wrapped the controls within my pop up in an UpdatePanel because they made server postbacks. The code in the snippet above should only run when the host page loads or posts back, but not when my user control posts back. What I had is a search edit box/button and a GridView within my popup. The search button causes a postback and so does paging on the grid. Within these postbacks, I don't want my extender to receive a new unique id because if it does the javascript back on the page will receive the wrong ID - why I can't quite remember - I have a function that closes the popup. In this case "this.IsPostback" doesn't work (nor does this.Parent.IsPostback which maps to the same result) because this would represent the host page's postback. My hack elegant solution to this problem was to examine the BehaviourID of the PopupControlExtender. By default, that BehaviourID would contain the ID of the PopupControlExtender itself. If it is a postback of my control, I would've already changed that BehaviourID to be something like a guid (from the snippet above). So here's the condition:
{
return !this.PopupControlExtender1.BehaviorID.Contains(PopupControlExtender1.ID);
}
Tuesday, July 08, 2008
Business Vs. Technical Validated
In it, Jeff Atwood holds the opposing position, but Spolsky's comments were spot on. He did some significant work on a medical application w/o caring a whit about the medical field. I maintained an application which illustrated annuity performance w/o much caring about the specifics of annuities.
I just cannot understand Jeff's position. The specifics of how a given business works is brain filling fluff whereas programming related knowledge is a programmer's bread and butter.
I'm currently working on software which supports educational institutions, but learning more about educational institutions is never germane to making our software better.
Friday, June 06, 2008
Silverlight Bar Chart and Wizard Samples
- A silverlight bar chart / graph application.
- A silverlight wizard.
Wizard: This was created by this guy. When I downloaded it, it didn't work. Note that the arrows are actually images (which probably shouldn't be used as long as you have XAML). It is a good model, though. The whole wizard is one control, all the data is in one thing, no postbacks and the next/back is just a smooth animation. It makes a wizard a very easy, clean thing.
Wednesday, February 27, 2008
Web Programming is a Hack, Part Deux
It's just so fundamental. We're sitting here debugging a problem that happens when the user mixes the browser's "back" with our "next". We're tempted to yell at users, "don't click that button", but it makes perfect sense for them to do so. The problem is that web apps are hacks. Browsers don't make good platforms.
Of course, we fixed the "bug" (by "fixed the bug" I mean "made a better hack"), but the overarching hack remains.
It sure seems to me that there needs to be something new for applications. Browsers work great for HTML, but why isn't there something else (just as ubiquitous and standardized) for applications? It seems like RESTful apps are much less hacky than web applications, but not quite there. Plug-ins like flash and silverlight embed applications in the browser, and while I know nothing about actionscript, silverlight can make for a full fledged clean application. AJAX, hell no, but application plug-ins maybe. But these plug-ins are always mixed with HTML...
It may be that the hack will continue for a very long time simply due to the fact that people keep making ever more hacks at an increasing rate.
Hack on, I suppose.
Thursday, January 31, 2008
Web Programming is a Hack
I've spent most of my career so far doing either non-web apps or things like web services that are cohesive programs. Now I'm doing web development full time, so it's in my face.
HTML/HTTP was (as we all know) not intended to be a systems development platform. Nowadays, darn near every system must run over it. That's a fallacy, of course; many web apps don't need to be so (how many corporate internal web apps are there that could be much simpler as desktop apps), but most people think it's true.
Everything is so fragmented in web development - you may have:
- Code on the client for validation or whatever.
- Different code on the client that has nothing to do with the other code (AJAX).
- Code constructs in your CSS.
- Code on the server.
- Code directly on the page (sometimes necessary).
Consider what I had to do recently in order to make a simple animated gif run while a lengthy process takes place. I fully realize there are other ways to do it (queuing the process and just returning a simple message seems the best candidate), and I'm constrained in this case by slightly older technology (.net 1.1), but still:
- Client submits a batch of email messages. (Note: IE locks animated gifs while waiting for the return of a post).
- Server receives request, needs to return quickly so it starts an asynchronous process to send the messages, sends down a javascript handler to the page to receive the results of the asynchronous process, and turns on the gif.
- The page receives the result so it starts animating the gif.
- The asynch process starts sending email messages maintaining a collection of those that may have failed.
- When it finishes, it can return text to the client, but that handler can't receive complex results (JSON not avail, can't use a web service here), so all it receives is the number of successful email messages. It stuffs the failed collection in cache on the server.
- Now the client receives the result, knows how many messages were successfully sent, but it doesn't have the data on the failure collection. It builds a url to post back to the server with a query string sending the number of successful email messages back.
- The server detects the successful email query string, checks for a failure collection in cache, binds that to a grid (and turns it on) if it exists, writes out the successful number (couldn't have done that on the client because the state would be lost on the second trip to the server) and returns. Whew!
