ASP.NET Web Forms and jQuery Mobile Reconsidered

I’ve written before on this blog about the problems with combining Web Forms and jQuery Mobile. At bottom, it boils down to this: the Ajax navigation does not play well with ScriptManager. Beyond that, when working with jQuery Mobile you have multiple pages in the DOM at the same time, so you have to make sure you client-side IDs are unique across the site. And not only does every ASP.NET control already have a client ID, you also have to add IDs to the data-role page elements so that pageinit works properly.

I like the animated transitions – but not enough to make it worth that amount of effort. So I want to stop jQuery Mobile from converting my links and forms to use Ajax. Fortunately, that’s entirely doable.

jQuery Mobile exposes an ajaxEnabled property. If you set it to false inside the mobileinit event, your links will all behave as nature intended.

The one twist is that you need to do this between the script references for jQuery and jQuery Mobile. I placed the above code inside jquery.configuration.js, and then put them together thus:

When I click on the link to test2.html, the request does NOT use Ajax:

Test2.html does NOT have the configuration file:

And as a result, the request for test.html DOES use Ajax:

So – now we can use jQuery Mobile with ASP.NET Web Forms without worrying about all those pesky ID problems… And since that will eliminate most of the problems that arise from combining jQuery Mobile with the ScriptManager, wouldn’t it be nice if we could start using the ScriptManager with jQuery Mobile?

We could just add the configuration and jQuery Mobile scripts after the ScriptManager: but then other scripts might be injected in between, and that might lead to unexpected side effects down the line. So the best case scenario would be if we could inject our configuration file and jQuery Mobile into the ScriptManager itself. Fortunately, we can.

So how do we do that? Well, first we need to create a static class and a static method that calls ScriptManager.ScriptResourceMapping and adds a new definition:

Now the one final step is to tell ASP.NET about our new definitions. For this, we use the System.Web.PreApplicationStartMethod attribute, which runs at a very early stage in the application life cycle:

The best place to add this is inside AssemblyInfo under project properties

Now we can add both our configuration file and jQuery Mobile as ScriptReferences, using the names we specified when we added the definition:

And the end result is that the Web Forms application now uses jQuery Mobile AND ScriptManager seamlessly – with the minor sacrifice of forgoing the fancy animated transitions.

Kevin Rattan

For other related information, check out these courses from Learning Tree:

Building ASP.NET Web Applications: Hands-On

jQuery: A Comprehensive Hands-On Introduction

Allowing Users to Select Mobile or Desktop Views

My jQuery Mobile/ASP.NET MVC site, cocktailsrus.com, began as a pure jQuery Mobile site. I then added a desktop view and automatically switched users to the appropriate view depending on their device. This works pretty well – especially with the 51Degrees NuGet to improve on the built-in device detection – but even so, there are occasions when the automated detection fails. Someone with a new device comes to the site, the device isn’t recognized, and the user ends up with the wrong version.

This bugs me.

Or, to be more accurate – what bugs me is that there’s no way for the user to change to the appropriate view.

That’s is the problem with automatic device detection: it’s fine until it doesn’t work. Then you’re stuck. So the answer, obviously, is to provide the user with a means to override the default device detection. They need a button that lets them switch to the other view.

The problem is – how to override the detection and automatic version selection, especially since I am relying on a 3rd party tool to do the detection for me? Fortunately, there is a solution in the form of another NuGet: jQuery Mobile MVC. Scott Hanselman blogs about it here. It’s an excellent solution to the problem… but the sample view user control provided in the NuGet needs a little tweaking if it’s going to solve the specific problem I outlined above.

First, let’s take a look at the NuGet and what it does for us. So let’s install it:

If you don’t already have a mobile specific .layout file, then the install gives you a basic version with associated CSS etc. designed for mobile devices:

And right at the top of the mobile view is the output (“Displaying mobile view Desktop View”) from a view user control, _ViewSwitcher. This control is really what the NuGet is all about.

Clicking on the link leads the user off to a small controller, ViewSwitcherController, with the single method SwitchView:

So… if you are in a mobile device, you get a choice between the mobile and the desktop view. When you click on the link, it sets the BrowserOverride and the user is given the view of their choice rather than the default. Nice.

It’s very neatly done… except that it doesn’t actually solve the problem I started out with. My problem was – what to do about a mobile device that wasn’t picked up as a mobile device… and this code wouldn’t solve that problem.   It will only display the choice of view to devices that the system already thinks are mobile devices. A mobile device that isn’t detected won’t get any option to switch to a mobile view – even if the _ViewSwitcher control is added to the desktop version. The problem is in the first if statement:

Only recognized mobile devices get the links. I want a user with an unrecognized mobile device to be able to select the mobile view. So in my version, I remove the outer if statement and also some of the ancillary text so that I can use the links as part of the menus. Also, because both versions of the site make extensive use of Ajax, I explicitly redirect the user to the home page when they switch view (otherwise, they might end up getting a partial page):

With this small tweak, I now have links in both the mobile and the desktop versions that seamlessly allow the user to override the default selection and switch between the different views:

Now I just need to do something about that bottom menu, as it’s getting too bulky. Time to upgrade to jQuery Mobile 3 and put the navigation in a panel. Onwards….

Kevin Rattan

For other related information, check out this course from Learning Tree:

Building Web Applications with ASP.NET MVC

Having the Server Update the Client with SignalR: A Case Study, Part II

In a previous post, I started a case study, looking at an application where it made sense to use Microsoft’s new SignalR technology. The application that I chose is a sales order system that allows users to purchase products. In addition (and here’s where the client-side callback support that SignalR provides becomes useful), users can mark a sales order as “to be expedited.” An “expedited” sales order could go through all of the processing that leads to it being shipped in a few minutes. In that scenario, the client might be willing to stay online and get updates as the order changed progressed through the process. To support that, I wrote some JavaScript code to have the Web page connect to a SignalR message hub. The hub would send out messages about the status of an order as the hub received updates from other, non-Web clients that were processing the order. When a user marked an order as “to be expedited”, my JavaScript code registered with the hub, passing the ID of the sales order the page was interested in. I used SignalR’s support for messaging groups to organize requests in groups by sales order number–the message hub could send a single update to the group for a salesorder and have that message delivered to all of the interested parties.

In this post, I’m going to look at the .NET clients that, while processing a sales order, will also log onto the message hub and send updates to the hub for processing.

One caveat: As I’m writing this, SignalR is still in beta. It’s possible that things will change before SignalR is released, invalidating what I say here. It’s also possible that SignalR will go the way of Microsoft’s Upshot and never get released at all.

Sending Updates

I could have the .NET clients that will be processing the sales order connect to the message hub and get a list of registered sales orders. However, that’s not necessary in this case because expedited sales order have a field indicated whether or not they’re being expedited. As a result, when a client is processing a sales order it will check the Expedite flag on the order and connect to the message hub.

The first step in creating a .NET client that can work with SignalR is, in Visual Studio, to use NuGet to add the Microsoft ASP.NET SignalR Client and the Json.net packages to the project. That’s true even if, as I am, you’re creating a non-ASP.NET client (I’m building my client-side code in WPF). Once that package is in place (and an Imports or using statement added for the Microsoft.AspNet.SignalR.Client.Hubs namespace) you can write the code to open a connection to the message hub.

With the Web client, I was obliged to start the connection with the hub and keep the connection open so that my Web client could receive messages from the hub at any time. However, this .NET client is only sending messages to the hub and not receiving them. Since there is a cost associated with every connection maintained at the server, if there are a lot of .NET clients then it may make sense to start the connection from these clients only when there’s some information to send. For this case, study, however, I’m going to assume that there is only a single program that processes sales order and so I’ll have that program start the connection as soon as the program starts. This means that I’ll be starting the connection in one method (Window_Load, probably) and using the connection in other methods–I’ll need to declare both the hub and connection at the class level:

Class MainWindow
  Private conn As HubConnection
  Private hub As IHubProxy

To open the connection to the message hub, I create a HubConnection object, passing the URL for the server that my hub is executing on. After that, I create the hub by passing the name of my hub’s class (SalesOrderNotifications, in my case) to the HubConnection’s CreateHubProxy method. I then call the HubConnection’s Start method. This is an asynchronous method but, since there’s not much point in continuing if I don’t have the connection started, I use the Await keyword to wait for the Start method to complete. To support the Await keyword, I put the code in a separate method that I’ll declare as Async and call the method from the Window_Load event of my WPF client:

Private Async Sub InitializeHub()
  conn = New HubConnection("http://localhost:49206/")
  hub = conn.CreateHubProxy("SalesOrderNotifications")

  Await conn.Start
End Sub

In some other method, I’ll call the method on my message hub that send out an update message as the sales order’s status changes. For that I use the hub’s Invoke method, passing the name of the method on the message hub I want to call along with any parameters that method requires. My method on the message hub is called UpdateSalesOrderStatus and must be passed the SalesOrder Id and a SalesOrder status. The Invoke method is also asynchronous but, since nothing in my client depends on the method, I’ll use it in “fire and forget” mode: call the method let it complete in the background. That code looks like this:

Public Sub TalkToHub(SalesOrderID As String, Status As SalesOrderStatus)
  hub.Invoke("UpdateSalesOrderStatus", SalesOrderID, Status)
End Sub

When a client gets tired of receiving messages, they can call the stop method on their connection connection’s hub property which will not only break the connection but will also remove them from any groups they are part of.

And that’s it: I have a message hub that accepts clients and routes messages between them. It’s a very cool technology.

- Peter Vogel

Image Caching Headers and Amazon S3

A while ago I wrote an article about using Amazon S3 to serve images for my site, cocktailsrus.com. One thing I hadn’t noticed – until it was pointed out by my colleague, Richard Howells, who is very hot on caching and efficiency in general – was that the images were not being served with an expiry header to set client-side caching. That leads to two questions: why should I care, and what do I do about it?

1. Why should I care?

If you don’t set an expiry date, every single request for your page will lead to a request to the server to a. check if the image has changed, and b. download it if it hasn’t. That potentially means lots and lots of requests – like this:

Even though they aren’t downloading the images, all those requests take time. By adding a caching policy, we can make sure that those requests never happen. What we need is an HTTP header that tells the client to save the image until some specified date in the future:

With this header in place, a visit to the page (but not a browser refresh: that behaves differently) will lead to many fewer requests:

2. What do I do about it?

Different web servers will offer their own mechanisms for setting cache policies – but if you’re using Amazon S3, you have three options:

  1. Add the policy programmatically when you insert the image
  2. Set caching rules individually through the Amazon AWS console
  3. Use a tool to set the policy on all images in an existing bucket

1. Setting the cache value programmatically:

The Amazon SDK provides the PutObjectRequest object. This has an AddHeader() method that accepts two arguments – the header key and value, both as strings. You could calculate the date as so many days/months/years in the future, but the upshot needs to be something along these lines.AddHeader(“expires”, “Mon, Jan 1 2024 11:11:11 GMT”);

2. Set cache policies directly on individual images:

You can do this via the AWS Management Console. Just select the individual image:


Then add the metadata rule you want and click Save:


3. Use a tool to set caching on an entire bucket:

There’s a free tool – cloudberry, which you can download here – which will do this for you. You need to give it your AWS keys, and then you can connect to your buckets. Right click on the bucket to bring up the Set HTTP Headers option:

Then tell it to add the header you want:

You’ll need to make sure your security policy is set to allow you to make the changes from your machine for this to work. I temporarily removed my policy and re-added it after the changes were complete – and now my images no longer require a 304 check every single time someone visits the page. Since S3 charges by usage, that could add up to significant savings over time as well as the obvious performance advantages.

Kevin Rattan

For related information, check out these courses from Learning Tree:

Cloud Computing with Amazon Web Services

Building ASP.NET Web Applications: Hands-On

Combining Google Maps with ASP.NET Web Forms – Part 2

In my last post I showed how easy it was to add a Google map to a Web page. This time, I want to look at how you can customize the map by adding markers to it, as well as using data binding to build the script dynamically.

The application I’m using provides restaurant recommendations for Learning Tree education centers. What I want to do is add a map that shows the location of the restaurant – and for that, we need to have the latitude and longitude. That’s not something users will know, so the first thing we need is a form that asks Google for the information.

Here is the form:

The user fills in the address, then clicks on the Get Coords button– this calls a retrieveCoords() function which asks Google to look up the address and give us the longitude and latitude. The key object we need is Google.maps.Geocoder. It has a geocode() method that accepts a string address argument and returns an array of results containing location information – and specifically, lat() and lng() methods that have the details we need. Here is the code:

So now we have the longitude and latitude available, we want to add a map showing the restaurant location, so the the app goes from looking something like this:

To something like this:

This is similar to what we did last time, except that now we’re going to be building the script using longitude and latitude values from the database.

The basic page is as follows:

  1. The maps.googleapis.com/maps/api file is referenced in a content area mapped to the header on the master
  2. A div with the id map has been added immediately above a DetailsView which displays information about a restaurant. The div has been styled to float right (as in the image above).
  3. The DetailsView is bound to a list of Restaurant objects

Now what we have to do is build a script to

  1. retrieve a map centered on the longitude and latitude of the bound restaurant, and
  2. create a marker to indicate its location.

Since we have all the data ready to hand inside the DetailsView, that’s where we’re going to build the script. The first thing we need is TemplateField, and since there’s already one there, we’re just going to reuse that – we’re only adding script, after all, so it’s not going to affect the look and feel:

Then we use the same code that we did when we were building a simple Google Map – but this time using data binding to write the values into the JavaScript:

If you look carefully at the image, you’ll see that Visual Studio isn’t entirely happy. It thinks there’s a syntax error:

Fortunately, it’s wrong. The generated code is perfectly fine:

Which means we now have a Google map driven by a script that’s built dynamically using data binding. The resulting page looks like this:

However, we’re not done yet. We want to show the user the exact location of the restaurant, not just the general area. For that we need a marker – a google.maps.Marker, to be precise – which can be added to the map using the setMap() method. Like the Map object itself, the Marker needs a google.maps.LatLng object, so this time I’m going to create one explicitly and then use it in both methods. Here is the code:

And this is the final result, complete with marker (which will display the name of the restaurant if you hover over it, thanks to the title attribute:

There’s obviously a lot more you can do with Google maps – like customizing icons, adding directions and waypoint markers and much else besides – and I may just explore some of those features in future posts. For now, though, I think that should be enough to help those former (and future) students who want to make a start on combining Google maps and ASP.NET web forms.

Kevin Rattan

For other related information, check out these courses from Learning Tree:

Building ASP.NET Web Applications: Hands-On

Combining Google Maps with ASP.NET Web Forms – Part 1

One question that keeps cropping up while I’m teaching is – how do I include Google Maps in ASP.NET pages?

The short answer is – it’s incredibly easy: just go to this page, follow the steps and you’ll get code you can paste into your page. The problem with that is it only works for fixed locations. It’s great for showing a map of your office on your web page, not so great for displaying maps using data you’ve retrieved from a database. For that, you need to use JavaScript to call the Google Maps API.

In this post, I’m going to introduce the basics – how you sign up and the bare minimum JavaScript you need to get up and running. In my next post, I’ll look at building the script dynamically using data binding, and show you how to add a marker on the map.

The first thing you need to do is sign in to your Google account (you have one, right? If not, just sign up: it’s free). Then you need to go along to the API console at
http://code.google.com/apis/console
and get an API key. Without the key, there are limits to how much you can do with your maps and you can’t exceed the anonymous usage limit of 25000 requests per day. Passing the key to Google along with every request lets them track your usage so they don’t cut you off when the free limit is exceeded. (And if you’re going to be a heavy business user, then you need to go further, use the business API and get a client id).

Once you’re at the API console, you need a project to work with – so if you don’t have one already, it guides you through creating one. Then you need to sign up for individual services.

Just click on Services on the left….

And scroll down the page until you find maps. There are various options, but you want Google Maps API v3.

Now you need to get yourself a key. So click on API Access in the left hand menu:

Copy the key – you’re going to need it by and by. The other thing you’ll want to do is set up restrictions so that the key can only be used on domains you control (so that no-one can steal your key and use some of your quota). Click on the Edit allowed referers link on the right and then add your domains to the dialog – one line per domain, using the pattern *.mydomain.com/*. (Don’t worry about adding localhost; just leave the key out altogether when you’re testing).

Click on Update and you’re ready to begin.

First, add a script reference to maps.googleapis.com/maps/api/js to the page. It’s available via both http and https, so use a relative path beginning with // so it defaults to whichever protocol is currently being used by the page. The two arguments are key (which you can omit if you want to, and should omit during testing on localhost) and sensor, which is designed to tell Google whether the device sending the request has used GPS to find its location (so if you’re not doing so, just set it to false).

With the script available, you can now call the maps API. The first thing we need is a div on the page to hold the map. I’ve given mine the id ‘map’ (not very original, I know) and styled it in an external CSS file.

Now the infrastructure is in place, all we have to do is call the map and tell it what to display. We need to do two things:

  1. Create an options object literal specifying where we want the map to be centered, what zoom level to show (0 – 21: where 0 is the whole world) and which kind of map to display – a standard road map, satellite view or a hybrid.

  2. Create a new map, telling it 1) which div to use for display and 2) how to display it – which is done by passing in the options object we just created

And that gives us a map on the page, centered on the location we specified:

That’s it for now. In my next post, I’ll cover retrieving latitude and longitude information for a given address, displaying icons on the map and building the script dynamically on an ASP.NET page using data binding.

Kevin Rattan

For other related information, check out these courses from Learning Tree:

Building ASP.NET Web Applications: Hands-On

Having the Server Update the Client with SignalR: A Case Study, Part I

In earlier posts, I described how to set up a SignalR server-side hub that can receive message from and, more importantly, send messages to the client, how to create a JavaScript client that could send and receive messages from that hub, and how to send and receive messages from  .NET (i.e. non-JavaScript) clients. As I noted in those articles, these are things that any Web Service can do.However, with an ordinary Web Service, the server can only send a message as part of response to a request from the client. SignalR lets the server send messages to the client whenever the server has information to share with the client and have the client receive the messages immediately (at least, with up-to-date browsers; with older browsers SignalR automatically falls back to repeatedly checking the server for updates (polling).

But all of those posts used simple “proof of technology” examples (I was only passing text between the clients and the server, for instance). This post will do something more realistic (not “realistic,” just “more realistic”) with the tools developed earlier. I’ll also introduce some SignalR functionality that makes it easy to send messages to groups of users.

A Sales Order Status Notification System

For my case study, I’ll assume a Web-based Ajax-enabled sales order system. While it would be possible to create all the Web Services required by this application (all the CRUD operations on sales orders, for instance) using SignalR for those operations doesn’t make a lot of sense. A SignalR hub requires a persistent connection between the client and the hub so that the hub can send messages to the clients at will (in fact, the technology that I’ve used in these posts is a wrapper for a more low-level object called PersistentConnection)–those connections have to start piling up at the server and should be used…judiciously. While I haven’t done any performance testing a hub certainly appears to be a singleton object which suggests that it could form a bottleneck within an application. And, besides, the CRUD operations don’t require what SignalR provides: immediate updates from the server when something changes at the server. SignalR shouldn’t be used indiscriminately but only where it adds value.

So where, in this application, would using SignalR add value? While most sales orders are processed in a cycle that takes several days, the application lets customers ask for “expedited processing” which causes the order to be placed and the product shipped (or a service team dispatched) in a few seconds–one or two minutes at the most. In this scenario, the user can leave their client up and see updates as the sales order progresses through the processing cycle.

To support that, the application register expedited orders with a SignalR hub. As applications elsewhere in the company process the sales orders they will send messages to the same hub that all the other clients have connected to. The hub will then update the interested client (or clients) in the sales order’s progress

Creating the Infrastructure

I’ll start by defining a class that I can use to pass sales order status around and an enumerated value for the various statuses that an order can go through:

Public Class SalesOrderStatusUpdate
    Public Property ID As String
    Public Property Status As SalesOrderStatus
End Class

Public Enum SalesOrderStatus
    Started
    InProcess
    FinalProcessing
    ProductOrServiceTeamDispatched
End Enum

My next step is to define the hub with a method that allows clients to tell the hub which sales order(s) the clients are interested in tracking. In this organization it’s possible that multiple clients may want to track a single order so, to support that, I’ll take advantage of SignalR’s Groups functionality. SignalR Groups allow you to create named collections of clients that you can send messages to (without sending messages to clients in other groups). Clients are added to groups by passing the group name (which you make up) and the client’s ConnectionId (which can be retrieved through the hub’s Context property).

This is the method that the client will call, passing a sales order Id, to register the client’s interest in a sales order. The code adds the client to a group with name of the sales order Id. The code then sends a return message to the caller the using the Clients Caller property (this code assumes that the client has defined a function called acknowledgeRegister to accept that message):

Imports System
Imports System.Web
Imports Microsoft.AspNet.SignalR

Public Class SalesOrderNotifications
    Inherits Hub

    Public Sub RegisterForSalesOrderStatusUpdate(ID As String)
        Me.Groups.Add(Me.Context.ConnectionId, ID)
        Me.Clients.Caller.AcknowledgeRegister("Registered")
    End Sub

The next step is to define a method that other applications can call to provide information about the sales order’s status. This method will accept two parameters (a sales order’s Id and the current status of the sales order), create a SalesOrderStatusUpdate object with those values, and then send that SalesOrderStatusUpdate object to all the members of the group with the same name as the sales order Id. Sending that message is especially easy: Just use the Clients’ Group method, passing the group name.

This code does all of that (and assumes that clients have implemented a method called GetSalesOrderStatusUpdate to receive the message):

Public Sub UpdateSalesOrderStatus(ID As String, Status As SalesOrderStatus)
    Dim sosu As New SalesOrderStatusUpdate With {.ID = ID, .Status = Status}
    Me.Clients.Group(ID).ReceiveSalesOrderStatusUpdate(sosu)
End Sub

Also, on the server, I need to register my SignalR hubs with this line of code (in an ASP.NET MVC application, I’d include this code with the rest of my routing commands; in an ASP.NET application, I would put it in my Global.asax file’s Application_Start method):

RouteTable.Routes.MapHubs()

Creating the Client

On the client, I need to open a connection to my server when the user requests the “Expedited Order” option. First I need a textbox to hold the sales order id and a checkbox to let the user indicate that the order is to be expedited:

<asp:TextBox ID="salesOrderIdTxt" runat="server" />
<asp:CheckBox ID="expeditedOrderChk" runat="server" Text="Expedite Order" />

When the checkbox’s change event fires I need to see if the box has been checked or unchecked. If the box has been checked, I will create a connection to my SalesOrderNotifications hub:

<script type="text/javascript">
 var conn;
$(function () 
 {
   $("#expeditedOrderChk").change(function () 
       {
         if (this.checked) 
           {
            conn = $.connection.salesOrderNotifications;

The next step is to supply client-side JavaScript functions to be executed when the hub either calls the  acknowledgeRegister or receiveSalesOrderStatusUpdate methods at the server:

            conn.client.acknowledgeRegister = function (message) 
                    {
                     $('#messages').append(message);
                    };
            conn.client.receiveSalesOrderStatusUpdate = function (sosu) 
                    {
                      $('#messages').append(sosu.Id + ": " + sosu.status);
                    };

Now, when the hub calls the aknowledgeRegister method passing a string (in the server-side RegisterForSalesOrderStatusUpdate method) the JavaScript code here will add that string to the messages element on the page; similarly when the hub calls the recieveSalesOrderStatusUpdate method (as part of the hub’s UpdateSalesOrderStatus method) the Id and status properties will be pulled from the object and added to the messages element.

I’m now ready to start the connection. This code, after starting the connection, retrieves the sales order id from the page and passes it to the hub using the registerForSalesOrderStatusUpdate method to indicate that I want to receive messages about the sales order (i.e. to have me added to the group for this sales order Id):

            $.connection.hub.start().done(function () 
                    {
                     var soId = $("#salesOrderIdtxt").val();
                     conn.server.registerForSalesOrderStatusUpdate(soId);
                    });
           }
       });
 });

In my next post, I’ll look at the applications that will send sales order updates to the hub and allow clients to deregister from the hub.

Astute readers will recognize that what I’m implementing here is a variation on the Observer pattern. In the Observer pattern, an object maintains a list of clients that are to be notified when something happens. Clients register with the observer to be added (or removed) from this list. In the basic Observer pattern, all the clients are simply notified that “something has happened”  at the object. To find out what has actually happened, the clients have to request information from the object to find out exactly what has happened. In more sophisticated examples, the clients are sent information about the change in the object which may make it unnecessary for the client to request information from the object (either the clients get all the information they need or can use the information to determine that they’re not interested in the change). This is the way that, for instance, the .NET events system works, passing additional information in the e/EventArgs parameter. Other variations allow the clients to specify what information they’re interested in receiving and avoid being notified about all changes in the object. The Observer pattern (and many others) are described in Learning Tree’s 511 course, .NET Best Practices and Design Patterns.

Peter Vogel


Learning Tree International

.NET & Visual Studio Courses

Learning Tree offers over 210 IT training and Management courses, including a full curriculum of .NET training courses.

Free White Papers

Questions on current IT or Management topics? Access our Complete Online Resource Library of over 65 White Papers, Articles and Podcasts

Enter your email address to subscribe to this blog and receive notifications of new posts by e-mail.

Join 24 other followers

Follow Learning Tree on Twitter

Archives

Do you need a customized .NET training solution delivered at your facility?

Last year Learning Tree held nearly 2,500 on-site training events worldwide. To find out more about hosting one at your location, click here for a free consultation.
Live, online training

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: