Back in 2002, many thousands - difficult to say how many exactly - of ASP/VBScript developers were left alone in the woods by a very small team of MS developers who chose to build ASP.NET and throw ASP and VBScript in the bin. ASP got canceled, from one day to the other. That's how it felt back then, and that's what really happened looking back on it today.
I sometimes try to picture it like this: In 2002, Microsoft had just had a great couple of years. Windows 95, 98, 98SE, 2000 Professional, NT Server, 2000 Advanced Server. Not to mention Windows Me and the various XP editions. MicroSoft also had Internet Explorer, Office, SQL Server and many other products. These were all great products that were loved by basically any gamer, student, school, non-for-profit and all businesses. Between 1997 and 2003, MicroSoft was the leading software company worldwide, without any doubt. They sure could afford to make mistakes and make some wrong decisions. And so they did. That's how Classic ASP/VBScript ended up in the bin, without much ado. Just like that. But - as always - pride comes before a fall.
ASP (Active Server Pages) can be combined with multiple Active Scripting languages in IIS. VBScript has always been the most popular choice (by far). JScript and Perlscript were the two others, but they were barely used. 99% of all ASP web applications used and still use VBScript as their preferred Active Scripting language.
But I was not the only one. ASP/VBScript were very popular web development and scripting technologies between 1997 and 2003. They were MicroSoft's first answer to another rapidly growing web technology and competitor: PHP and Apache servers. We all know how that turned out. However, most businesses preferred ASP over PHP back then. ASP was running faster, integrated well with other MicroSoft products (Active Directory, SQL Server, Office, ...), had a rapidly growing community and supported (multi-threaded) COM-extensions. But above all, ASP/VBScript was very easy to learn, even for people like me, without a degree in computer science whatsoever. VBScript was a basic, visual, case insensitive and lousy typed scripting language. And I loved it.
Even though ASP was a huge success, MicroSoft developers decided to pull the plug and amuse themselves with... how many ... well what the heck, 25 versions of ASP.NET. ASP.NET never was nowhere near a popular web development technology. ASP/VBScript was. In its early years, ASP.NET was left far behind by PHP frameworks. Today full stack JavaScript frameworks have taken over.
PHP developers had more luck. Between 2000 and 2010 (about the same period of the rise and fall of ASP.NET and Windows Servers) a handful of PHP developer frameworks gained popularity amongst web developers (Zend, CakePHP, Symfony, Laravel, ...). They were there to stay. Everybody felt that. Unlike ASP.NET, these PHP frameworks were built with PHP, they did not want to replace it. And PHP ran on less expensive hosts and servers.
ASP/VBScript developers also needed a developer framework back in 2002. They did not need ASP.NET. A developer framework for ASP/VBScript should have taken care of various shortcomings in ASP/VBScript (better support for dealing with files - pdf, jpg, zip, etc), workarounds for known bugs or issues, facilitate code behind, become event-driven, bring a solution for the spaghetti-coding, improve coding habits, url-rewriting (MVC!), increase scalability and security. Last but not least, ASP/VBScript needed a framework in order not to reinvent the wheel each time a new project was to be developed. But that framework never happened.
By the time ASP.NET was stable (not before 2.0 in 2005), most talented developers had given up on .NET and went developing plug-ins for WordPress, Joomla or Drupal, or they developed one or the other social network. Using PHP. For me it was very simple: By then, I had 100s of customers who were using my Classic ASP applications. I built a hosting business around ASP/VBScript somewhere between 2000 and 2005. I did not want to refactor 10000s of lines of code just because MicroSoft wanted me to. So I got stuck with Classic ASP. And even today in 2024, I'm still hosting 100s of web applications solely relying on ASP/VBScript.
Classic ASP was considered sloppy, buggy and weak. A technology for lesser Gods.
At least that's what MicroSoft developers wanted us to believe back then. And I sure identify as a lesser developer God. But the Classic ASP community did have some individuals who did a great job with some very powerful scripts. I remember a pure ASP/VBScript upload class by Lewis Moten that I used a lot in QuickerSite and other projects. It got fine-tuned and Unicode-proof by other developers over the years. Others developed very useful encryption classes for Classic ASP (Sha256.asp by Phil Fresle in 2001). I also remember a very useful Captcha image generator-class by Emir Tüzül in 2006. Later on we also saw a few popular JSON-classes.
But the Classic ASP community also saw some great attempts to develop true frameworks. Michal Gabrukiewicz did a great job with asp-AJAXed but sadly passed away in 2009. More about Michal later. He played an important role when developing aspLite, even though he passed away 10 years before I started developing it. CLASP by Christian Caldereon was another great .NET-alike framework for Classic ASP. Others came up with Classic ASP MVC implementations, each one of them relying on the very useful 404-rewriting-trick. All in all, developer frameworks for ASP/VBScript developers did not last long and were often developed (and used) by a single developer. Apparently it was very hard to build a community around Classic ASP back then. I still blame MicroSoft. MicroSoft has given its own ASP/VBScript tandem very bad press back in those days. Nobody dared to stick his neck out by building a durable and community-driven ASP/VBScript framework back in 2003-10. I still regret that. We only needed 1 widely used Classic ASP (MVC) framework back in 2000. And we could and should have built it. But MicroSoft developers did not want us to have fun with that, they wanted to have fun themselves. So they created .NET.
No painless nor hassle-free upgrade-path to ASP.NET
Back in those days, ASP/VBScript developers did not have a painless nor hassle-free upgrade path to ASP.NET. VBScript was not supported by ASP.NET. Oh yes, for small web applications, one or two ASP pages, it was doable. MicroSoft even provided some automated conversion tools. But they were very limited. In 2003, by the time ASP.NET 1.1 fixed some very annoying bugs in 1.0, I was dealing with 3 extremely large ASP/VBScript code bases. Tons of includes files, classes, functions and routines. It was impossible to refactor them to ASP.NET without spending at least a year. And for what reason? ASP.NET did not offer much extra compared to ASP/VBScript at that time. In 2002, ASP.NET was often considered too little too late by seasoned MicroSoft developers. But what the heck, I didn't have the time for all that. I was building a business around my developing, selling and communication skills. And I really didn't need ASP.NET for that. Classic ASP/VBScript did it for me. Always has.
There was (and still is!) another very good reason to stay away from .NET back in 2002-2005! .NET web applications always needed much more RAM compared to Classic ASP apps. I mean: MUCH more RAM. An average Classic ASP application (still today) only needs a maximum of 30MB of RAM - that is - when done the aspLite-way (or MVC), using the one-entry-point method. Therefore it was perfectly possible to host between 50 and 100 Classic ASP applications on a single Windows Server with 4GB of RAM (a very common RAM-amount for production servers back in 2000). ASP.NET web applications easily need 10 times more. For a business owner this is a no-brainer. If you are/were into ASP.NET development, you need more and stronger hardware. The cost of building a SAAS hosting-business - like I have been doing all my life - is at least two/three times higher when using ASP.NET.
As soon as a technology gets introduced and gains a large user base - as was the case for ASP/VBScript between 1997 and 2002 - it's impossible to stop it. People will always be prepared to live with its limitations, work their way around them or learn to live with them. That's exactly what I did back then, and I'm still doing now with aspLite. And I'm not alone. It's a human thing. And we're all humans after all. MicroSoft misjudged that.
We're 25 years later now. There are still loads (millions) of Classic ASP/VBScript applications out there, most of them serving dynamic websites for more than 25 years now. Still MicroSoft refuses to clearly communicate about the EOL policy of Classic ASP. There IS no EOL policy for ASP. So after all these years, we - Classic ASP developers - are STILL left in the woods. Alone. Without even the slightest clue on when exactly MicroSoft will pull the plug.
I learned to live with that even though I still feel sad about it. In 2020 I decided to develop a new framework for ASP/VBScript developers: aspLite. I will be working on aspLite for the rest of my life. I somehow love this technology. And it just won't end. Classic ASP is fun! And fun is key.
Some kudos for MicroSoft though
Sure, it's not all bad news either. MicroSoft still offers Classic ASP through ISAPI in IIS and it made sure that all our ASP applications just kept on running smoothly like they did back in 2000. They even run better now, thanks to today's faster hardware. MicroSoft did not mess up Classic ASP. They did not add things to it, but they also did not screw things up. That's a plus.
We also saw some interesting attempts to provide an IDE for Classic ASP developers. We had two flavors of "Webmatrix", an all-in-one IDE for both MicroSoft and Open Source technologies. I liked both versions of Webmatrix a lot. But they both suddenly disappeared at some point in time.
So far, all Visual Studio editions supported intellisense and code-completion for Classic ASP. And even all IIS Express-versions support Classic ASP/VBScript to the bone. Today, even the Windows 10 and 11 Home editions come with a full version of IIS. In a way, nowadays it's much easier to start developing in ASP/VBScript than it was back then. You needed Windows 2000 Professional or a Server back in 2000. Today you only need to know how to enable ASP to get it up and running. Unfortunately, very few Windows users still know how to do that, and they couldn't care less. This illustrates how things turned out for MicroSoft. In 2000, companies spent quite some money on Windows 2000 Pro licenses. Today, MicroSoft ships its entire development framework and its dependencies for free. But even that never turned the tide.
Another pleasant evolution is that - over the years - MicroSoft embraced Open Source technology. At some point in time, it was easier to install both WordPress, Joomla and Drupal on any Windows host than it was to get ASP applications up and running. The wonderful Web Platform Installer however, was retired in 2022 - again - for inexplicable reasons. Probably because it didn't bring any money in and only pushed developers towards Open Source developer frameworks even more. I'm curious about how long Visual Studio Code will survive. The most popular plug-ins for VSC are about ... Python. Not quite a MicroSoft technology either.
The story behind aspLite tells the story of my career and the way I dived into web development about 24 years ago.
The early days
24 years ago - in 2000 - I was 28 years old. I was young and eager to learn to code. I had no degree in computer science whatsoever, but I picked up a lot from colleagues very quickly. Developing web applications quickly became an obsession. I developed all sorts of applications - both as a professional web developer, and for a hobby. Back in those days, I happened to work for a company that specialized in developing COM components for e-commerce websites. I was not part of the RnD team actually building these components. I was part of the support-team, implementing them for customers. We used Classic ASP and VBScript. What else did you think?
Visual Basic Scripting - VBScript
Rather than use the COM components made by that RnD team, I quickly realized that it was actually much easier (and much quicker) to develop custom classes in VBScript to fully meet the customer's requirements. Actually, using those COM components slowed down our applications and the development cycles. So in the end, we didn't use them. It somehow meant the end of that company. And I was in it for something... well, a lot actually. When I left that company in 2002, I took its biggest customer with me and started my own company. Shame on me. But hey... that's life. I was a 30 year old entrepreneur after all. I simply had to do it.
VBScript was the first programming language I learned to use. Full stack developers will claim that VBScript is useless and cannot be called a serious programming language. I disagree. VBScript is visual (easy to read/write, case-insensitive coding, no nested curly braces {{{{{}}}}} - I mean...), basic (easy to understand, no complex statements) and scripted (just-in-time, no compilation). These 3 properties made a huge success of VBScript back in 1997-2003. VBScript can also be used together with ActiveX Data Objects (ADO) - a high-level, easy-to-use interface to OLE databases (Access, SQL Server, Oracle, etc). ADO is what made VBScript a success. And it still does.
QuickerSite
After having coded my way through basically any type of web application somewhere between 2000 and 2007, I decided to come up with a CMS (Content Management System) that combined all my best scripts and coding habits that had passed the test of time so far: QuickerSite. QuickerSite was a success, especially in its early years. Only one year after its initial release in 2007, it was translated into 11 languages, including Danish, Hebrew, Italian, Turkish and Swedish. In 2008 QuickerSite was used by about 1000 users worldwide, who created at around 6000 QuickerSites in total. It was as if a lot of ASP coders all over the world had been waiting for a CMS developed in pure ASP/VBScript.
Between 2007 and 2014 I built a hosting business around QuickerSite. At its peak, I hosted 1200 QuickerSites on a single dedicated Dell server with very basic specs (3GB RAM, a slow 120 GB SATA disk and a single CPU). But it worked. It rocked. And all this time, I was a one-man-band. Nobody else but me, myself and I were dealing with everything related to my business: selling, developing, designing, hosting, invoicing, mailing. And everything else related to QuickerSite. So yes, I sure was a bit of a lone wolf back then. And as much as Microsoft forced me to switch over to ASP.NET, I did the exact opposite and stayed with Classic ASP. That's me.
Developing, selling, hosting and supporting QuickerSite for a wide variety of customers (and hosting conditions) thaugt me even more about ASP/VBScript, its caveats AND its strengths. Developing QuickerSite was no doubt the best time of my life. I still host many QuickerSites today on a Windows 2019 Server in the cloud. And I made some life-time friends.
The 2010 paradigm shift
By the time QuickerSite had grown mature - somewhere in 2010, there were quite a few things going on: HTML5, CSS3 and JavaScript frameworks got adopted by the WWW, mobile devices (phones and tablets) were rapidly gaining in popularity, social media took over our lives, and last but not least - by then - a large group of developers adopted open source solutions and frameworks developed in PHP/MySQL. Nowadays, JavaScript/CSS frameworks like Bootstrap (Twitter), React (Facebook), Angular (Google) and Node.js are completely dominating the web development business. While Microsoft was trying hard to keep up with these ever changing circumstances - by releasing dozens of ASP.NET versions and editions - Classic ASP developers were slowly and silently being ignored and left alone in the woods. Many of them are retired by now, or they are no longer actively developing (new) Classic ASP applications. I regret that a lot, because all this time - and still today - ASP/VBScript has been a perfect fit for these JavaScript frameworks, for instance by providing very straight-forward database access (ADO), dealing with binary files (uploading/streaming) or by simply delivering very useful AJAX, XML and JSON integrations. All that is where aspLite is about.
Trip down memory lane
When Jesse James Garret "invented" AJAX in 2005 - and Google made a success of it - some Classic ASP/VBScript developers must have thought by themselves: what the heck, we did AJAX back in 1998 already! We used something named Remote Scripting. It was AJAX avant-la-lettre. We loaded a Java Applet (it took about 3 seconds to load) and next we were able to provide AJAX functionality in pure Classic ASP/VBScript. Back in 1998. This technique worked fine on both Internet Explorer and Netscape, the only two browsers that mattered back then.
PHP and ASP.NET libraries in Classic ASP
As Classic ASP is a dead-end street anyway, it may be a good idea to do some neighborhood shopping. Why not use some PHP or ASP.NET libraries in Classic ASP? I've been doing that for many years already. I use .NET's web.config files to configure url rewriting (http->https), set custom error handling (404 catch) and set default documents (default.asp). I also developed a single VB.NET page that takes care of (unobtrusive) server-side image-resizing/cropping and recoloring. I added that aspx-page to the jpg-plugin in aspLite (jpeg.aspx). The idea of using resx extensions for HTML files is another (ab)use of .NET. All in all it's not much, but it is something.
In the past I have also successfully implemented PHP's built-in zipper (ZipArchive()) and a plugin named mPDF to create zip -and pdf files on the server from within a Classic ASP application. Worked like a charm. As these libraries reside on the server, they can safely be implemented with internal ServerHTTPRequests from a Classic ASP environment.
Concerns about the future of Classic ASP
Some developers may ask themselves: "For how long are we going to be able to host Classic ASP applications on Windows Servers"? Windows Server 2022 fully supports all Classic ASP/VBScript components. Windows Server 2025 will not remove support for ASP/VBScript either.
As for the end-of-life policy of ASP, MicroSoft has published this very short article on the 25th of January 2022. In short: both ASP and IIS support lifetimes are tied to the support lifecycle of the host operating system, in most cases a Windows Server. The support lifecycle for Windows 2022 is Oct 14 2031. The support lifecycle for Windows 2025 will probably be 3 years later, somewhere the end of 2034. In all honesty, I believe we will be able to host Classic ASP/VBScript web applications for at least 10 more years in optimal conditions (somewhere until 2035). After that, things might become tricky.
VBScript is about to be removed from Windows OS. Really?
In Oct 2023, MicroSoft announced that VBScript will retire. MicroSoft says: "VBScript will be available as a feature on demand before being retired in future Windows releases. Initially, the VBScript feature on demand will be pre installed to allow for uninterrupted use while you prepare for the retirement of VBScript."
This announcement is - again - very confusing. Will this also affect Classic ASP/VBScript web applications relying on server-side VBScript in IIS? Or will VBScript only be removed as a client-based scripting language in Windows Scripting Host? These are two completely different things. MicroSoft's communication is not very clear on this matter. First tests on early builds of Windows 11 however confirm that after VBScript's removal from Windows O/S, IIS no longer serves ASP pages using VBScript. A 500-error message is returned.
Suggestion: turn ASP/VBScript into Open Source
The best way forward for Classic ASP is to turn it into an open source project, pretty much like Microsoft did with ASP.NET Core. Given today's success of scripted languages like JavaScript, Ruby and PHP, VBScript still has the potential to reach a different kind of developer. Some developers (like me) will NEVER be able to read through dozens of nested curly braces. It's just visually too poor for me. But that does not mean we are unable to write poetry in ASP/VBScript. I'll keep on trying anyway.
I often receive messages like the ones below. It's striking how many of us Classic ASP developers share the same feeling. Left alone in the woods with our ASP-scripts and functions that passed the test of time (some of them are working flawlessly for 25 years now, on all Windows Servers).
"I am moved by your back stories with the development of aspLite. I have been an ASP web developer since 2004 and still using asp Classic as for my freelance projects. I can relate on the struggle on having shared hosting upto know and I would love to learn more about AWS EC2 instance and own a hosting server like what you did. I'm happy to know that there are still believing in the power of Classic ASP."
"I like your work! Contrary to what the muppets say, Classic ASP is not dead. There is nothing that one cannot do with ASP. In fact I have written desktop applications in ASP... cannot do that with PHP. You may recall SOOP? It had a huge following and a lot of devs for a lot of useful plug-ins. Not sure what happened with that even though I was one of their plugin devs. Perhaps it was the cost of web hosting vs Apache servers and WordPress. Good luck with your CMS."
"I've just found your web site for the first time today and I just wanted to say thank you for doing it. I've been using Classic ASP since the late 90s and still use it today for projects even though I've been jeered and sneered at for doing so. It's great to see there are other people still out there using it for real and still flying the flag :0)"
"I found your page by chance, because I was looking for something in ASP that would work with the "DataTables | Table plugin for jQuery"... congratulations for maintaining this site. Hope you are still working with ASP...thanks."
Out of all the emails I have ever received, I will always remember this one. It's about QuickerSite, but it also applies to Classic ASP/VBScript... in a way:
"Pieter, many years ago I came across Quickersite and I paid I think for something (I hope I was a payer and not just a downloader) and I was overwhelmed with how amazing it all was. I thought I'd found the holy grail for me being a provider of web services... but my history has followed a similar trajectory to QS and I'm now wandering the plains with a lot of other buffaloes wondering what to do next. I read your posts occasionally as they are, and for a weird reason - it gives me hope. Hope because what you had going here was SO ON THE MARK. But the world rotated a little in another direction and twitter/FB / Instablah and all the other mindless platforms took the collective consciousness away from individual effort and relevant opinion, such that QS no longer had a home. ... Why? Who knows, the world is just different now and windows of opportunity open and close for the most inexplicable of reasons. This could of course be used as an equal answer to: "Why does humanity exist"? A: "For inexplicable reasons". So; no more Concorde, no more digital watches, no more moon landings and no more QS. It is in good company and I loved it."
IDE
Classic ASP developers have been lacking a dedicated IDE (Integrated Development Environment) for some time now. I therefore prefer to use Notepad++. It's free, lightning fast, reliable and it's very easy to use. Notepad++ also comes with some basic code-completion and intellisense for ASP. Notepad++ can also be freely installed and used on any Windows Server. I often use it to search for specific texts and strings in over 1 million files. No problem at all. Others may want to use Visual Studio Code instead.
Visual Studio Code is MicroSoft's Open Source and free code-editor. Its community is offering some very useful extensions for Classic ASP/VBScript developers. They usually include syntax highlighting, intellisense, and code navigation for VBScript inside Active Server Pages (ASP) files. When combined with one of the available IIS Express Extensions, Visual Studio Code can be considered a useful IDE (Integrated development environment) for Classic ASP development.
Where to host ASP sites today?
A final note on hosting. The vast majority of large hosting companies that offer shared hosting has given up on MicroSoft Servers and technologies many years ago. They no longer even offer ASP(.NET) shared hosting or they are not clearly communicating about it. Shared hosting is all about one-click WordPress installations these days. You can barely find Windows-based shared hosting solutions and the ones you find are often expensive and badly supported. I have personally never used or liked shared ASP hosting solutions of any type. From day one, I use my own server. In 2004 I bought my own. In 2010 I migrated to the cloud. Today (2024), I use an AWS EC2 instance. Very satisfying so far. Windows Servers got much cheaper over the years. You get a lot of "Windows Server" for somewhere 100$/month these days, including backups, data transfer and lots of disk space. In my opinion, if you're into ASP development, you're better off managing a Windows Server yourself, rather than rely on shared hosting. There are very few people left who can assist you with ASP hosting issues. We're on our own. But that shouldn't be a problem as Windows Servers are very easy to set up, deploy and maintain. As an ASP developer you need to know the basics of setting up backups, firewall, IIS, mail server and security on Windows Servers after all. On top of that, when you dive into ASP development, you may have to install specific COM software or you may want to prefer a specific setup to facilitate code-reuse. This requires full access to a server. Both Microsoft Azure, Google Cloud and Amazon Web Services offer free-tier solutions that allow you to test-drive a basic setup for a year. It's really worthwhile looking into these solutions.
Should I learn Classic ASP/VBScript in 2024?
Hell no.
If you're young and you're willing or able to dive into new technologies, do not learn Classic ASP or VBScript. If you want to become a web developer (by extension a mobile app developer) - today or in the future - learn Python and/or JavaScript. However, if you are already using Classic ASP (and plan to keep on doing so), it's always a good idea to learn new things. Even after all these years, there's always something new to learn about ASP. aspLite is living proof of that.
That said, learning ASP actually means: learn VBScript! There is not much to learn about Classic ASP after all, except for 6 objects with each a handful of properties and methods: Application, Session, Response, Request, Error and Server. VBScript however is the preferred (and default) programming language for most ASP developers. Even though VBScript is no longer being developed by Microsoft, it is still used a lot, not only by web developers, but also to automate tasks on Windows servers.
Out of many online resources you find when searching for "learn Classic ASP", I personally liked 'A Practical Guide to Microsoft Active Server Pages 3.0' by Manas Tungare a lot. He was so kind to allow me to redistribute this guide on this website. These 70 well written pages are about everything you need to get started using Classic ASP and VBScript.
Furthermore, W3Schools.com (that website is developed in Classic ASP) is probably the best online resource to start as a Classic ASP/VBScript web developer. Before you learn Classic ASP, you must learn HTML and CSS (and grab some basic JavaScript as well). Make sure to also check their very complete VBScript and ADO reference. ADO can be used to access databases from Classic ASP pages.24 years ago, Classic ASP was about Request() and Response(). Ins and outs. Simply put.
Today, any server-side web framework is still about just that.
For everything else you can - in 2024 - rely on JavaScript frameworks. No more need for spaghetti-ASP or HTML server-controls.
aspLite is a lightweight framework for ASP/VBScript developers. It can help to develop better ASP/VBScript applications. aspLite does not reinvent, replace or rewrite ASP components. It only tries to come up with a new way of using some of them.
ASP/VBScript (also known as "Classic ASP") has been available for more than 25 years now and I believe it still makes sense to share this approach. After all, Classic ASP has proven to be reliable, fast and secure.
But that's not all. Today's web applications use JavaScript frameworks (Angular, Vue, React, jQuery, etc). Often only data (JSON) is transmitted back and forth to the server. That's why a server-side technology better stays small, fast and simple. Hence the name "aspLite".
An example. The aspLite demo site ships with a (fully functional) Classic ASP implementation of DataTables. This wonderful (free-to-use) widget has all it takes to offer ... datatables, including client-side sorting, searching and paging. There is only very little ASP code involved. Quite fascinating!
The vast majority of these JavaScript plug-ins are free to use, backed by 100's of contributors and they work in all (current) browsers, on all devices. How amazing is that?! Ever since the adoption of HTML5, CSS3 and ECMAScript 5 (somewhere between 2009 and 2012), client-side JS/CSS/HTML frameworks have become very popular. Much more popular than their server-sided predecessors (ASP(.NET), PHP, etc). Today, the most starred repositories on GitHub are all about (learning) JavaScript.
The aspLite demo puts the following front-end HTML/CSS/JS frameworks/plug-ins at work: Bootstrap, jQuery, jQuery UI Datepicker, JSZip, SheetJS, jsPDF, CkEditor, CodeMirror and DataTables.
aspLite does not rely on 3rd party COM components. It only relies on the built-in VBScript components. Therefore aspLite works on basically each and every shared hosting solution out there, but also on each and every Windows host with ASP installed.
If you're lucky enough to manage a Windows Server yourself, you may want to install aspLite as a virtual directory in your IIS website. This way you can use one aspLite codebase on an unlimited number of websites on your server. If you use a network location, you can even share one aspLite codebase amongst multiple servers.
When setup this way, the first line in your ASP page would read:
<!-- #include virtual="aspLite/aspLite.asp"-->
(in case you name the virtual directory "aspLite")
In its most low-level mode, aspLite is nothing more (or less) than a library of ASP/VBScript classes, functions and subroutines. They can be found in /aspLite/aspLite.asp. I will go through all of them later on in this article.
This page - ebook.asp - you're looking at, includes the aspLite framework in it's very first line:
<!-- #include file="aspLite/aspLite.asp"-->
Let's have a closer look at aspLite/aspLite.asp. The first line of that ASP page reads:
<@LANGUAGE="VBScript" CODEPAGE="65001"%>
That's probably the most important line in the entire aspLite code base. What is it doing?
The second line in aspLite/aspLite.asp reads:
Option Explicit
I assume you know that by having this line as the very first line in your ASP script, you are forced to declare variables and you're not allowed to declare them more than once. Even though it helps to keep the risks of overwriting certain values under control, it is not a 100% guarantee. Especially when using VBScript's Execute and ExecuteGlobal statements, Option Explicit does not have any effect. So be careful. Make a very good deal with yourself: always declare variables and keep their naming logical and consistent. That's harder than it sounds. Even though in theory you can declare (dim) the same variables (i, j, rs, counter,... are amongst the more popular variable names) in each and every class, function and sub, they DO overwrite each other. That's no doubt one of the reasons "real" developers never liked VBScript. You never really knew what value (and what type) a VBScript variable held. And if you're not used to that, I can imagine this is driving a developer crazy.
Right after Option Explicit, 2 ASP files are included.
The first one:
<!-- #include file="config.asp"-->
const aspL_path="aspLite"
lets you decide where exactly you want the aspLite "engine" in your application.
const aspL_debug=true
lets you decide whether or not aspLite throws errors. I personally always keep this "true".
The next include file is:
<!-- #include file="aspForm.asp"-->
That file holds a class, but it is not instantiated. Let's not go into detail right now. Bottom line: it's doing nothing. For now.
The next (and last) thing that aspLite/aspLite.asp does is create an instance of aspLite (cls_aspLite):
dim aspL
set aspL=new cls_aspLite
dim aspL : set aspL=new cls_aspLite
In ASP/VBScript, you can separate commands with a colon. That would have saved one line of code.
When creating an instance of a VBScript class, the Private Sub Class_Initialize() gets executed (if any). Always. And before anything else.
Every line in Class_Initialize gets executed every time an instance of cls_aspLite instantiates. So we better think twice before we add an infinite loop over there. Ok, bad joke. But each and every line in Class_Initialize is thoughtfully written.
class cls_asplite 'VERSION: 1.0 private debug,startTime,stopTime,plugins,p_fso,cacheprefix private multipart,p_json,p_randomizer,p_formmessages,p_value Private Sub Class_Initialize() on error resume next startTime = Timer() debug = aspL_debug '------------------------------------------- Response.Buffer = true 'next line not work on IIS5 (Windows 2000), comment it out for IIS5 Response.CharSet = "utf-8" Response.ContentType = "text/html" Response.CacheControl = "no-cache" Response.AddHeader "pragma", "no-cache" Response.Expires = -1 Response.ExpiresAbsolute = Now()-1 Server.ScriptTimeout = 3600 '------------------------------------------- 'check if a form with enctype="multipart/form-data" was submitted. 'in that case, the request(.form) collection cannot be called 'as it throws an error 'this is important for getRequest() -> see below If InStr(Request.ServerVariables("HTTP_CONTENT_TYPE"), "multipart")<>0 Then multipart=true else multipart=false end if cacheprefix="asplite_" set plugins = nothing set p_fso = nothing set p_json = nothing set p_randomizer = nothing set p_formmessages = nothing on error goto 0 End Sub ...
As we're using Option Explicit, we're forced - here also - to declare all variable names. In case of a class - rather than "dim" a variable (even though that also "works" in a way), you can use the private or public keyword. Private variables are available within the class only. Public variables (and their values) can be exposed to the outside world. When working alone on apps, you actually don't really need private values. But as aspLite might at some point in time be used by another developer, I guess I can better do it right, and keep most variables in the aspLite class private.
As the code in Class_Initialize is always executed when an instance of cls_aspLite is created, let's have a close look at what happens, line by line.
on error resume next
This basically tells the ASP-compiler to continue processing the lines below, even in case an error is thrown. But you knew that already, didn't you? What you also have to know is that this statement needs to be repeated in each and every function or sub. In essence, this "resume next" will be reset at the end of Class_Initialize. In the next function, snippet or sub, the ASP compiler will - again - stop executing the script in case an error is thrown. Good to know I guess.
startTime = Timer()
Just because we can and for the fun of it, aspLite holds a timer. startTime will hold the start-time of the script. Let's do this. So far, this page took 0 milliseconds to load. That's not much. Having this aspl.printTimer
at your fingertips, can help you to isolate badly written code or isolate code that really runs too slow.
debug = aspL_debug
The value for aspL_debug was set in <!-- #include file="config.asp"-->
.
Response.Buffer = true
Honestly, this is questionable, again. True is the default value anyway. For a reason. If you need to empty the buffer before the ASP script has completely been executed, you can use response.flush and response.clear as often as you wish. As Response.buffer=true is the default value, this line could have been skipped.
Response.CharSet = "utf-8"
Questionable. We already made sure utf-8 is our default charset by adding CODEPAGE="65001" in the first line of aspLite/aspLite.asp. As the VBScript comments indicate, this line does not work in IIS5 (Windows 2000 Servers). Hence the "On error resume next" above.
Response.ContentType = "text/html"
99% of the output of a web application consists of text/html. So it's the default content type. It can easily be overwritten if needed though. More about that when discussing the file-serving capabilities of aspLite.
These next four lines ensure that browsers do not cache any output by any ASP page in our project. This is crucial. Back in the late 90s, browser caching was considered useful, as internet connections were slow. Today, you really do not want browsers to cache anything, except the things you really want them to cache (cookies, localStorage, etc).
Response.CacheControl = "no-cache" Response.AddHeader "pragma", "no-cache" Response.Expires = -1 Response.ExpiresAbsolute = Now()-1
Server.ScriptTimeout = 3600
I agree, this is quite a tolerant value. Our ASP scripts can run for an hour before timing out. Never ever let an ASP page run for an hour. But in some very rare cases, you may have no other option, like when dealing with large file-transfers or occasional migrations or synchronizations.
The next few lines may sound weird. But they are a crucial part of how aspLite deals with the ASP Request object. In case files are uploaded through a web form, the generic ASP Request collection cannot be used and it even throws an error when called. That's what this little check tries to cover. More about it later.
'check if a form with enctype="multipart/form-data" was submitted. 'in that case, the request(.form) collection cannot be called 'as it throws an error 'this is important for getRequest() -> see below If InStr(Request.ServerVariables("HTTP_CONTENT_TYPE"), "multipart")<>0 Then multipart=true else multipart=false end if
aspLite comes with an ASP Application-based caching system. Application caching is one of the most underestimated features of Classic ASP. PHP never had a similar function. I have successfully used the ASP Application to store lots and lots (1000's) of values, often in Arrays. Very powerful. Here we set the prefix, so that aspLite will never interfere with yet another caching routine in your (existing) solution.
cacheprefix="aspLite_"
aspLite includes some collections (VBScript dictionaries) and instances of other classes. To be able to check whether they are "nothing", they are set to "nothing" to begin with. It's a trick but very effective.
set plugins = nothing set p_fso = nothing set p_JSON = nothing set p_randomizer = nothing set p_formmessages = nothing
on error goto 0
This is technically not needed, as we're at the end of the sub anyway. After this, the On Error Resume Next will not have any effect anymore. However, I prefer to clear possible errors before continuing. That's what this line is for. It "wipes" the VBScript Err-object.
That was it. This is where we can start using aspLite.
In its most minimalistic mode, this is it. We now have the timer, the charset, the content type, the script timeout, the caching and the buffering all set. Good to go. Right?
In a way, yes indeed. These are more than enough reasons to use aspLite for any future ASP/VBScript project.
However, there is much more to aspLite than just some settings. The very minimal use of aspLite somehow assumes you know about the following aspLite methods. These are some basic aspLite functions that replace or extend some built-in ASP/VBScript functions. In most cases they relate to the major issue the null-value comes with. Nearly all built-in ASP/VBScript functions throw an error when used with null.
Executes the content of a Unicode file (relative filepath) or in case of aspl.executeASP - plain text - as ASP/VBScript. After the ASP code has been loaded, it is available in the namespace of the ASP script.
aspLite heavily relies and facilitates VBScript's executeGlobal function. In short: executeGlobal allows to "import" ASP code in an ASP page's namespace. The "imported" code does not even have to reside on the same server. It can be located anywhere on the internet. ExecuteGlobal imports any text and treats it as ASP code.
We all know the idea behind include-files, but executeGlobal somehow includes ASP code the "extreme" way.
An example:
<!-- #include file="aspLite/aspLite.asp"--> <% dim hw : hw="https://demo.aspLite.com/ebook/helloworld.txt" aspl.executeASP(aspl.XMLhttp(hw,false)) %>
That file does actually exist and serves some valid ASP code. aspLite loads the file and executes it. I'm not sure MicroSoft ever intended to use executeGlobal this way. But it works. I'm sure you can see lots of great opportunities, but also lots of extremely dangerous scenarios.
Live example:
Hello World
Another example. Imagine you have this default.asp:
<!-- #include file="aspLite/aspLite.asp"--> <% aspl.exec("scripts/" & aspl.getRequest("script") & ".inc") %>
The folder "scripts" holds 3 files:
response.write "hello1"
response.write "hello2"
response.write "hello3"
Finally, browse to:
The one and only default.asp runs 3 different "imported" scripts. Unlike when using include-directives, the imported ASP code is dynamically loaded (or not). You decide which ASP script has to load and which does not. This keeps RAM usage under control. But above all, it facilitates an amazingly well structured ASP code base. For each event you can have your dedicated classes, functions, subs and program flow. And still you would only be using only one ASP page (default.asp) as your main entry-point. That one ASP page is able to serve each and every module of your ASP application. This begins to smell like MVC.
CDN for Classic ASP/VBScript classes?
The Hello-World script above (https://demo.aspLite.com/ebook/helloworld.txt) could have led - back in 2000 - to another idea. But it didn't. Anyway, the idea would be to set up a CDN (Content Delivery Network) serving well-written ASP-classes. Pretty much like JavaScript frameworks rely on CDN, Classic ASP/VBScript CDN could have been set up in a very similar way. It would not be the browser loading a CDN file, but the server loading full-blown (compressed) classes to deal with (less) common tasks and functions. THAT's what we needed in 2002. We did not need ASP.NET. Also, this CDN would have been a major step towards Open Source. Still today, it would be a great help for Classic ASP developers. We don't even need a hosting solution. All we need is a couple of megabytes of cloud storage. This idea of CDN for Classic ASP goes beyond the scope of this book. It's something worth experimenting with however.
Be careful with aspl.exec() and aspl.executeASP().
Dynamically importing ASP code with aspl.exec or aspl.executeASP is very powerful. It can be used to create plug-ins, import remote code, keep your codebase strictly structured, etc. Unfortunately there is no way to trace errors in ASP code that is imported this way. Using regular include files will return appropriate error messages like we're used to and we would always need to during development. The underlying VBScript function executeGlobal
is to blame. Make sure to use aspl.exec
and aspl.executeASP
with care and only for small ASP snippets or well-tested ASP code.
aspl.getRequest(value)
replaces the generic ASP Request object. Unlike the built-in ASP Request Object, it does not throw an error in case a file was uploaded through a form. Pretty much like in the original ASP Request Object, there is a sort order of what exactly is returned: first the form, next the querystring and finally cookies if any.
Unlike server.htmlEncode(value), aspl.htmlEncode
does not throw an error when value is null.
Protects your SQL queries against SQLinjection. In essence, single quotes in value are replaced with two single quotes.
aspl.isEmpty(value)
returns true in case value is null, empty or contains blank spaces only. Unlike VBScript's isEmpty(value), aspl.isEmpty(value) does not throw an error when value is null.
aspl.isNumber(value)
returns true in case value is a number. Unlike VBScript's isNumeric(value), aspl.isNumber(value) does not throw an error when value is null (but returns false in this case).
aspl.length(value)
returns the length of a value. Unlike VBScript's len(value), aspl.length(value) does not throw an error when value is null (but returns 0 in this case).
aspl.convertStr(value)
converts value to a string. Unlike VBScript's cstr(value), aspl.convertStr(value) does not throw an error when value is null (but returns an empty string in this case).
aspl.convertNmbr(value)
converts value to a number. In case value is null or a string, 0 is returned.
aspl.convertBool(value)
returns a boolean (true/false). Unlike VBScript's cbool(value), aspl.convertBool(value) does not throw an error when value is null (but returns false in this case). aspl.convertBool returns true when value is "true", true or 1. In all other cases convertBool returns false.
aspl.convertNull(value)
converts value to null in case it is 0, empty or null. In case value holds any other number (>0) a number is returned. In case value is a string, a string is returned.
aspl.convertHtmlDate(date)
returns a date in the following format: yyyy-mm-dd (needed for the HTML5 date field).
Example:
<input type="date" value=<%=aspl.convertHtmlDate(date)%> />
returns:
aspl.pCase(value)
converts value to proper case, in addition to VBScript's lCase and uCase functions.
aspl.curPageURL
returns the url of the current ASP script, including the protocol, server name and script name.
aspl.fso
returns an instance of the VBScript FileSystem Object
aspl.dict
returns an instance of the VBScript Dictionary Object
aspl.recordset
returns a disconnected adodb.recordset
aspl.checkEmailSyntax
checks the syntax of an email address - returns true/false
Strips the HTML tags from value
Example:
<%=aspl.stripHTML("<u>not underlined</u>")%>
returns:
not underlined
Adds paddingChar left to value until totalLength is reached.
Example:
<%=aspl.padLeft("5",3,"0")%>
returns:
005
Adds paddingChar right to value until totalLength is reached.
Example:
<%=aspl.padRight("5",3,"0")%>
returns:
500
Returns the file extension for a given filename.
Example:
<%=aspl.getFileType("photo.jpeg")%>
returns:
jpeg
aspLite comes with a basic logger: aspl.log("anything")
will write "anything" to aspLite/aspLite.log. The exact time of the logging is included as well. This logging feature is very useful as you can always tell what exactly happens to a variable, or when things go wrong. I often use it to debug certain functions.
Resets an ASP application by opening, saving and closing the global.asa-file.
Randomizer class with 3 functions:
aspl.randomizer.randomText(nmbrchars)
returns a random string with a given nmbrcharsaspl.randomizer.randomNumber(start,stop)
returns a random number between start and stopaspl.randomizer.createGUID(length)
returns a GUID of a given lengthRemoves all cookies
Returns the actual execution time of your ASP script.
Sends value to the browser as text/html. aspLite destroys itself and all its plug-ins right after. In case value includes [aspLite_executionTime], the actual execution time will be added to the output as an HTML comment.
ASP comes with a very interesting object, the Application Object. It was designed to store some application-wide values. In most cases, ASP developers only used it very minimally. They typically stored only very few strings and numbers in the Application Object. However, it was designed to be used a lot heavier than that. It can easily store 10000s of values. When pumped with Arrays, the Application Object is nothing less but a very powerful dictionary or collection object. That's why I built a very tiny layer around it in aspLite.
aspl.setcache(name,value)
stores an array in the Application Object. Not only the name and the value are stored, but also the exact time. Remember the cacheprefix above? It helps not to interfere with other Application variables you may already use in your ASP script.
aspl.getcache(name)
returns the Application value for a given name.
aspl.getcacheT(name,seconds)
returns the Application value for a given name only if it was stored less than x seconds ago.
aspl.clearAllCache()
clears all Application values for your cacheprefix.
For years I have assumed that ASP/VBScript was not capable of dealing with (large) binary files (upload, read, write, save, download). At least, that was, without expensive third party COM components. I was wrong all this time. Lewis Moten once created a purely scripted (and free-to-use) ASP/VBScript Upload class. It did and still does a very good job.
While the VBScript FileSystemObject is needed to browse files and folders, aspLite uses ADODB.Stream to read, write and save files, both binaries and texts. The FileSystemObject does not support binary files and shows major issues when dealing with the UTF-8 (Unicode) charset. ADODB.Stream however, perfectly handles binaries and Unicode characters.
Returns the content of a text file. Path is the relative path to a text-file.
aspl.loadText(path)
is a great help when separating ASP-code from HTML, thus preventing the so-called spaghetti-ASP-way of coding. You would typically want to isolate all html blocks and snippets in a specific folder and dynamically include them in your ASP script when needed.
Example:
In the root of your application, you have "default.asp".
<!-- #include file="aspLite/aspLite.asp"--> <% dim html : html=aspl.loadText("html/default.inc") html=replace(html,"[titletag]","My first aspLite page",1,-1,1) html=replace(html,"[body]","Hello World",1,-1,1) aspl.dump(html) %>
The folder "html" holds default.inc:
<!DOCTYPE html> <html> <head> <title>[TITLETAG]</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <meta name="theme-color" content="#712cf9"> </head> <body> [BODY] </body> </html>
Congratulations! You just wrote your first code-behind ASP file and your first Classic ASP MasterPage.
.inc is a good choice. I also use .resx. Both file types are not served by IIS. In other words, they will never be executed by IIS and they will never be sent nor exposed to the browser. Many other formats will either be sent or executed: .asp, .htm(l), .txt. Be careful with them.
Saves text (data) to a file (relative path). Unlike the FileSystemObject, this one is 100% UTF-8-proof.
<!-- #include file="aspLite/aspLite.asp"--> <% aspl.saveText "html/textfile.txt", "This is the content of the file." %>
Sends text-files (only) to the browser (with a download-prompt). Filename is the default filename, filebody is the content of the file.
At first sight, this is not very useful, as you can send any text to the browser already. However, in some cases, you may want to send text-files (.txt, .html, .css, .js, etc), rather than just text. After showing the download-prompt, they will then end up in the visitor's default download-folder.
<!-- #include file="aspLite/aspLite.asp"--> <% aspl.saveAsFile "textfile.txt", "This is the content of the file." %>
Returns the content of a binary file (an image, pdf, zip or any other non-text file). Filename is the relative path to a binary file.
As Classic ASP/VBScript does not have many file-editing options, there is not much use for loading binary files, other than using a lot of RAM in case of large files.
Except perhaps when combined with another aspLite function:
Saves a given binary file to filename (absolute path!)
The example below copies html/sample.jpg to html/copy.jpg.
<!-- #include file="aspLite/aspLite.asp"--> <% dim file : file=aspl.loadBinary("html/sample.jpg") aspl.saveBinaryData server.mappath("html/copy.jpg"), file %>
Another use I can think of is to copy a file from anywhere to your local drive. The example below downloads (GET) the JPG file and saves it to your local html folder.
<!-- #include file="aspLite/aspLite.asp"--> <% dim oXMLHTTP : set oXMLHTTP = Server.CreateObject("MsXML2.ServerXMLHTTP") oXMLHTTP.open "GET", "https://demo.aspLite.com/default/html/smallfile.jpg" oXMLHTTP.send aspl.SaveBinaryData server.mappath("html/smallfile.jpg"),oXMLHTTP.responseBody %>
Sends a binary file (relative path) to the browser, asking the user to download the given file as "dumpAs". dumpAs would typically be the filename with the correct extension (jpg,zip,pdf, etc).
The example below offers a picture of a Suzuki Samurai for download.
<!-- #include file="aspLite/aspLite.asp"--> <% aspl.dumpBinary "html/sample.jpg", "Suzuki Samurai.jpg" %>
Returns custom paths in case a 404-redirect is set up in web.config.
For this to work fine, you need the following web.config file in the root of your application:
<?XML version="1.0" encoding="UTF-8"?> <configuration> <system.webServer> <!-- set to <httpErrors> while developing and when facing errors --> <httpErrors errorMode="Custom"> <remove statusCode="404" subStatusCode="-1" /> <!-- change to the asp-file of your choice - most likely /default.asp--> <error statusCode="404" path="/default.asp" responseMode="ExecuteURL" /> </httpErrors> </system.webServer> </configuration>
This tells IIS to execute /default.asp in case a 404-error (file not found) is thrown by IIS. Luckily, the missing filename is passed on via Request.ServerVariables("query_string"). That's where aspLite.pathinfo
grabs the missing file or folder name.
The aspLite demo site comes with some examples:
https://demo.aspLite.com/about points to a non-existing folder. aspl.pathinfo
returns "about" in this case. From there you can easily serve specific content for the "about" page.
This technique of executing scripts in case of a 404 is available from Windows 2000 (IIS5) onwards. It still works on Windows 2022 servers. But it was never documented or approved by MicroSoft as a valid method. That's a shame, because all this time Classic ASP developers could have made use of this technique to offer user-friendly and SEO-optimized urls. I used this technique in QuickerSite from day one. This has meant a big deal and it has drastically improved SEO for all my hosted Classic ASP customers.
ASP ships with some very useful http-callers. In short: from within your ASP/VBScript application, you can call basically any other url on the internet, post to any form out there, next wait for its response, and use that output in your application. The output can be text/html, XML or again, pure binary content. I have often used this technique to read RSS-files (typically done with JavaScript today) and copy entire folders recursively from one server to the other (or to any localhost). These ASP functions are also often used to synchronize data between applications via web services.
Another way to look at these two functions is as an Emergency Exit for ASP/VBScript developers. They can be used to organize excursions out of ASP/VBScript and implement PHP or .NET functionalities that are not (easily) available for Classic ASP developers. The technique I'm referring to is to load specific PHP or ASP.NET pages and have them supercharge your ASP script (create images, zip files, pdf files, etc).
Returns the output of any url - whether or not binary (true/false).
Let's revisit one of the above examples. The line below downloads a JPG file from demo.aspLite.com and saves it to your local html folder. All in one line of ASP code.
<!-- #include file="aspLite/aspLite.asp"--> <% dim file file="https://demo.aspLite.com/default/html/smallfile.jpg" aspl.SaveBinaryData server.mappath("html/smallfile.jpg"),aspl.XMLhttp(file,true) %>
Returns an XML document (url) you can list all elements for. Today's web developers would prefer JSON data to XML. More about JSON and aspLite later.
Be careful, XMLhttp and XMLdom perform synchronous requests
Unlike AJAX calls in browsers, these two server-side http-request calls perform synchronous calls. That means that ASP waits for the output of the loaded url before it continues to execute the code below. Therefore, you have to be careful not to load urls that take a long time to execute. That would cause your ASP page to hang.
An example. The code below shows the 6 latest news items from NYTimes' world-stories-RSS: https://rss.nytimes.com/services/xml/rss/nyt/World.xml. The RSS is loaded via aspL.XMLdom.
<% dim XMLDOM : set XMLDOM=aspL.XMLdom("https://rss.nytimes.com/services/xml/rss/nyt/World.xml") dim feeditems : set feeditems=XMLDOM.getElementsByTagName("item") dim i, item, child dim template, news for i=0 to feeditems.length-1 template="<li>[date]: <a target=""_blank"" href=""[link]"">[text]</a></li>" set item=feeditems(i) for each child in item.childNodes select case lcase(child.nodename) case "title" : template=replace(template,"[text]",aspl.htmlencode(child.text),1,-1,1) case "pubdate" : template=replace(template,"[date]",aspl.htmlencode(child.text),1,-1,1) case "link" : template=replace(template,"[link]",aspl.htmlencode(child.text),1,-1,1) end select next news=news & template if i=5 then exit for next response.write "<ul>" & news & "</ul>" %>
Live example:
Summary
aspLite comes with quite some powerful functions to deal with text files, binaries, whether they reside on your server or anywhere on the internet. Unlike when using VBScript's FileSystemObject, aspLite ensures that any text converts UTF-8-proof.
Classic ASP's serverhttp-requests can open up a whole new world when integrating with other web-technologies and services.
When I was developing aspLite during the first weeks of the Covid19 lockdown, I quickly realized aspLite would soon lead to an AJAX formbuilder.
So far aspLite did not rely on any JavaScript framework. And if you don't plan to use asplForm, you don't need jQuery nor any other JavaScript to use aspLite.
asplForm was the first plugin I developed for aspLite. asplForm needs aspLite.js in the browser. aspLite.js relies on jQuery. That makes aspLite a full stack developer framework. Classic ASP developers always were full stack anyway. They needed HTML, CSS and JavaScript for the front and ASP, VBScript, SQL in the back. On top of that, most Classic ASP developers had to deal with IIS administration, SQL Server management, setting up ftp-servers, mail servers, firewalls, backup solutions, Windows server security, etc. That's what I like a lot about being a Classic ASP developer. It's a colorful job with a lot of diversity and complexity. Classic ASP developers were not only full stack, they were often one man bands, taking care of basically everything that related to building applications for their customers.
What is asplForm exactly?
asplForm facilitates AJAX web forms. asplForm collects form-fields (VBScript dictionaries), sends them to the browser in JSON-format, and aspLite.js converts it to HTML5 web forms. asplForms bind to any HTML element, regular DIV's in most cases. This technique is the heart of the aspLite framework. Again, asplForm is not only relying on pure Classic ASP/VBScript. There is a very important role for JavaScript when converting server-generated JSON to HTML controls.
Returns an instance of an asplForm. Example: dim form : set form=aspl.form
Adds value as plain text/html to the JSON output of the form.
Adds value as JavaScript to the JSON output of the form.
Builds the form, including all its fields and next stops executing the ASP script.
An example. Create a file named default.asp in the root of your aspLite application:
<!-- #include file="asplite/asplite.asp"--> <% dim form : set form=aspl.form select case lcase(aspl.getRequest("asplEvent")) case "myheader" : form.write "This is the header" : form.build case "myarticle" : form.write "This is the article" : form.build case "myfooter" : form.write "This is the footer" : form.build end select %> <!DOCTYPE html> <html> <head> <title>asplForm Demo</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <!-- jQuery & aspLite JS --> <script src="asplite/jquery.js"></script> <script>var aspLiteAjaxHandler='default.asp'</script> <script src="asplite/asplite.js"></script> </head> <body> <header id="myheader" class="asplForm"></header> <main> <article id="myarticle" class="asplForm"></article> </main> <footer id="myfooter" class="asplForm"></footer> </body> </html>
If everything goes well, you end up with something like this in your browser:
This is the header This is the article This is the footer
When a browser loads default.asp for the first time, it basically loads all HTML, CSS and JavaScript. As soon as they're loaded, asplite.js kicks in. Asplite.js will perform AJAX calls for all HTML elements that have the class "asplForm". These AJAX calls look like this: default.asp?asplEvent=myheader, default.asp?asplEvent=myarticle and default.asp?asplEvent=myfooter. Default.asp deals with these "events" in the select case-statement.
Default.asp is loaded 4 times in total. The first time to load all HTML, CSS and JavaScript, the next 3 times to load specific content for specific (onload) events.
Let's have a closer look at the JSON generated by default.asp?asplEvent=myheader:
{ "target":"myheader", "offSet":150, "className":"", "doScroll":false, "id":"myheader_aspForm", "onSubmit":"aspAjax('POST',aspLiteAjaxHandler,$(this).serialize(),aspForm);return false;", "executionTime":"31ms", "bShowToasts":false, "aspForm":[ { "type":"hidden", "name":"asplPostBack", "value":"true", "noinit":"true" }, { "type":"hidden", "name":"asplSessionId", "value":"479394439", "noinit":"true" }, { "type":"hidden", "name":"asplTarget", "value":"myheader", "noinit":"true" }, { "type":"hidden", "name":"asplEvent", "value":"myheader", "noinit":"true" }, { "type":"plain", "html":"this is the header" } ] }
target
: tells which HTML-control has load the asplFormoffSet
: currently not usedclassName
: the CSS-class for the form-tag of the asplFormdoScroll
: currently not usedid
: the id of the formonSubmit
: the JavaScript to be executed when a form is submittedexecutionTime
: the time (ms) it took to process the asplForm and generate the JSONbShowToasts
: currently not usedaspForm
: a JSON object including all the form-fieldsaspForm.asplPostBack
: always true, telling asplForm that a form was submittedaspForm.asplSessionId
: ASP SessionID, used to avoid session-riding, also known as Cross-Site Request Forgery (CSRF)aspForm.asplTarget
: target-div for asplFormaspForm.asplEvent
: repeats the incoming asplEvent for asplForm to know which code to runThe form.build
-method stops the execution of the ASP script. That's why it's very important to call it here. If we don't, it would each time add the HTML, CSS and JavaScript to its response. We already have these, so we don't need them again.
Some might think: is it a good idea to load default.asp 4 times (in a row)? Maybe not. Or maybe it doesn't really matter. The most important idea however is that from now on, it's possible to build a web application based on events and clearly organize your ASP code base, using one ASP entry-point (default.asp) only, performing an unlimited number of different tasks. How you structure it is up to you. There are several ways to do it. You can even use good old include-files for each asplEvent, or use aspLite's execute methods.
Let's take this one step further
Use this default.asp in the root of your ASP application:
<!-- #include file="asplite/asplite.asp"--> <% dim asplEvent : asplEvent=aspl.getRequest("asplEvent") if not aspl.isEmpty(asplEvent) then 'dynamically execute the scriptname in asplEvent aspl("asp/" & asplEvent & ".inc") else 'no event, load the HTML, CSS and JavaScript response.write aspl.loadText("html/default.inc") end if %>
Note that we're not loading (including) one particular file. We're loading "whatever" value aspl.getRequest("asplEvent")
holds. This way, we dynamically load ASP code into the ASP page's namespace. If a file does not exist, aspLite throws a file-not-found error in case aspL_debug (aspLite/config.asp) is "true".
Next, create html/default.inc (create the HTML-folder if needed) with the following HTML, CSS and JavaScript:
<!DOCTYPE html> <html> <head> <title>asplForm Demo</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <!-- jQuery & aspLite JS --> <script src="asplite/jquery.js"></script> <script>var aspLiteAjaxHandler='default.asp'</script> <script src="asplite/asplite.js"></script> </head> <body> <header id="myheader" class="asplForm"></header> <main> <article id="myarticle" class="asplForm"></article> </main> <footer id="myfooter" class="asplForm"></footer> </body> </html>
Finally, create the asp folder and add 3 files to it:
asp/myheader.inc:
<% dim form : set form=aspl.form form.write "this is the header" form.build %>
asp/myarticle.inc:
<% dim form : set form=aspl.form form.reload=1 form.write "this is the servertime: " & now() form.build %>
asp/myfooter.inc:
<% dim form : set form=aspl.form form.write "this is the footer" form.build %>
Browsing to default.asp will return the following HTML:
this is the header this is the servertime: 5-4-2024 18:25:08 this is the footer
A clock starts to run in asp/myarticle.inc. As form.reload
was set to 1 for myarticle.inc, the asplForm in myarticle.inc gets reloaded every second.
Also, this time the code is organized in a different way. Call it code-behind or anything you like. Default.asp executes ASP code, html/default.inc holds all HTML, CSS and JavaScript and the 3 asp-includes each return an asplForm with some content.
Why would you reload an aspLite form every x seconds?
Can't I just use include-files instead of using aspl.exec()?
Sure. This would have worked as well:
<!-- #include file="asplite/asplite.asp"--> <% dim asplEvent : asplEvent=aspl.getRequest("asplEvent") if not aspl.isEmpty(asplEvent) then dim form : set form=aspl.form select case asplEvent case "myheader" %> <!-- #include file="asp/myheader.inc"--> <% case "myarticle" %> <!-- #include file="asp/myarticle.inc"--> <% case "myfooter" %> <!-- #include file="asp/myfooter.inc"--> <% end select else 'no event, load the HTML, CSS and JavaScript response.write aspl.loadText("html/default.inc") end if %>
Be careful though! I moved the line dim form : set form=aspl.form
to default.asp and removed it in all 3 *.inc files. If not, we get a "name redefined" error. That error is not thrown when using aspl.exec()
- or VBScript's executeGlobal. Not sure why that is the case. Maybe it's a bug in executeGlobal.
As such, "good old" including all different ASP scripts like this will not harm performance nor will it need much more RAM. The only inconvenience is that you have to list all events and "manually" assign the appropriate script to them, like in the example above. There is one other reason why you'd prefer regular includes: better error descriptions! Whenever something goes wrong in ASP code imported by aspl.exec()
, all you get is a vague error message without a line number! That can be very annoying to debug your ASP code.
Returns a VBScript Dictionary. Example: dim field : set field=form.field("select")
returns an HTML selectbox.
So far we've used asplForms to serve plain text/html. There is much more to HTML forms than plain text/html. These are the various field-types for asplForms:
Also, it's about time to import a true HTML & CSS framework. I personally like Bootstrap a lot. At this time of writing, Bootstrap 5.3 is the latest version and considered the most popular front-end development framework.
Whenever you start a new ASP development, always use a modern front-end framework. Do not write your own, use an existing one. Back in the late nineties, ASP developers were used to hand code HTML controls, very often on an HTML table-layout. You could really tell if a website was on Classic ASP/VBScript back in those days. They all looked very similar.
An average customer never cares about which technologies their website uses in the back. Customers only care about what they see in the front: an attractive design, good usability, nice colours, does it work on a phone, the icon-set you use, etc. Your application can use the very latest ASP.NET version, if it doesn't have an attractive font-end, your customer won't be happy. On the other hand, if you're using a state-of-the-art front-end framework and well-coded Classic ASP/VBScript in the back, your invoices get paid with a smile.
An example
Let's reuse the default.asp from above in the root:
<!-- #include file="asplite/asplite.asp"--> <% dim asplEvent : asplEvent=aspl.getRequest("asplEvent") if not aspl.isEmpty(asplEvent) then 'dynamically execute the scriptname in asplEvent aspl("asp/" & asplEvent & ".inc") else 'no event, load the HTML, CSS and JavaScript response.write aspl.loadText("html/default.inc") end if %>
Add html/default.inc and import the Bootstrap CSS and JS libraries via CDN:
<!DOCTYPE html> <html> <head> <title>asplForm Demo</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <!-- jQuery & aspLite JS --> <script src="asplite/jquery.js"></script> <script>var aspLiteAjaxHandler='default.asp'</script> <script src="asplite/asplite.js"></script> <!-- Bootstrap CSS and JS --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous"> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script> </head> <body> <main class="container asplForm p-3" id="main"> </main> </body> </html>
Finally, add asp/main.inc:
<% dim form : set form=aspl.form if form.postback then form.write "Hello " & aspl.htmlEncode(aspl.getRequest("name")) form.newline form.write "You're an ASP developer for " & aspl.htmlEncode(aspl.getRequest("years")) & " years." form.newline form.write "Your birthdate: " & aspl.htmlEncode(aspl.getRequest("birthdate")) form.newline form.write "Your email: " & aspl.htmlEncode(aspl.getRequest("email")) form.newline form.write "Your remarks: " & aspl.htmlEncode(aspl.getRequest("remarks")) form.newline form.write "Hidden field: " & aspl.htmlEncode(aspl.getRequest("hidden")) form.build end if dim hidden : set hidden=form.field("hidden") hidden.add "name","hidden" hidden.add "value","12345" dim name : set name=form.field("text") name.add "name","name" name.add "class","form-control" name.add "label","Your name" name.add "required",true form.newline dim years : set years=form.field("number") years.add "name","years" years.add "class","form-control" years.add "label","For how many years are you an ASP developer?" form.newline dim birthdate : set birthdate=form.field("date") birthdate.add "name","birthdate" birthdate.add "class","form-control" birthdate.add "label","Your birthdate" form.newline dim email : set email=form.field("email") email.add "name","email" email.add "class","form-control" email.add "label","Your email" form.newline dim remarks : set remarks=form.field("textarea") remarks.add "name","remarks" remarks.add "class","form-control" remarks.add "label","Remarks" remarks.add "rows",5 form.newline dim submit : set submit=form.field("submit") submit.add "html","Submit" submit.add "class","btn btn-primary" dim reset : set reset=form.field("reset") reset.add "html","Reset" reset.add "class","btn btn-secondary" form.build %>
As form.field("")
returns a VBScript dictionary, the following syntax works as well:
dim hidden : set hidden=form.field("hidden") hidden("name")="hidden" hidden("value")="12345"
If all goes well, you end up with a form asking for your name, your birth date, your email address and some remarks. After you successfully submit, some confirmation message shows up. Congratulations! This is your first (fully responsive) AJAX form using Bootstrap, aspLite and asplForm!
Live example:
A lot is happening in the back:
default.asp?asplEvent=main returns JSON:
{ "target":"main", "offSet":150, "className":"", "doScroll":false, "id":"main_aspForm", "onSubmit":"aspAjax('POST',aspLiteAjaxHandler,$(this).serialize(),aspForm);return false;", "executionTime":"31ms", "bShowToasts":false, "aspForm":[ { "type":"hidden", "name":"asplPostBack", "value":"true", "noinit":"true" }, { "type":"hidden", "name":"asplSessionId", "value":"575517568", "noinit":"true" }, { "type":"hidden", "name":"asplTarget", "value":"main", "noinit":"true" }, { "type":"hidden", "name":"asplEvent", "value":"main", "noinit":"true" }, { "type":"hidden", "name":"hidden", "value":"12345" }, { "type":"text", "name":"name", "class":"form-control", "label":"Your name", "required":true }, { "type":"plain", "html":"<div style=\u0022clear:both;height:7px\u0022 class=\u0022clearfix\u0022><\u002Fdiv>" }, { "type":"number", "name":"years", "class":"form-control", "label":"For how many years are you an ASP developer?" }, { "type":"plain", "html":"<div style=\u0022clear:both;height:7px\u0022 class=\u0022clearfix\u0022><\u002Fdiv>" }, { "type":"date", "name":"birthdate", "class":"form-control", "label":"Your birthdate" }, { "type":"plain", "html":"<div style=\u0022clear:both;height:7px\u0022 class=\u0022clearfix\u0022><\u002Fdiv>" }, { "type":"email", "name":"email", "class":"form-control", "label":"Your email" }, { "type":"plain", "html":"<div style=\u0022clear:both;height:7px\u0022 class=\u0022clearfix\u0022><\u002Fdiv>" }, { "type":"textarea", "name":"remarks", "class":"form-control", "label":"Remarks", "rows":5 }, { "type":"plain", "html":"<div style=\u0022clear:both;height:7px\u0022 class=\u0022clearfix\u0022><\u002Fdiv>" }, { "type":"submit", "html":"Submit", "class":"btn btn-primary" }, { "type":"reset", "html":"Reset", "class":"btn btn-secondary" } ] }
This JSON-stream is generated by the JSON class in asplite/asplite.asp. It's a 100% copy of the JSON class I found in ASP-Ajaxed on https://github.com/ASP-Ajaxed/asp-ajaxed. It was originally developed by Michal Gabrukiewicz. His passing in 2009 was surely one of the reasons for this framework not to become what it could have been. With aspLite, I want to honour Michal for his great work on ASP-Ajaxed. He was one of the best ASP developers ever. I consider aspLite at least 50% Michal's work.
Nowadays JSON parsers are amongst the most popular Classic ASP repositories on GitHub.
This illustrates how developers find their way around limitations and shortcomings. MicroSoft never added support for JSON to Classic ASP. But that does not stop ASP developers. We need JSON-functionality to integrate modern JavaScript libraries and frameworks into our applications. So we get it done!
Adds a line feed of 7px (height).
Example with selectboxes, checkboxes and radio buttons. Keep both default.asp and html/default.inc. Change asp/main.inc to:
<% dim form : set form=aspl.form 'VBScript dictionary dim i, dictionary : set dictionary=aspl.dict for i=1 to 9 dictionary.add i,"option" & i next if form.postback then dim feedback : feedback="<div class=""alert alert-success"">" feedback=feedback & "<p>Selectbox value: <strong>" feedback=feedback & aspl.htmlencode(aspl.convertNmbr(aspl.getRequest("selectbox"))) & "</strong></p>" feedback=feedback & "<p>Radio value: <strong>" feedback=feedback & aspl.htmlencode(aspl.convertNmbr(aspl.getRequest("radio")))& "</strong></p>" feedback=feedback & "<p>Checkbox values: <strong>" feedback=feedback & aspl.htmlencode(aspl.getRequest("checkboxes"))& "</strong></p>" form.write feedback & "</div>" end if dim selectbox : set selectbox=form.field("select") selectbox.add "name","selectbox" selectbox.add "emptyfirst","" selectbox.add "class","form-control form-select" selectbox.add "label","Selectbox showing all values of a VBScript dictionary" selectbox.add "options",dictionary selectbox.add "onchange",form.submit form.newline dim radio : set radio=form.field("radio") radio.add "name","radio" radio.add "class","form-check-input" radio.add "labelclass","form-check-label" radio.add "label","Radiobutton showing all values of a VBScript dictionary" radio.add "options",dictionary radio.add "onchange",form.submit form.newline form.write "Checkboxes showing all values of a VBScript dictionary" dim checkboxes : set checkboxes=form.field("checkbox") checkboxes.add "class","form-check-input" checkboxes.add "labelclass","form-check-label" checkboxes.add "name","checkboxes" checkboxes.add "options",dictionary checkboxes.add "container","div" checkboxes.add "containerclass","form-check form-switch" checkboxes.add "onclick",form.submit form.build %>
Live example:
You can see how a VBScript Dictionary is translated into HTML select boxes, a list of radio buttons and checkboxes by aspLite.js. Be careful, only VBScript dictionaries are supported! They are a perfect match with these HTML-controls (key/value-pairs).
In the example above, the VBScript helper function form.submit
returns the JavaScript code necessary to submit the asplForm.
The JSON string for asp/main.inc looks like this:
{ "target":"main", "offSet":150, "className":"", "doScroll":false, "id":"main_aspForm", "onSubmit":"aspAjax('POST',aspLiteAjaxHandler,$(this).serialize(),aspForm);return false;", "executionTime":"55ms", "bShowToasts":false, "aspForm":[ { "type":"hidden", "name":"asplPostBack", "value":"true", "noinit":"true" }, { "type":"hidden", "name":"asplSessionId", "value":"575517568", "noinit":"true" }, { "type":"hidden", "name":"asplTarget", "value":"main", "noinit":"true" }, { "type":"hidden", "name":"asplEvent", "value":"main", "noinit":"true" }, { "type":"select", "name":"selectbox", "emptyfirst":"", "class":"form-control form-select", "label":"Selectbox showing all values of a VBScript dictionary", "options":{ "1":"option1", "2":"option2", "3":"option3", "4":"option4", "5":"option5", "6":"option6", "7":"option7", "8":"option8", "9":"option9" }, "onchange":"$('#main_aspForm').submit();return false;" }, { "type":"plain", "html":"<div style=\u0022clear:both;height:7px\u0022 class=\u0022clearfix\u0022><\u002Fdiv>" }, { "type":"radio", "name":"radio", "class":"form-check-input", "labelclass":"form-check-label", "label":"Radiobutton showing all values of a VBScript dictionary", "options":{ "1":"option1", "2":"option2", "3":"option3", "4":"option4", "5":"option5", "6":"option6", "7":"option7", "8":"option8", "9":"option9" }, "onchange":"$('#main_aspForm').submit();return false;" }, { "type":"plain", "html":"<div style=\u0022clear:both;height:7px\u0022 class=\u0022clearfix\u0022><\u002Fdiv>" }, { "type":"plain", "html":"Checkboxes showing all values of a VBScript dictionary" }, { "type":"checkbox", "class":"form-check-input", "labelclass":"form-check-label", "name":"checkboxes", "options":{ "1":"option1", "2":"option2", "3":"option3", "4":"option4", "5":"option5", "6":"option6", "7":"option7", "8":"option8", "9":"option9" }, "container":"div", "containerclass":"form-check form-switch", "onclick":"$('#main_aspForm').submit();return false;" } ] }
You can see how the various collections of "options" are passed through as JSON objects.
Writes pure text/html (plain) and JavaScript (script)
Example: keep default.asp and html/default.inc. Change asp/main.inc to:
<% dim form : set form=aspl.form dim plain : set plain=form.field("plain") plain.add "html", "This adds plain/text or <u>HTML</u>. Check the console (f12)!" dim script : set script=form.field("script") script.add "text", "console.log('Add JavaScripts');" form.build %>
Live example:
There are shortcuts for both field-types (plain and script):
form.write "This adds plain/text or <u>HTML</u>" form.writejs "alert('Add JavaScripts');"
asplForms come with a VIEWSTATE-kind of facility
By default asplForms keep track of form-values and auto-fills form-controls with user-input. There is no need to initialize them "manually" with request-values.
This helper-function relies on Bootstrap 5. It returns Bootstrap alerts.
Change asp/main.inc to:
<% dim form : set form=aspl.form form.alert "Primary","Primary alert" form.alert "Secondary","Secondary alert" form.alert "Info","Info alert" form.alert "Success","Success alert" form.alert "Warning","Warning alert" form.alert "Danger","Danger alert" form.alert "Light","Light alert" form.alert "Dark","Dark alert" form.build %>
Live example:
Sometimes you need more flexibility when it comes to placing form-controls.
<% dim form : set form=aspl.form form.className="row" dim name : set name=form.field("text") name.add "name","name" name.add "class","form-control" name.add "label","Your name" name.add "required",true name.add "container","div" name.add "containerclass","col-md-6" dim years : set years=form.field("number") years.add "name","years" years.add "class","form-control" years.add "label","For how many years are you an ASP developer?" years.add "container","div" years.add "containerclass","col-md-6" dim birthdate : set birthdate=form.field("date") birthdate.add "name","birthdate" birthdate.add "class","form-control" birthdate.add "label","Your birthdate" birthdate.add "container","div" birthdate.add "containerclass","col-md-6" dim email : set email=form.field("email") email.add "name","email" email.add "class","form-control" email.add "label","Your email" email.add "container","div" email.add "containerclass","col-md-6" dim remarks : set remarks=form.field("textarea") remarks.add "name","remarks" remarks.add "class","form-control" remarks.add "label","Remarks" remarks.add "rows",5 remarks.add "container","div" remarks.add "containerclass","col-md-12" form.newline dim submit : set submit=form.field("submit") submit.add "html","Submit" submit.add "class","btn btn-primary" submit.add "style","margin-left:13px;width:100px" dim reset : set reset=form.field("submit") reset.add "html","Reset" reset.add "class","btn btn-secondary" reset.add "style","margin-left:7px;width:100px" form.build %>
Live example:
By setting form.className="row"
, we initialize Bootstrap's 12-column grid-system. name.add "containerclass","col-md-6"
makes sure the "name" field uses half of the available grid-lines (6). This way you can easily have multiple form-fields on 1 line. The good thing about using Bootstrap for this purpose, is that forms remain 100% responsive.
Helper function to navigate between asplForms. asplEvent indicates which ASP code to load, target is the ID of the HTML-control that will be bound to and querystring can be used to add custom parameters.
<% dim form : set form=aspl.form if form.postback then form.writejs(form.redirect("redirectNew","redirect","message=" & server.urlencode("Hello there!"))) end if dim redirect : set redirect=form.field("submit") redirect.add "html","Redirect" redirect.add "class","btn btn-primary" form.build %>
Live example:
After clicking the Redirect-button, the file redirectNew.inc is loaded:
<% dim form : set form=aspl.form form.alert "info",aspl.htmlEncode(aspl.getRequest("message")) dim back : set back=form.field("button") back.add "html","Back" back.add "class","btn btn-secondary" back.add "onclick",form.redirect("redirect","","") form.build %>
After clicking the back-button, the ASP code in redirect.inc is loaded again. If "target" is empty, it will inherit the name of asplEvent, thus load the script "ebook/redirect.inc" in div-id "redirect".
Unlike when using "response.redirect" in Classic ASP, you can "redirect" to multiple divs. In other words: multiple divs can be reloaded.
ebook/redirectmultiple.inc reads:
<% dim form : set form=aspl.form if form.postback then form.writejs(form.redirect("redirectNewMultiple","redirect1","message=Hello%20Redirect1")) form.writejs(form.redirect("redirectNewMultiple","redirect2","message=Hello%20Redirect2")) form.writejs(form.redirect("redirectNewMultiple","redirect3","message=Hello%20Redirect3")) form.write "Ok, we're done here." form.build end if dim redirect : set redirect=form.field("submit") redirect.add "html","Redirect" redirect.add "class","btn btn-primary" form.build %>
After hitting the "Redirect"-button, 3 divs are loaded: redirect1, redirect2 and redirect3. They each load the same ASP code (as they have the same asplEvent: redirectnewmultiple).
ebook/redirectnewmultiple.inc reads:
<% dim form : set form=aspl.form form.alert "info",aspl.htmlEncode(aspl.getRequest("message")) form.build %>
Live example:
Summary
asplForms turn aspLite into a modern full-stack developer framework for Classic ASP/VBScript developers. It offers a powerful AJAX web form-builder whether or not making use of the wonderful Bootstrap front-end HTML/CSS framework.
The collaboration between server-side Classic ASP/VBScript and browser-side JavaScript frameworks is very promising and opens up a whole new world to ASP development.
All-in-all, this is a very first attempt to bring back Classic ASP/VBScript development. I would even call it "embryonic". Especially the JavaScript file asplite/asplite.js needs some attention and further tweaking.
It should not be too complicated to add even more helper-functions and as aspLite will grow over the years, more helper functions will be needed to facilitate even more exciting features. For now however I prefer to keep things compact, simple and very robust. That's what I liked about Classic ASP in the first place.
The next chapters will cover more interesting topics and will include many more ideas to make Classic ASP development fun again, whether or not using aspLite as the preferred ASP/VBScript framework.
aspLite comes with some plug-ins. These plug-ins are located in the asplite/plugins-folder. All plug-ins should go in that folder.
There is an important rule to keep in mind when adding your own plug-ins: the name of the folder/ASP-file has to correspond with the name of the actual VBScript Class.
Example: one of the folders in asplite/plugins is "database". The ASP file of the actual plugin is also named "database.asp". That is a requirement for aspLite-plugins to work. Other plug-ins are /asplite/sha256/sha256.asp and /asplite/atom/atom.asp.
Not only the folder and filenames are important, the name of the actual VBScript Class also has to correspond. If you browse the plug-ins, you'll notice the following convention: cls_asplite_database, cls_asplite_atom, cls_asplite_sha256, etc. So there again, the exact same name of the plugin-folder and ASP file returns.
This setup guarantees that aspLite plug-ins work, and all work the same way.
How to create an instance of an aspLite plug-in?
dim db : set db=aspl.plugin("database")
creates an instance of the class cls_asplite_database
, located in /asplite/plugins/database/database.aspdim sha256 : set db=aspl.plugin("sha256")
creates an instance of the class cls_asplite_sha256
, located in /asplite/plugins/sha256/sha256.aspdim uploader : set db=aspl.plugin("uploader")
creates an instance of the class cls_asplite_uploader
, located in /asplite/plugins/uploader/uploader.aspI'm sure you get the point.
One of the most robust and best supported mail-sending components from IIS 5 (Windows 2000) onwards, has been the "CDO.Message"
-object. Pretty much all (shared) hosting solutions supported it. And it still works on Windows Server 2022. CDO.Message
was the successor of "CDONTS.NewMail"
. I have been successfully using CDO.Message
in QuickerSite for almost 25 years now. Never ran into problems.
aspLite comes with a wrapper-class that facilitates the use of this widely supported ASP component. Be aware that I have commented out all .send() methods to prevent abuse. You would need to un-comment the .send-methods for the scripts to work.
This first example does not use a SMTP-username and password, using your server's own SMTP server:
<% dim form : set form=aspl.form if form.postback then if aspl.CheckEmailSyntax(aspl.getRequest("email")) then dim cdomessage : set cdomessage=aspL.plugin("cdomessage") cdomessage.smtpserver="localhost" cdomessage.smtpport=25 cdomessage.sendusing=1 cdomessage.pickupdir="C:\inetpub\mailroot\Pickup" cdomessage.fromname="aspLite" cdomessage.fromemail="info@iseral.be" cdomessage.receiveremail=aspl.getRequest("email") cdomessage.subject="aspLite testmail" cdomessage.body="aspLite testmail" 'cdomessage.send set cdomessage=nothing form.alert "success","Mail sent!" form.build else form.alert "danger","Incorrect email address!" end if end if dim email : set email=form.field("email") email.add "required",true email.add "name","email" email.add "class","form-control" form.newline dim send : set send=form.field("submit") send.add "html","Send email" send.add "class","btn btn-info" form.build %>
Live example:
By default, the mail-body is wrapped into a valid HTML5-template ensuring Unicode-compatibility and correct HTML output.
The next example uses an SMTP-username and password, using an external SMTP server and it adds an attachment. The SMTP server also requires communication via SSL:
<% dim cdomessage : set cdomessage=aspL.plugin("cdomessage") cdomessage.smtpserver="smtp-auth.mailprotect.be" cdomessage.smtpport=465 cdomessage.sendusing=2 cdomessage.pickupdir="C:\inetpub\mailroot\Pickup" cdomessage.fromname="Setlist Planner" cdomessage.fromemail="info@setlist.be" cdomessage.receiveremail="pietercooreman@gmail.com" cdomessage.smtpusername="info@setlist.be" cdomessage.smtpuserpw="xxxxx" cdomessage.smtpusessl=true cdomessage.subject="aspLite testmail" cdomessage.body="aspLite testmail" cdomessage.attachments.add "Attachment",server.mappath("ebook/attachment.rtf") 'cdomessage.send set cdomessage=nothing %>
This last example uses a mobile-friendly (responsive) mail-template that I used on Setlistplanner.com. Feel free to use it for your own projects.
<% dim cdomessage : set cdomessage=aspL.plugin("cdomessage") cdomessage.smtpserver="localhost" cdomessage.smtpport=25 cdomessage.sendusing=1 cdomessage.pickupdir="C:\inetpub\mailroot\Pickup" cdomessage.fromname="aspLite" cdomessage.fromemail="info@iseral.be" cdomessage.receiveremail="pietercooreman@gmail.com" cdomessage.subject="aspLite testmail" dim body : body=aspl.loadtext("ebook/mail.txt") body=replace(body,"[systemname]","Setlist Planner",1,-1,1) body=replace(body,"[body]","Welcome to Setlist Planner",1,-1,1) cdomessage.body=body 'cdomessage.send set cdomessage=nothing %>
The HTML/CSS for ebook/mail.txt reads:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>[systemname]</title> <style type="text/css">@media all and (max-width: 599px) { .container600 { width: 100%; } } </style> </head> <body style="background-color:#CCC"> <p><br></p> <center> <table class="container600" width="600" align="center" cellspacing="0" cellpadding="0" style="box-shadow: 10px 10px 5px #aaaaaa;font-family:Arial;font-size:16px;width:600px;background-color:#FFF;"> <tr> <td style="background-color:#444444;width:40px"> <table style="color:#FFFFFF;" cellpadding="10"> <tr> <td><a href="https://setlistplanner.com" target="_blank"> <img border=0 alt="Setlist Planner Logo" src="https://secure.setlistplanner.com/apps/playthatgig/image/logo.png" /></a> </td> <td style="background-color:#444444;color:#FFFFFF;"> <strong>Setlist Planner</strong> - <a style="color:#FFFFFF" target="_blank" href="http://www.setlistplanner.com">www.setlistplanner.com</td> </tr> </table> </td> </tr> <tr> <td colspan="2"> <a href="https://setlistplanner.com" target="_blank"><img border=0 style="width:100%;border-style:none" alt="Setlist Planner Banner" src="https://secure.setlistplanner.com/apps/playthatgig/image/banner.jpg" /></a></td> </tr> <tr> <td colspan=2> <table cellpadding="20" style="width:100%"> <tr><td> [BODY] </td></tr> </table> </td> </td> </tr> <tr> <td colspan=2 style="text-align:center;background-color:#209CEE;color:#FFFFFF;"> <table align="center" style="color:#FFFFFF;" cellpadding="20"> <tr><td><strong>Setlist Planner</strong> - <a style="color:#FFFFFF" target="_blank" href="http://www.setlistplanner.com">www.setlistplanner.com</td> </tr> </table> </td> </tr> </table> </center> <p><br></p> </body> </html>
Warning: Windows Servers 2025 do no longer come with an SMTP-server!
Windows Servers always shipped with a built-in SMTP server. When I recently gave Windows Server 2022 a try, it appeared to me that the built-in SMTP server was still there, but it got broken. It was no surprise to read that from Windows Server 2025 onwards, the built-in SMTP service will be removed, as it was part of IIS6. IIS6 will also no longer be available in Windows Server 2025.
You may need a replacement for the built-in SMTP server. I always use the freeware version of MailEnable. However, be aware that nowadays the vast majority of email-providers require domain-specific SPF-records that point to the sending server(s). This is something to keep in mind when setting up email-sending applications for your customers. To avoid spam as much as possible, you can better send email over the appropriate mail servers for a given domain. Always check the MX-records for your domain on websites like https://mxtoolbox.com/SuperTool.aspx
The most important reason for using VBScript back in 1997-2003 was ADO (ActiveX Data Objects). ADO is a programming interface to access data in a database. When combined with ODBC (Open DataBase Connectivity), Classic ASP/VBScript developers have always been able to connect to Oracle-databases, PostgreSQL, MySQL, Microsoft SQL Server, Access, Excel, DB2, etc. The sky's the limit.
The only two ADO-objects I've ever used in each and every ASP application, are the ADODB.Connection
and the ADODB.Recordset
. I actually use 2 types of recordsets: read-only (adLockReadOnly - the default lock-type) and read/write (adLockOptimistic). They're all you'll ever need.
aspLite comes with a plugin and wrapper-class for ADODB.Connection
and ADODB.Recordset
:
<% dim form : set form=aspl.form dim db : set db=aspL.plugin("database") db.dbms=1 'Access - default 'db.dbms=2: SQL Server 'db.dbms=3: MySQL db.path="ebook/access.mdb" 'Read/Write Recordset dim rs : set rs=db.rs rs.open "select * from person where 1=2" rs.addNew() rs("lastname")=aspl.pCase(aspl.randomizer.randomText(10)) rs("firstname")=aspl.pCase(aspl.randomizer.randomText(10)) rs.update rs.close set rs=nothing 'Read-only recordset set rs=db.execute("select top 10 * from person") while not rs.eof form.write rs("lastname") & ", " & rs("firstname") & "<br>" rs.movenext wend form.newline form.write "If you want to know the total number of records: " form.write " <strong>" & db.execute("select count(*) from person")(0) & "</strong>" form.newline form.write "If you want to use recordcount however, you need a read/write recordset again: " set rs=db.rs : rs.open("select * from person") form.write " <strong>" & rs.recordcount & "</strong>" set rs=nothing set db=nothing form.build %>
Live example:
Every time this ebook is loaded, a "person" gets created in the database. It's an easy trick for me to see how often this book is read. I'm showing only 10 records though.
This little script is doing a lot in the back:
dim db : set db=aspL.plugin("database")
creates an instance of the database-plugindb.dbms=1
specifies we're dealing with an Access databasedb.path="ebook/access.mdb"
sets the path to the databasedim rs : set rs=db.rs
creates a read/write recordset, next a record is added to the table "person"set rs=db.execute("select top 10 * from person")
creates a read-only recordset and next loops through the records and sends them to the browser.The database-plugin in aspLite is designed to open a connection to a database only once through the lifespan of an ASP script/page. Opening and closing a connection to a database is time-consuming. You can therefore better avoid opening and closing database connections more than once in your ASP script. In other words: you can better create a database-plugin only once through the lifespan of your ASP script. This is very important to keep the speed and performance of your ASP application optimal.
Please note that we're using an Access database here, like we do in the aspLite demosite. In case you mainly use read-only recordsets and you use read/write recordsets with care, Access databases are your best friend, even for large and heavily used datasets.
Access databases?
A lot has been said about using Access databases in web applications (whether or not developed in Classic ASP). Bottom line, most experts say: don't! However, I say: Access is the easiest, fastest, most server-friendly (uses no RAM!) and an extremely reliable database to use with Classic ASP. Period. I have been using them for 25 years now. Never ran into a single problem. And today - with these fast SSD drives and powerful CPUs - you're not going to crash an ASP page that's using an Access database. I have gone through all the available stress-tests over the years. I was even getting frustrated at some point, as I have invested a lot of time and money in migrating to SQL Server in the past. All in vain. Waste of time and money. I went back to Access for all my hosted websites in 2018. No regrets.
That said, there are some guidelines and things to keep in mind when using Access in Classic ASP:
The sha256 ASP/VBScript code was originally written by Phil Fresle in 2001. It still works like a charm, after all these years.
The sha256 plugin would typically be used to store user-passwords in a database. Passwords that are "hashed" with sha256 are one-way encrypted. There is no way to restore passwords once they are hashed with sha256.
Example:
<% dim form : set form=aspl.form dim password : password=aspl.randomizer.createGUID(10) form.write "<strong>" & password & "</strong><br>" form.write " hashes to <br>" form.write " <strong>" & aspL.plugin("sha256").sha256(password) & "</strong>" form.newline dim reload : set reload=form.field("submit") reload.add "html","Reload" reload.add "class","btn btn-danger" form.build %>
Live example:
The Jpeg plugin can be used to resize, recolor and crop jpg/png-files. This is unobtrusive. The original jpg-file is not being modified in the process. This plugin works fine for images up to 1MB. When the JPG files are (much) bigger, converting may take some (processing) time and hurts your server's processor.
<% dim form : set form=aspl.form dim jpg : set jpg=aspL.plugin("jpg") jpg.maxsize=140 'px - max: 2560px jpg.path="/default/html/smallfile.jpg" form.write "These are the 4 available effects:" form.newline dim i for i=0 to 3 jpg.effect=i form.write "<img style=""margin:2px;border-radius:5px;"" src=""" & jpg.src & """ />" next form.newline form.write "The next 4 photo's will be cropped to squares:" form.newline jpg.square=1 jpg.maxsize=140 for i=0 to 3 jpg.effect=i form.write "<img style=""margin:2px;border-radius:5px;"" src=""" & jpg.src & """ />" next form.build %>
Live example:
Before I started using Lewis Moten's pure ASP/VBScript upload-class (he developed it in 2002), I had used various 3rd party COM components to upload files in Classic ASP. Back then, the most known company (by far) for developing these components was Persits. They built (and still do) a variety of COM-products for both Classic ASP and ASP.NET.
Uploading files in Classic ASP/VBScript applications has always been very challenging. After some time though, I was able to build various fileupload-scripts, whether or not using AJAX to smoothen the user-experience. At some point I even started using a JavaScript function that resized JPG-files before they were uploaded. Very useful in case you needed customers to upload lots of photos, but you wanted to speed-up the upload-process. The demo of aspLite ships with such an upload form. Check it out!
I amended Lewis Moten's code so that only a limited number of filetypes can be uploaded. I didn't want users to upload ASP(x) or PHP scripts. The following file-types are allowed for upload: "jpg", "jpeg", "jpe", "jp2", "jfif", "gif", "bmp", "png", "psd", "eps", "ico", "tif", "tiff", "ai", "raw", "tga", "mng", "svg", "doc", "rtf", "txt", "wpd", "wps", "csv", "xml", "xsd", "sql", "pdf", "xls", "mdb", "ppt", "docx", "xlsx", "pptx", "ppsx", "artx", "mp3", "wma", "mid", "midi", "mp4", "mpg", "mpeg", "wav", "ram", "ra", "avi", "mov", "flv", "m4a", "m4v", "htm", "html", "css", "swf", "js", "rar", "zip", "ogv", "ogg", "webm", "tar", "gz", "eot", "ttf", "ics", "woff", "cod", "msg", "odt". I agree this is quite an arbitrary list, but so far it did the trick for me.
In its most basic mode, a (multiple) file-upload form using the Uploader-class in aspLite could look like this:
<!-- #include file="aspLite/aspLite.asp"--> <% dim totalMB, file, message, uploader set uploader=aspl.plugin("uploader") uploader.save server.mappath("upload") if uploader.UploadedFiles.count>0 then for each file in uploader.UploadedFiles set file=uploader.UploadedFiles(file) message=message & "<li>" message=message & "Filename: " & file.filename & " / " message=message & "Filetype: " & file.filetype & " / " message=message & "Filesize: " & round(file.length/1024,0) & " kB" message=message & "</li>" totalMB=totalMB + round(file.length/1024/1024,0) next message="<p>These " & uploader.UploadedFiles.count &_ " files were uploaded in <strong>" &_ round((aspl.printTimer/1000),0) &_ "</strong> seconds with a total of <strong>" &_ totalMB &" MB</strong>:</p><ul>" & message & "</ul>" end if %> <!doctype html> <html lang="en"> <head> <title>Basic upload in aspLite</title> <meta charset="utf-8"> </head> <body> <%=message%> <form action="default.asp" method="post" enctype="multipart/form-data"> <input multiple type="file" name="file" required><br><br> <input type="submit" value="Upload"> </form> </body> </html>
The actual upload takes place in:
uploader : set uploader=aspl.plugin("uploader") uploader.save server.mappath("upload")
This assumes you have a folder "upload" relative to where you run the script. This sample also lists all uploaded files, their names, types and sizes. This is where you could rename them, copy them, process them further, etc. I am often asked what it takes to store files in a database. I strongly recommend to never store files in a database. Use the filesystem! You can store up to 4,294,967,295 files into a single folder on a Windows NTFS filesystem. If you need more... use 2 folders.
Overcome Classic ASP's 200kB (or 28,6MB) upload limit in IIS (Express)
When you run the code above, you might well end up with a server-error in case your total upload-filesize exceeds 28,6 MB.
The following error might occur:
HTTP Error 413.1 - Request Entity Too Large
To fix this, there are 2 limits you have to look into:
Both settings are actually dealing with the same limit, however, maxRequestEntityAllowed
is specific for Classic ASP (only).
In case you use IIS, both limits can be changed using the IIS-snap-in.
To enable up 2GB file-upload in Classic ASP, change requestFiltering/requestLimits/maxAllowedContentLength to 2147483647 (integer / 2GB)
The other limit to set is ASP's maxRequestEntityAllowed. The default is 200000 bytes (200kB). The maximum value is again 2147483647 (integer / 2GB). In my experience, setting ASP's maxRequestEntityAllowed to 2000000 (2MB) works better. However, you should than also update internalChunkSize
in /asplite/plugins/uploader/uploader.asp to enable larger uploads. There as well, update internalChunkSize to 2000000 (just add a 0).
When using IIS Express, you would need to open the file: C:\Users\XXXX\Documents\IISExpress\config\applicationhost.config. Search for "<asp ". Change the entry to:
<system.webServer> <asp scriptErrorSentToBrowser="true"> <cache diskTemplateCacheDirectory="%TEMP%\iisexpress\ASP Compiled Templates" /> <limits scriptTimeout="00:02:00" queueConnectionTestTime="00:00:05" requestQueueMax="1000" maxRequestEntityAllowed="2147483647" /> </asp> </system.webServer>
For IIS Express, you also need to add the following line to C:\Users\XXXX\Documents\IISExpress\config\applicationhost.config (search for </requestFiltering>), add the line <requestLimits maxAllowedContentLength="2147483647" /> right above.
With these settings, Classic ASP can be used to upload up to 2GB in files. Be careful, you don't want users to upload 2GB, unless your application really requires that. Not only will this enable uploading larger files. It will also allow to submit large datasets through regular HTML-forms (in an AJAX call for instance).
You can read more about the ASP limitations in IIS here.
Advanced AJAX uploading in aspLite
The more advanced AJAX-driven example for this ebook uploads one file at a time. That can easily be updated to "multiple" files, simply by adding the attribute "multiple" to the HTML-fileupload control in ebook/uploadhtml.inc. The upload process may seem a little complex, but it is an AJAX upload-facility after all, giving a nice user-experience without annoying page-reloads during the entire process.
Various files are used:
First, the browser requests /ebook/upload.inc:
<% dim form : set form=aspl.form form.onSubmit="" form.write(aspl.loadText("ebook/uploadhtml.inc")) form.writeJS(aspl.loadText("ebook/uploadjs.inc")) form.build %>
As the actual upload will be performed by a custom JavaScript, I disable aspLite's default "onsubmit" by emptying form.onSubmit=""
Furthermore, the ASP script loads 2 files:
The HTML snippet in ebook/uploadhtml.inc:
<input class="form-control" type="file" name="files" id="files" onchange="$('#upload_aspForm').submit();return false;">
Tip: if you add the attribute "multiple" to the HTML-control above, you allow/enable uploading multiple files.
The JavaScript in ebook/uploadjs.inc:
var i=0 var j=0 var files $("#upload_aspForm").submit(function(e){ e.preventDefault() $("#upload_aspForm").find("input[type=file]").each(function(index, field){ files=field.files if (files.length>0) {jQueryUpload()} }) }); function jQueryUpload() { var file = files[i] if (typeof(file) != "undefined") { var fd = new FormData() fd.append('file', file, file.name) ajax(fd,file) } } function ajax(fd,file) { $.ajax({ type: "post", method: "post", url: aspLiteAjaxHandler + "?asplEvent=uploadfile", dataType: "text", contentType: false, data: fd, processData: false, success: function (data) { i++; if(i<files.length) { jQueryUpload() } else { aspAjax('GET','ebook.asp','asplEvent=uploadfb',aspForm) } }, error: function (data) { i++ if(i<files.length) {jQueryUpload()} } }) }
The actual file-saving takes place in ebook/uploadfile.inc (note that the actual "save"-method is commented out - no files are saved for security reasons!). You would need to uncomment that line to enable the actual file-saving.
<% dim uploadsDirVar : uploadsDirVar = server.MapPath ("/ebook/upload") dim upload : set upload = aspL.plugin("uploader") 'upload.Save uploadsDirVar 'commented out! set upload=nothing %>
The JavaScript script itself loads a final file, ebook/uploadfb.inc after a successful upload:
<% dim form : set form=aspl.form form.target="upload" form.alert "success","File was uploaded" form.build %>
All in all, the upload-demo in https://demo.asplite.com is very similar, but adds some very useful feedback-messages for each uploaded file and also summarizes the final result.
The obligate "/asplite/helloworld/helloworld.asp" plug-in demonstrates how to create your own . The most important rule to keep in mind: never response.write anything to the browser from within a plugin. aspLite and aspForm rely on JSON-streams that talk to aspLite.js so they can be converted to HTML. If you expect any output from a plugin, use public functions or variables to expose them to the calling script.
<% class cls_asplite_helloworld public function hw hw="Hello world! Plugin-example. Create your own plugins with ease!" end function end class %>
The plugin can be used like this:
<% dim form : set form=aspl.form form.write aspl.plugin("helloworld").hw form.build %>
Live example:
Custom plug-ins are not limited to asplForms though. You can also directly use them in a more classic way:
<%=aspl.plugin("helloworld").hw%>
Live example:
Summarizing
aspLite comes with a couple of plug-ins (VBScript classes) to facilitate emailing, database, rss & atom, file-upload, encryption and picture-manipulation. Plug-ins can be compared to regular include-files in Classic ASP, except they are only loaded when you initialize them. As soon as a plugin is instantiated, the plugin's-code is available in the ASP page's namespace.
It's possible to develop your own aspLite plug-ins, by adding asp files (including a VBScript class) in the appropriate folder: asplite/plugins/myplugin/myplugin.asp. The most important rule is not to response.write anything to a browser from within a plugin. That would make your plugin useless when working with asplForms.
To conclude this e-book, I want to discuss a couple of loose ends. Things I have been developing over the years, whether or not relying on JavaScript frameworks or relating to aspLite.
Disconnected recordsets are regular recordsets. But they have nothing to do with databases. They are full-blown datatables, stored in the computer's RAM. Disconnected recordsets are much smarter than VBScript dictionaries and arrays. They are designed to store massive volumes of data of any given data type.
aspLite comes with a helper-function that returns a disconnected recordset:
<% public function recordset 'returns a recordset set recordset=server.createobject("adodb.recordset") set recordset.ActiveConnection = Nothing recordset.CursorLocation=3 recordset.CursorType=3 recordset.LockType=4 end function %>
An example:
<% dim form : set form=aspl.form dim rs : set rs=aspl.recordset rs.fields.append "firstname",203,70 rs.fields.append "lastname",203,60 rs.fields.append "birthdate",133,25 rs.fields.append "description",203,25 rs.fields.append "admin",11 rs.open dim i for i=1 to 10 rs.AddNew rs("firstname") = aspl.pcase(aspl.randomizer.randomtext(5)) rs("lastname") = aspl.pcase(aspl.randomizer.randomtext(7)) rs("birthdate") = dateadd("d",(round(aspl.randomizer.randomNumber(10000,25000),0))*-1,date()) rs("description") = aspl.pcase(aspl.randomizer.randomtext(10)) rs("admin") = true rs.Update next 'always movefirst before exporting the recordset! 'by default, after a loop like the one we do, the recordset is EOF (end of file)! rs.movefirst 'apply sorting rs.sort="lastname asc, firstname asc" 'recordcount returns the total number of records in a recordset form.write "We just added <strong>" & rs.recordcount & "</strong> records." form.newline form.write "aspl.json.toJSON converts this recordset to JSON:" form.newline form.writejs "$('#drs').append('"& aspl.json.toJSON("recordset",rs,true) & "');" form.build %>
Live example:
Disconnected recordsets are often used to convert large custom datasets for use in JavaScript frameworks, like could be the case in the example above.
The possibilities are endless. And there are a lot of different datatypes you can store in a recordset. You can also sort, filter and delete records in a recordset. In the above example, the recordset is sorted by last name and first name (both ascending).
One use case for disconnected recordsets could be to list the 100 most recently modified files in a folder. By default, files are sorted alphabetically when browsing them. Adding the file.DateLastModified
to a recordset, allows to sort files by modification date, instead of filename.
Also, sometimes you may want to create recordsets based on complex database queries, combined with other data from other data sources. The sky's the limit. Again, recordsets don't have anything to do with databases. You can use them to organize and export any data of any type.
DataTables sure is one of the most popular jQuery plug-ins. "DataTables is a plug-in for the jQuery Javascript library. It is a highly flexible tool, built upon the foundations of progressive enhancement, that adds all of these advanced features to any HTML table." (source: https://datatables.net/)
These past few years, I have successfully used DataTables in various Classic ASP/VBScript applications, in various ways.
Long story short: all you need to get started with DataTables, is include both the CSS and JS libraries in your HTML header (head-section):
<!-- DataTables CSS & JS --> <link rel="stylesheet" href="https://cdn.datatables.net/2.0.3/css/dataTables.dataTables.css" /> <script src="https://cdn.datatables.net/2.0.3/js/dataTables.js"></script>
From there on, things get very exciting very quickly:
<% dim form : set form=aspl.form dim db : set db=aspl.plugin("database") db.path="ebook/access.mdb" dim i,table table="<table class=""table table-striped"" id=""myTable"" style=""width:100%"">" table=table & "<thead>" table=table & "<tr>" table=table & "<th>ID</th>" table=table & "<th>First name</th>" table=table & "<th>Last name</th>" table=table & "</tr>" table=table & "</thead>" table=table & "<tbody>" dim rs : set rs=db.execute("select top 250, * from person order by id desc") while not rs.eof table=table & "<tr>" table=table & "<td>" & rs("id") & "</td>" table=table & "<td>" & rs("firstname") & "</td>" table=table & "<td>" & rs("lastname") & "</td>" table=table & "</tr>" rs.movenext wend table=table & "</tbody>" table=table & "</table>" form.write table form.writejs "$(document).ready(function() {$('#myTable').DataTable()});" form.build %>
Live example:
This quick 'n dirty example demonstrates how a regular HTML table - filled with records from our person-table - miraculously turns into a searchable, sortable, pageable and striped DataTable. All it takes is the CDN-includes and:
$(document).ready(function() {$('#myTable').DataTable()});
Even though this technique is useful when you have few 100's of records, it will not work with large datasets as your ASP script would take too long to render the table-string. Therefore, DataTables also support AJAX-calls to load limited numbers of records based on searches/paging. You can preview a working sample of DataTables with AJAX for Classic ASP/VBScript here and download its code on Github.
I developed ChromeASP in 2023, as I needed a solid PDF creator for a customer. ChromeASP is a free ASP/VBScript library that uses Headless Chrome (yes, Google's browser) to generate PDF and JPG files. The code and samples for ChromeASP can be downloaded on Github.
Live example: http://pdf.asplite.com/
Classic ASP/VBScript developers never had an easy (and free) way to generate PDF and/or JPG/PNG files. This ASP script uses headless Chrome to get the job done. Chrome converts both websites (urls) and html-snippets to pdf and/or jpg (screenshots). As Chrome is creating the PDF files, there are absolutely no limits when it comes to HTML/CSS/JavaScript support. Chrome simply supports them ALL! Chrome even automatically fixes all sorts of errors and inconsistencies in your HTML. Even the most powerful and well known PHP PDF-libraries are looking at headless Chrome as their unbeatable successor. Check it out!
SheetJS is an Open Source JavaScript library that reads, edits and exports spreadsheets.
The aspLite demo site includes a working example. First, /default/asp/sampleform30.resx is loaded:
<% 'SheetJS sample set form=aspl.form if form.postback then 'conditional load of JavaScript! form.writejs aspl.loadText("default/html/sampleform30.resx") end if dim submit : set submit = form.field("submit") submit.add "html","click to generate xlsx" submit.add "class","btn btn-success" submit.add "id","generateExcel" form.build %>
When the Submit-button is clicked, the following JavaScript is loaded by the browser:
createXLS() function createXLS () { $('#generateExcel').html('Tabledata is requested') $('#generateExcel').append(' <span class="spinner-border spinner-border-sm text-dark" role="status">'); $('#generateExcel').append('<span class="visually-hidden"></span></span>') $('#generateExcel').removeClass('btn-success') $('#generateExcel').addClass('btn-warning') aspAjax ("get",aspLiteAjaxHandler,"asplEvent=sampleform30_data",createFile) } function createFile(data) { $('#generateExcel').html('Tabledata was received') var wb = XLSX.utils.book_new(), ws = XLSX.utils.json_to_sheet(data.aspl) XLSX.utils.book_append_sheet(wb, ws, 'SheetJS Experiment') XLSX.writeFile(wb, 'aspLite.xlsx', {type: 'file'}) $('#generateExcel').removeClass('btn-warning') $('#generateExcel').addClass('btn-primary') $('#generateExcel').html('File is ready for download!') setTimeout(function() { $('#generateExcel').removeClass('btn-primary') $('#generateExcel').addClass('btn-success') $('#generateExcel').html('click to generate xlsx') }, 2000); }
Apart from manipulating the button-class and -html, this JavaScript loads /default/asp/sampleform30_data.resx to get its data:
<% on error resume next sql="select top 5000 contact.iId, contact.sText, contact.iNumber, contact.dDate, country.sText as country " sql=sql & "from contact LEFT JOIN country ON contact.iCountryID = country.iId order by contact.sText asc" dim rs : set rs=db.rs : rs.open(sql) aspL.asperror("json_datatables") aspl.json.dump(rs) on error goto 0 %>
The function aspl.json.dump(rs)
in /default/asp/sampleform30_data.resx generates the JSON-data needed by SheetJS to generate an XLSX-file in function createFile(data)
. Long story short: very few lines of Classic ASP/VBScript and JavaScript code generate a pretty nice 5000-records (Unicode-proof) XLSX-file.
Select2 presents itself as "The jQuery replacement for select boxes". Select2 gives you a customizable select box with support for searching, tagging, remote data sets, infinite scrolling, and many other highly used options. (source: https://select2.org/)
Again, all it takes is to include the necessary CSS and JavaScript in head-section of your HTML page:
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" /> <script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
Basic example:
<% dim form : set form=aspl.form if form.postback then form.alert "success","Option " & aspl.getRequest("selectbox") & " was selected!" end if dim options : set options=aspl.dict for i=1 to 1000 options.add i,"Option " & aspl.padLeft(i,4,0) next form.write "Basic Select2 example for aspLite" dim selectbox : set selectbox=form.field("select") selectbox.add "class","form-control" selectbox.add "name","selectbox" selectbox.add "id","js-data-example-basic" selectbox.add "options",options selectbox.add "onchange",form.submit form.writejs "$('#js-data-example-basic').select2();" form.build %>
This line: $('#js-data-example-basic').select2();
turns a regular HTML selectbox in a searchable control. This is extremely useful for larger option-sets, like lists of countries, cities, etc.
Live example:
However, in some cases, it's impossible to pre-load all options in your selectbox. Imagine you want to be able to select a contact person out of a database with thousands of persons. You can't possibly preload such large option-sets in an HTML selectbox. Therefore, Select2 comes with an AJAX-solution:
Advanced example using AJAX:
<% dim form : set form=aspl.form if form.postback then form.alert "success","Id: " & aspl.getRequest("selectbox") & " was selected!" form.build end if form.write "AJAX Select2 example for aspLite" dim selectbox : set selectbox=form.field("select") selectbox.add "class","form-control" selectbox.add "name","selectbox" selectbox.add "id","js-data-example-ajax" form.writejs aspl.loadText("ebook/select2js.inc") form.newline dim submit : set submit=form.field("submit") submit.add "html","Submit" submit.add "class","btn btn-primary" form.build %>
Apart from the select-control, the following JavaScript in ebook/select2js.inc is loaded:
$('#js-data-example-ajax').select2({ placeholder: '', allowClear: true, ajax: { url: 'ebook.asp?asplevent=select2data', dataType: 'json', delay: 100 } });
Select2 loads the following ASP script: ebook.asp?asplevent=select2data. It searches for matching results in our table "persons" (that we used in previous examples). Select2 sends the parameter "term" to ebook.asp?asplevent=select2data&term=x. Our ASP script returns the JSON-object "results" at the very end of the script:
aspl.dumpJson("{""results"": [" & suggestions & "]}")
<% 'we expect a parameter named "term " from the ajax call. make sure to protect against SQLinjection 'in this query, the searchstring will be searched for in the entire name ('%term %'). 'if you prefer to only search for person names BEGINNING with the querystring, use 'term %' dim db : set db=aspl.plugin("database") db.path="ebook/access.mdb" dim suggestions if len(trim(aspl.getRequest("term")))>0 then dim cleanTerm : cleanTerm=trim(aspl.removeTabs(aspl.sqli(aspl.getRequest("term")))) sql="select * from person " sql=sql & " where firstname like '%" & cleanTerm & "%' or lastname like '%" & cleanTerm & "%' " sql=sql & " order by lastname, firstname" set rs=db.rsOpen(sql) while not rs.eof suggestions=suggestions & "{ ""text"": """ & aspl.json.escape(rs("lastname")) & ", " suggestions=suggestions & aspl.json.escape(rs("firstname")) & """, ""id"": """ & rs("id") & """ }," rs.movenext wend set rs=nothing suggestions=suggestions & "{ ""text"": ""No results"", ""id"": ""0"" }," end if if right(suggestions,1)="," then suggestions=left(suggestions,len(suggestions)-1) end if aspl.dumpJson("{""results"": [" & suggestions & "]}") %>
Live example (start typing 1 or 2 characters):
A Bootstrap modal is no doubt my favorite Bootstrap component. I can only think of advantages when using Bootstrap modals:
The only limitation that I have faced in the past, is that you can only have one modal at a time. Another thing to keep in mind is that when you're having both large and small modals in your app, they could hide each other. The larger modals always hide the smaller ones. The only solution is to close (manually or by JavaScript) modals before loading new ones.
The necessary HTML for any Bootstrap modal is usually loaded (only once) together with the main HTML framework. The modal is nothing but an invisible div, somewhere in your body. For this occasion, I use a large modal (modal-lg). You can also have small (modal-sm) and x-large modals (modal-xl):
<!-- Modal --> <div class="modal fade" id="myModal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="modalTitle" aria-hidden="true"> <div class="modal-dialog modal-lg"> <div class="modal-content"> <div class="modal-header"> <h1 class="modal-title fs-5" id="modalTitle">Modal title</h1> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> </div> <div class="modal-body" id="modalBody"> ... </div> <div class="modal-footer" id="modalFooter"> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button> <button type="button" class="btn btn-primary">Understood</button> </div> </div> </div> </div>
To open a modal, you can use::
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#myModal"> Launch static backdrop modal </button>
Live example
The advantage of a static backdrop modal is that the user cannot close the modal by accident (by accidentally clicking it away). I always use static backdrop modals whenever user-input is expected/required.
I slightly amended the HTML-code for this default modal. I added 3 id's for the 3 div's that matter:
Let's load 3 asplForms for these 3 divs:
<% dim form : set form=aspl.form if form.postback then form.writejs "aspAjax('GET',aspLiteAjaxHandler,'asplEvent=modalTitle',getAspForm);" form.writejs "aspAjax('GET',aspLiteAjaxHandler,'asplEvent=modalBody',getAspForm);" form.writejs "aspAjax('GET',aspLiteAjaxHandler,'asplEvent=modalFooter',getAspForm);" 'open the modal form.writejs "$('#myModal').modal('show');" end if dim submit : set submit=form.field("submit") submit.add "class","btn btn-primary" submit.add "html","Open modal with 3 asplForms!" form.build %>
Live example
Modals are by no means limited to short feedback messages. It's possible to load fully-featured interactive forms in Bootstrap modals. Let's revisit one of the first forms we used above:
<% dim form : set form=aspl.form if form.postback then form.writejs "aspAjax('GET',aspLiteAjaxHandler,'asplEvent=modalTitle',getAspForm);" form.writejs "aspAjax('GET',aspLiteAjaxHandler,'asplEvent=fields&asplTarget=modalBody',getAspForm);" form.writejs "aspAjax('GET',aspLiteAjaxHandler,'asplEvent=modalFooter',getAspForm);" 'open the modal form.writejs "$('#myModal').modal('show');" end if dim submit : set submit=form.field("submit") submit.add "class","btn btn-primary" submit.add "html","Open modal with a fully featured interactive asplForm" form.build %>
Live example
Closing a modal is as easy as:
<% dim form : set form=aspl.form set close=form.field("button") close.add "html","Close" close.add "class","btn btn-secondary" close.add "databsdismiss","modal" form.build %>
I'd love to end this book where it all began, back in 2005. AJAX was invented at around that time and it became popular when Google started using it for its one and only input field on www.google.com. After all these years, nothing has changed. Google.com still offers just that one input field, serving you with auto-filled (autocomplete) suggestions and popular similar searches. That one and only autocompleting input field has made Google the strongest software company on the planet. Never change a winning team, isn't it.
This last excursion is about Ajax Autocomplete for jQuery and jQuery UI's Autocomplete and how to use them in Classic ASP/VBScript. Both jQuery libraries are in maintenance mode. They may even look a little outdated. They're no longer under active development. But they still get the job done!
As always, first things first: add the JavaScript library to the head-section:
<!-- jQuery Autocomplete--> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.devbridge-autocomplete/1.4.11/jquery.autocomplete.min.js">
Let's create a regular input field (type: "text") and load the necessary JavaScript:
<% dim form : set form=aspl.form dim autocomplete : set autocomplete=form.field("text") autocomplete.add "name","autocomplete" autocomplete.add "id","autocompleteJQ" autocomplete.add "placeholder","start writing a name" autocomplete.add "class","form-control" form.writejs aspl.loadText("ebook/autocompletejs.inc") form.build %>
The following JavaScript in ebook/autocompletejs.inc is loaded:
$('#autocompleteJQ').devbridgeAutocomplete({ serviceUrl: 'ebook.asp?asplEvent=autocompletedata' });
The AJAX-call to ebook/autocompletedata.inc retrieves data (max 100 records) from our "person" table:
<% 'we expect a parameter named "query" from the ajax call. make sure to protect against SQLinjection 'in this query, the searchstring will be searched for in the entire lastname ('%query%'). 'if you prefer to only search for persons lastnames BEGINNING with the querystring, use 'query%' dim db : set db=aspl.plugin("database") db.path="ebook/access.mdb" dim rs : set rs=db.rs dim sql : sql="select top 100, lastname from person " sql=sql & " where lastname like '%" & aspl.sqli(aspl.getRequest("query")) & "%'" sql=sql & " order by lastname asc" rs.open(sql) if not rs.eof then aspl.dumpJson(aspl.json.toJSON("suggestions",rs.getRows,false)) else aspl.dumpJson(aspl.json.toJSON("suggestions","",false)) end if %>
Live example:
jQueryUI's Autocomplete enables users to quickly find and select from a pre-populated list of values as they type, leveraging searching and filtering.
jQuery UI's Autocomplete is nearly identical to the implementation by Devbridge.
Include the necessary JavaScript/CSS in the head-section (below jQuery!):
<!-- jQuery UI --> <script src="https://code.jquery.com/ui/1.13.2/jquery-ui.min.js" integrity="sha256-lSjKY0/srUM9BE3dPm+c4fBo1dky2v27Gdjm2uoZaL0=" crossorigin="anonymous"></script> <link rel="stylesheet" href="https://code.jquery.com/ui/1.13.2/themes/base/jquery-ui.css">
Let's create a regular input field (type: "text") and load the necessary JavaScript:
<% set form = aspl.form set field = form.field ("text") field.add "id","jqAutocomplete" field.add "class","form-control" field.add "placeholder","start writing a name" form.writejs aspl.loadText("ebook/autocompletejQueryUIjs.inc") form.build %>
The following JavaScript in ebook/autocompletejs.inc is loaded:
$( "#jqAutocomplete" ).autocomplete({ source: 'ebook.asp?asplEvent=autocompletejQueryUIdata' });
The AJAX-call to ebook/autocompletejQueryUIdata.inc retrieves data from our "person" table:
<% 'we expect a parameter named "term" from the ajax call. make sure to protect against SQLinjection dim output, db, rs set db=aspl.plugin("database") : db.path="ebook/access.mdb" set rs=db.rs rs.open("select top 100, lastname from person where lastname like '%" & aspl.sqli(aspl.getRequest("term")) & "%'") output = "[" while not rs.eof output = output & """" & aspl.json.escape(rs(0)) & """," rs.movenext wend output = left(output,len(output)-1) & "]" aspl.dumpJson(output) %>
Live example:
Summary
All in all it does not take a lot of Classic ASP code to integrate with very popular JavaScript frameworks and jQuery-plugins. And the final result is often sensational, especially compared to what we had available in the early years of Classic ASP. ASP may be outdated, but when using these JavaScript libraries, we can make our applications look great again.
The aspLite demo site integrates some more JavaScript libraries: jQuery UI Datepicker, JSZip, jsPDF, CkEditor and CodeMirror. Check them out!
Classic ASP/VBScript is fun. It always was. It's a capable, fast, server-friendly, light, easy-to-read and understand web development toolbox. In general, it still runs faster than both ASP.NET and PHP ever did on Windows Servers. MicroSoft should never have dropped it. It may not be as powerful, well thought of or structured as ASP.NET always was, Classic ASP had a large following and a reason to exist. It always was - and still is for some - a very popular scripting language.
But Classic ASP lost all its shimmer and shine over the years. The talented developers jumped off the MicroSoft boat a long time ago. I wasn't able to jump. I just had too much going on in Classic ASP back then. And all these years I kept on digging my development grave for way too long now. I need to make up my mind about my career as a web developer someday soon.
aspLite comes way too late in the game, and it's not much more than a collection of fresh ideas on how to use Classic ASP in 2024. In fact, it would have been a better idea to build aspLite on a true MVC architecture from the start. I may get that done any time soon.
Despite the many kudos I receive ever since I started building aspLite in 2020, I am probably the only developer who actually uses aspLite and asplForms. I'm still building amazing web apps with it, yes in 2024. My most recent project is Setlistplanner.com, a web app to organize songs into shareable setlists to ease band rehearsals and live gigs. Setlistplanner.com is built entirely on aspLite.
I recently even started offering good old Classic ASP applications as PWA's (Progressive Web Applications). There is no way you can tell the difference between a responsive PWA (using Bootstrap) and a native app on Windows PC's and/or Android devices. Apple is a different story though.
I hope you liked reading this book. I put all my heart and brains in it.
Pieter Cooreman, April 2024.
About the author
Pieter Cooreman (born 1972) started his developer career in 1998. As a die-hard expert in Classic ASP/VBScript - the most popular web development technology between 1997 and 2003 - Pieter developed his way through basically any type of web application. Between 2007 and 2014, Pieter developed and supported QuickerSite webCMS, a free ASP/VBScript CMS loved and still used by hundreds. At its peak, worldwide more than 6000 QuickerSites were hosted on a variety of hosting solutions for a wide variety of customers and industries.
As Classic ASP is still around, Pieter always loves to share ideas through ASP code and snippets. In 2020 the pieces of a complex puzzle started to fall in place with aspLite, an AJAX-first developer framework for Classic ASP/VBScript developers. Writing this book felt like a roller coaster and sure led to new ideas. Classic ASP/VBScript may be dead, but will be hostable for at least another decade on Windows Servers. So we can better have fun with it. Because fun is key!
Besides being a self-employed developer, Pieter is a caring father of three and a loving husband, living near Leuven, Belgium. He's a web developer during winter and a highly demanded live performer during summer, having regular appearances on national radio and tv. Pieter is living his dreams and passions. And sharing them - Pieter believes - brings true happiness.