Category Archives

19 Articles

Posted by .Ronald on

Creating an Angular Single Page Application with Azure Active Directory and adal.js that uses an ASP.NET WebAPI

This sample shows how to create a single page application (SPA) that uses Azure Active Directory (AAD) authentication with adal.js and uses an ASP.NET WebAPI with AAD.

The source code for this sample can be found in the angular2-adaljs-webapi GitHub repository.

Set up the applications

  1. Create an Angular application.
    I’ve started from the Angular QuickStart seed to bootstrap an easy to use SPA.
  2. Create a WebAPI application.
    I’ve started in Visual Studio by creating a new ASP.NET Web Application, using the Empty template with Web API folders and core references added to it.

Implementing the WebAPI

I’ll setup the WebAPI first to provide data to the SPA without any authentication

  1. Create a model
    public class Message
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string Body { get; set; }
        public string Author { get; set; }
        public DateTime PublishedAt { get; set; }
    }
  2. Create a WebAPI 2 controller
    public class MessageController : ApiController
    {
        private IList<Message> _messages = new List<Message>()
        {
            new Message { Id = 1, Title = "Lorem ipsum", Body = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", PublishedAt = DateTime.Now},
            new Message { Id = 2, Title = "Pellentesque convallis", Body = "Pellentesque convallis finibus erat, sed lacinia eros mattis quis.", PublishedAt = DateTime.Now},
            new Message { Id = 3, Title = "Maecenas scelerisque", Body = "Maecenas scelerisque pretium risus, eu gravida elit porttitor id.", PublishedAt = DateTime.Now}
        };
    
        public IHttpActionResult Get(int id)
        {
            var message = _messages.FirstOrDefault(m => m.Id == id);
    
            if (message == null)
            {
                return NotFound();
            }
    
            return Ok(message);
        }
    }

This result in a WebAPI that can be consumed like this:

Implementing the Angular single page application

The SPA will consume the WebAPI I’ve created before and show the data on the screen. I will try to follow at least a few of the standards and best practices in Angular development. But bear in mind that this application is not meant to serve as a sample application as such.

  1. Create a model to represent your data
    export class Message {
        Id: number;
        Title: string;
        Body: string;
        Author: string;
        PublishedAt: Date;
    }
  2. Create a service that consumes the WebAPI
    import { Injectable } from '@angular/core';
    import 'rxjs/add/operator/toPromise';
    import { Http } from '@angular/http';
    import { Message } from './message';
    
    @Injectable()
    export class MessageService {
        private messageUrl = 'http://localhost:50071/api/';
    
        constructor(private http: Http) {}
    
        getMessage(id: number): Promise<Message> { 
            return this.http.get(this.messageUrl + 'message/' + id)
                .toPromise()
                .then(response => response.json() as Message)
                .catch(this.handleError);
        }
    
        private handleError(error: any): Promise<any> {
            console.error('An error occurred', error); // for demo purposes only
            return Promise.reject(error.message || error);
        }
    }
  3. Create a component to call the service and display the data
    import { Component } from '@angular/core';
    import { Message } from './message'
    import { MessageService } from './message.service';
    
    @Component({
        selector: 'message',
        template: `<div *ngIf="message; else noMessage">
            <h2>{{message.Title}}</h2>
            <div>{{message.Body}}</div>
            <br />
            <div><label>Author: </label>{{message.Author}}</div>
            <div>{{message.PublishedAt | date:'fullDate'}}</div>
        </div>
        <button (click)="getMessage()">Get message</button>`
    })
    export class MessageComponent {
        messageId: number;
        message: Message;
    
        constructor(private messageService: MessageService) {
            this.messageId = 0;
            this.message = null;
        }
    
        getMessage() {
            this.messageId = Math.floor((Math.random() * 3) + 1);;
            this.messageService.getMessage(this.messageId).then(m => this.message = m);
        }
    }
  4. Add routing and declarations to the Angular app.
    This is what my app.module.ts looks like:

    import { NgModule }      from '@angular/core';
    import { BrowserModule } from '@angular/platform-browser';
    import { RouterModule } from '@angular/router';
    import { HttpModule } from '@angular/http';
    import { AppComponent }  from './app.component';
    import { MessageComponent } from './message.component';
    import { MessageService } from './message.service';
    
    var routeConfig = [
      {
        path: 'messages',
        component: MessageComponent
      }
    ];
    
    @NgModule({
      imports: [BrowserModule, RouterModule.forRoot(routeConfig), HttpModule ],
      declarations: [ AppComponent, MessageComponent ],
      providers: [ MessageService ],
      bootstrap:    [ AppComponent ]
    })
    export class AppModule { }

    And this is what my app.component.ts looks like:

    import { Component } from '@angular/core';
    
    @Component({
      selector: 'my-app',
      template: `<a routerLink="/">HOME</a> <a routerLink="messages">Messages</a>
        <h1>Hello {{name}}</h1>
        <router-outlet></router-outlet>`
    })
    export class AppComponent { 
      name = 'Angular'; 
    }

Enable CORS to allow cross origin web requests

As the Angular application and the WebAPI are served from different hosts, we need to explicitly allow the SPA to consume data from the WebAPI. We do this by enabling CORS in the WebAPI and allow requests originating from the SPA.

  1. Add the CORS package to the WebAPI
    Install-Package Microsoft.AspNet.WebApi.Cors
  2. Enalbe CORS in WebApiConfig.cs
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            config.EnableCors();
    
            // Web API configuration and services
    
            // Web API routes
            config.MapHttpAttributeRoutes();
    
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }
    }
  3. Allow GET request coming from the SPA to the controller
    [EnableCors(origins: "http://localhost:3000", headers: "*", methods: "get")]
    public class MessageController : ApiController
    {

Add Azure Active Directory Authentication to the Angular SPA

I’ve already described how to add AAD authentication to an existing Angular 2 application in a previous blog post Add Azure Active Directory to an existing Angular 2 Single Page Application. I will follow the steps outlined there. This is in short how this is done:

  1. Configure the app to use SSL
  2. Register the application in Azure Active Directory
  3. Configure it to use OAuth2
  4. Implement and configure adal.js
  5. Add login and (optionally) logout functionality to your app that logs in to AAD
  6. Add a route guard to protect routes from unauthorized access and force AAD authentication

I’ve now created an Angular2 SPA that requires Azure Active Directory authentication (in some parts of the application), and that consumes a WebAPI, which not yet requires authentication.

Set up the WebAPI to require authentication

Next step is to set up the ASP.NET WebAPI to require authentication on the service the Angular2 SPA is consuming. This step is also already described in a previous blog post. You can read it here: Add Azure Active Directory to an existing ASP.NET MVC web application.

I am using Visual Studio 2017, so the easiest way for me to add Azure Active Directory authentication is by right-clicking on the Connected Services item in the project

  1. Right-click on the Connected Services item and select Add Connected Service
  2. From the list of connected services select Authentication with Azure Active Directory to configure single sign-on in your application
  3. On the introduction screen of the wizard click Next
  4. On the next screen enter your Domain (tenant) and an App ID URI
    If your WebAPI wasn’t already configured to use SSL, the wizard will do that for you.
  5. Optionally you can click Next to enable Directory access, so the application can read profile information from AAD
  6. Click Finish and the wizard will make the necessary changes to your code like adding Owin middleware, packages the Authorize attribute to the controllers, configure authentication

Tie the ends together

Now with both a WebAPI and a SPA configured to require Azure Active Directory, all I have to do is have them work together. I do this by telling the Angular2 SPA to send a JSON Web Token with every request sent to the WebAPI.

  1. Add the angular2-jwt libraries to the Angular2 SPA
    npm install angular2-jwt --save
  2. Have the route guard acquire the token for the logged in user and store the token in the localStorage. Therefore it needs the App ID URI from the WebAPI service. You can find this in the Azure Portal.
    Add this App ID URI to your AdalJs settings.

    import { Injectable } from '@angular/core';
    @Injectable()
    export class SecretService {
        public get adalConfig(): any {
            return {
                tenant: '[your tenant]',
                clientId: '[a GUID, the application ID]',
                redirectUri: window.location.origin + '/',
                postLogoutRedirectUri: window.location.origin + '/',
                resourceId: "[App ID URI]]"
            };
        }
    }

    Use it to acquire the token in the route guard:

    if (this.adalService.userInfo.isAuthenticated) {
        this.adalService.acquireToken(this.secretService.adalConfig.resourceId)
            .subscribe(tokenOut => localStorage.setItem('id_token', tokenOut));
    
        return true;
    } else {
  3. Change the root URL of the API to its https counterpart and replaced the regular Http provider with the AuthHttp provider in the service
    import { AuthHttp, AuthConfig, AUTH_PROVIDERS, provideAuth } from 'angular2-jwt/angular2-jwt';
    constructor(private http: AuthHttp) {}
  4. Add the provideAuth configuration in app.module.ts to tell it that it needs to add the token from the adal service (from step 2) to each request to the WebAPI that requires authentication
    providers: [MessageService, SecretService, AdalService, RouteGuard, provideAuth({
      tokenGetter: (() => localStorage.getItem('id_token'))
    }) ],
  5. In the Azure Portal, add API access to the WebAPI application to the SPA application
Posted by .Ronald on

Add Azure Active Directory to an existing Angular 2 Single Page Application

This article will guide you through the process of configuring your Single Page Application (SPA) in TypeScript (or JavaScript) to use Azure Active Directory (AAD) authentication.

We will use adal.js Active Directory Authentication Library (ADAL) for JavaScript and ng2-adal (which is built upon adal-angular)

Prepare your application to use Azure Active Directory

  1. If your application doesn’t already use SSL, it is hightly recommended to do it now. AAD without SSL, thus running over an unsecure connection is not advisable and a real hassle to setup.
    Note the SSL url of your application, as you will need it later to register the application in AAD.

Register your application in Azure Active Directory

If you haven’t already registered your application in the Azure Portal, follow the steps below:

  1. Sign in to the Azure portal
  2. Choose Azure Active Directory from your services (search using More Services if it isn’t shown yet)
  3. Choose App registrations and Add
  4. Enter a Name, choose Web app / API for Application Type and enter the URL of your web application under Sign-on URL (without the trailing slash)
    The URL is the SSL url we got earlier when we enable SSL for our web application
  5. Click Create
  6. Still in your application registrations, choose your application, choose All settings and Properties
  7. Copy the Application ID
  8. Enter the Logout URL as the Sign-on URL you entered earlier, followed by /Account/EndSessionThis will link to the single sign out URL of our application
  9. Also from the Settings menu, add a Key with a duration of 1 or 2 years
    Note down the key, as you will not be able to retrieve it afterwards.

Additional steps required for your SPA

Authentication happens using OAuth2 protocol. Applications provisioned in AAD are not by default enabled to use OAuth2, so you need to explicitly opt-in to do so:

  1. Still in the Azure Portal and in the page of the application you created before, click on Manifest to open the manifest editor.
    Alternatively, you can download, edit and upload the manifest afterwards, but the inline manifest editor is much easier to use.
  2. Look for the oauth2AllowImplicitFlow setting, which by default is set to false. Set it to true and save the manifest
    "oauth2AllowImplicitFlow": true,

Implementing and configuring adal.js in your Angular 2 SPA – Overview

In general you will need to follow theses steps, I will explain them in detail further on:

  1. Acquire the adal.js resources
  2. Create a service that provides you with the AAD settings
  3. Create and use a routeguard
  4. Add the Adal services to your application and initialize them
  5. Create a component to login and logout

Acquire the adal.js resources

  1. If you’re using the Node Package Manager (npm) system, it’s as easy as executing 1 single command to pull in the ng2-adal package and all it’s dependencies
    npm install ng2-adal --saveYou can also pull in the ng2-adal package with another package manager or manually. Make sure to also pull in all the required dependencies.
  2. If you’re using a module loader like SystemJS, you will need to add the modules to its configuration file, like it is shown for the systemjs.config.js file for SystemJS:
    (function (global) {
      System.config({
        paths: {
          // paths configuration
        },
        map: {
          // existing map configuration
    
          // adal libraries
          'ng2-adal': 'npm:ng2-adal',
          'adal': 'npm:adal-angular/lib',
          'adal-angular': 'npm:adal-angular/lib',
        },
        packages: {
          // existing package configuration
    
          // adal packages
          'ng2-adal': { main: 'core.js', defaultExtension: 'js' },
          'adal-angular': { main: 'adal-angular', defaultExtension: 'js' },
          'adal': { main: 'adal.js', defaultExtension: 'js' }
        }
      });
    })(this);

Create a service that provides you with the AAD settings

This is a simple angular service that stores the AAD settings, so they are easily manageable and accessible

  1. create a file called secret.service.ts
    import {Injectable} from '@angular/core';
    
    @Injectable()
    export class SecretService {
        public get adalConfig(): any {
            return {
                tenant: '[your tenant]',
                clientId: '[a GUID, the application ID]',
                redirectUri: window.location.origin + '/',
                postLogoutRedirectUri: window.location.origin + '/'
            };
        }
    }

Create and use a routeguard

A route guard is used to control the routers behavior and returns true or false to indicate whether the route can be followed or not

  1. Create an authentication guard (LoggedInGuard.ts) and implement the canActivate() method that checks whether the user is authenticated over Adal, if authenticated returning true, otherwise navigating to a login page
    import { Injectable } from '@angular/core';
    import { Router, CanActivate } from '@angular/router';
    import { AdalService } from 'ng2-adal/core';
    
    @Injectable()
    export class LoggedInGuard implements CanActivate {
        constructor(private adalService: AdalService,
            private router: Router) { }
    
        canActivate() {
            if (this.adalService.userInfo.isAuthenticated) {
                return true;
            } else {
                this.router.navigate(['/login']);
                return false;
            }
        }
    }
  2. Protect the route with the authentication guard in your routing configuration (fragmented code sample shown):
    import { SecretService } from "./secret.service"; 
    import { AdalService } from "ng2-adal/core"; 
    import { LoggedInGuard } from './LoggedInGuard';
    
    // ...
    
    { path: 'protected', component: protectedComponent, canActivate: [LoggedInGuard] },
    
    // ...
    
    providers: [AdalService, SecretService, LoggedInGuard],

Add the Adal services to your application and initialize them

  1. In app.component.ts add following code to import the services
    import { Component, OnInit } from '@angular/core';
    import { SecretService } from './secret.service';
    import { AdalService } from "ng2-adal/core";
  2. Still in app.component.ts initialize the Adal service in the constructor with the settings stored in the Secret service
    export class AppComponent implements OnInit {
        profile: any;
      
        constructor(
            private adalService: AdalService,
            private secretService: SecretService) {
            this.adalService.init(this.secretService.adalConfig);
        }
    }
  3. To prevent the user having to log in every time again, the authentication token is stored in the browser cache. This allows us to try to retrieve this token and continue using the application without being redirected again to the Azure login page.
    Add following code to app.component.ts to get the user object from cache:

        ngOnInit(): void {
            this.adalService.handleWindowCallback();
            this.adalService.getUser();
        }

Create a component to login and logout

This is a very straightforward way to add a login and logout button to your application. The essence is to call the adalService.login() and adalService.logOut() functions. Integrate them in your application to meet your requirements:

  1. Create a login component (login.component.ts)
    import {Component, OnInit} from '@angular/core';
    import {Router} from "@angular/router";
    import {AdalService} from 'ng2-adal/core';
    
    @Component({
        selector: 'welcome',
        template: '<h1>You need to login first</h1><button (click)="logIn()">Login</button>'
    })
    export class LoginComponent {
    
        constructor(
            private router: Router,
            private adalService: AdalService
        ) {
            if (this.adalService.userInfo.isAuthenticated) {
                this.router.navigate(['/home']);
            }
        }
    
        public logIn() {
            this.adalService.login();
        }
    }

    If the user is already logged in with valid Azure Active Directory credentials, he will immediately be redirected to the /home page.
    Otherwise, the user is presented with the Azure login page to login first, and afterwards redirected to the home page URL you provided in the AAD application registration.

  2. Create a logout component (logout.component.ts)
    import {Component} from '@angular/core';
    import {AdalService} from 'ng2-adal/core';
    
    @Component({
        selector: 'logout',
        template: '<div protected><h1>This is the logout page.</h1><button (click)="logOut()">Logout</button></div>'
    })
    export class LogoutComponent {
    
        constructor(
            private adalService: AdalService
        ) {
        }
    
        public logOut() {
            this.adalService.logOut();
        }
    }

    This will sign out the user from Azure Active Directory, invalidate the users authentication token and redirect to the post logout URL.

  3. Add routes to the login and logout components
    import { LoginComponent} from './login.component';
    import { LogoutComponent} from './logout.component';
    { path: 'logout', component: LogoutComponent },
    { path: 'login', component: LoginComponent },
    declarations: [AppComponent, LoginComponent, LogoutComponent, /* ... */ ]

References

Posted by .Ronald on

How to create a zip archive and download it in ASP.NET

In a previous post How to download multiple files in ASP.NET, I explained how to generate multiple documents and offer them as separate downloads in ASP.NET. One of the options I had when looking for a solution to offer multiple downloads, was adding all the documents to 1 single zip archive container, and offer that as download to the user. This solution didn’t completely satisfy the end-users, but is also offered for those who want to use it.

In this post I will explain, how I take the same list of documents, and offer them as a zip archive to download. Starting from the multiple download solution, this only required 1 extra step in the process, namely, creating a zip archive and adding all the documents to it. The rest of the process is as described in the previous post.

The method takes the same argument as when creating separate download links, namely an a list of byte arrays. Each byte array in its turn contains the binary content of the document. I use the SharpZipLib from ICSharpCode, which can be downloaded here: The Zip, GZip, BZip2 and Tar Implementation For .NET. This is what this method looks like:


Private Function ZipDocuments(ByVal reports As IList(Of byte())) As Boolean

' Add documents to 1 ZIP file, and open in browser
Using zipOutMemoryStream As New MemoryStream()
Using zipOutStream As New ZipOutputStream(zipOutMemoryStream)

'Add documents to Zip File.
Dim cnt As Integer = 1
For Each buffer As byte() In reports
Dim entry As New ZipEntry(String.Format("{0}_{1}.pdf", "GeneratedFile", cnt))

zipOutStream.PutNextEntry(entry)
zipOutStream.Write(buffer, 0, buffer.Length)
zipOutStream.CloseEntry()
cnt += 1
Next

zipOutStream.Finish()
zipOutStream.Close()
zipOutMemoryStream.Close()

Dim responseBytes As Byte() = zipOutMemoryStream.ToArray()

'Return Null on Empty Zip File
Const ZIP_FILE_EMPTY As Integer = 22
If responseBytes.Length <= ZIP_FILE_EMPTY Then
Return Nothing
End If

RegisterDocumentDownload(Guid.NewGuid().ToString(), responseBytes, ContentTypes.ZIP)

End Using
End Using
End Function

I first create a (binary) memorystream (zipOutMemoryStream) to contain the content of the zip file (zipOutStream).
Then I loop over the list of documents (or files), create an entry in the zip file (entry as ZipEntry), and write the binary content to the zip entry.
After adding the files to the zip and cleaning up, I can use the same RegisterDocumentDownload() method from the previous post, and the zip archive will be added to the user and opened in the browser.

And that’s it…

Posted by .Ronald on

How to download multiple files in ASP.NET

The project I’m currently assigned to, already has an option to generate reports (pdf) which are just streams the binary output of the report generator to the response output stream. Something like this:


Dim binReader As New System.IO.BinaryReader(report.ExportToStream())

With Response
.ClearContent()
.ClearHeaders()
.ContentType = "application/pdf"
.AddHeader("Content-Disposition", "inline; filename=AankondigingControlesEnGevolgen.pdf")
.BinaryWrite(strStream.ReadBytes(CInt(strStream.BaseStream.Length)))
.Flush()
.Close()
End With

This piece of code streams the binary output of the report to the response object, and setting the right ContentType and Header, it opens the document in the user’s browsers. Works like a charm.

But now I was asked to create a form where the user can select multiple reports to download and open them in the browser. My first answer was: We can’t do that (easily). But then I started to look at the options we have when working in ASP.NET and generating output to the client browser.

The solution I ended up with was that easy, that I found myself kind of stupid that I didn’t think about it earlier. This is what I did:

  1. Generate the documents and store them (binary), together with a unique key, in a session variable
  2. Generate download links with that unique key as parameter
  3. Open the links with clientside javascript
  4. In the download page, retrieve the content from the session variable and stream it to the client browser

Let’s take a look at that in detail.

1. Generate the documents and store them (binary), together with a unique key, in a session variable

I created a custom class to hold the binary document content, together with extra information that can be helpful when generating the download:


Private Class ContentTypes
Public Const PDF As String = "application/pdf"
Public Const ZIP As String = "application/zip"
End Class

<Serializable()> _
Private Class Download
Public Name As String
Public Content() As Byte
Public ContentType As String
End Class

Name: The name of the file that is generated and is used when the user downloads the file (save to disk)
Content: The binary content of the file
ContentType: Because I don’t want to be limited to 1 specific file type, I include the content type with the download

Currently I’m only using 2 types of documents, but as you can see, this can be easily extended.

2. Generate download links with that unique key as parameter

For each document I created and stored, together with a unique key, in a session variable, I generated the client-side script to open a new window with the download link. Because I use the same page to download the document, I can create a URL starting with the querystring question mark:


Private Sub RegisterDocumentDownload(ByVal key As String, ByVal content() As Byte, ByVal contentType As String)
Dim script As String = String.Format("window.open('?key={0}');", key)
Dim download As New Download()
download.Content = content
download.ContentType = contentType
download.Name = key

Session.Add(key, download)
ScriptManager.RegisterStartupScript(Me, Me.GetType(), "Download_" & key, script, True)
End Sub

3. Open the links with clientside javascript

The JavaScript that is generated, will look something like this (when generating 3 downloads):


<script type="text/javascript">
//<![CDATA[
window.open('?key=ee00cb06-81f6-48d7-bed2-6cf0af90d5f8');
window.open('?key=a05d2567-5ab8-4c41-a000-bb0bd16498ca');
window.open('?key=ea7bb0aa-6686-442e-be6f-b14ac14aacf5');
//]]>
</script>

4. In the download page, retrieve the content from the session variable and stream it to the client browser

Because I use the same page to download the file as well, I added code to the Page_Load() event that checks for the “key” parameter


If Not Request.QueryString("key") Is Nothing Then
StreamDownload(Request.QueryString("key"))
Exit Sub
End If

This calls the StreamDownload() method which takes the download from the session, streams the content to the browser client and cleans up everything before ending processing


Private Sub StreamDownload(ByVal key As String)

Guard.ArgumentNotNull(Session(key), "download")

Dim download As Download = DirectCast(Session(key), Download)
Dim stream As New MemoryStream()
Dim formatter As New BinaryFormatter()

formatter.Serialize(stream, Session(key))
With Response
.Clear()
.ContentType = download.ContentType

Select Case download.ContentType
Case ContentTypes.ZIP
.AppendHeader("Content-Disposition", String.Format("filename={0}.zip", download.Name))
End Select

.BinaryWrite(download.Content)
.Flush()
End With

' cleanup temporary objects
Session.Remove(key)
Session.Remove(key & "_download")

Response.End()

End Sub

As you can see, I also have the possibility to generate zip archives. This is to offer the functionality of downloading multiple documents in 1 zip archive container. I could easily immediately offer this zip download from within the page. But I prefer to use this generic solution, even if I’m only offering 1 file to download. This also gives me the possibility to offer other file formats as well. I just need to add a new content type, and alter the code where needed in the StreamDownload() method.

In a next post, I will show how I created 1 zip archive which contains 3 documents, and offer this as a download to the user.

Posted by .Ronald on

Prevent caching of stylesheet and javascript files

First something about caching

The numerous caching options you have in ASP.NET (MVC) are mainly focused on data and page output caching. But caching also occurs at the webserver, network and browser level.  These you can’t always control from within your code.

When your content leaves your application, it is processed by the webserver, depending on the server and version it has numerous options to control how and when it is cached. When your content is processed by the webserver and sent to the browser, there is also the network that can control the caching, namely proxy and web acceleration servers. Finally the content arrives in the browser and the browser itself has also numerous options related to caching. Generally spoken, they all use the same parameters, or at least some of them, to determine when, what and how long the content should be cached.

How does this caching work? Generally spoken, following rules apply:

  1. If the response header says not to cache, it doesn’t cache
  2. If we use a secure or authenticated transfer, like HTTPS, it doesn’t cache either
  3. If the cache expiring time or any other age-controlling header says it’s still ‘fresh’, it doesn’t cache
  4. If there’s an old version in the cache, the server will be asked to validate the version.  If the version is still good, it is served from the cache.
  5. Sometimes when the server cannot be reached due to network failure or disconnectivity, the content is also directly served from the cache.

Then what parameters are used, and how are they used?

  • HTTP Headers: these are sent in the request, but are not visible in the content
    • Expires: tells the cache how long the content stays fresh. After that time, the cache will always check back with the server. It uses an HTTP date in Greenwich Mean Time (GMT), any other or invalid format will be interpreted as in the pas and makes the content uncachable.  For static data you can set a time in the very far future, for highly dynamic content, you can set a time much closer, or even in the past to have the cache refresh the content more often or at every request.
    • Cache-Control: In response to some of the drawbacks of the Expired header, the Cache-Control header class was introduced. It includes (some, not all):
      • max-age=[seconds]
      • public / private
      • no-cache / no-store
      • must-revalidate
    • Pragma: no-cache: the HTTP specifications aren’t clear of what it means, so don’t rely on it, use the ones above
  • HTML meta tags: Unlike HTTP Headers, HTML meta tags are present in the visible content, more precisely in the <HEAD> section of your HTML page. A huge drawback of the us of HTML meta tags is, is that they can only be interpreted by browsers, and not all of them use them like you would expect. So prefer HTTP headers over HTML meta tags

A great Caching Tutorial can be found here: http://www.mnot.net/cache_docs/, and another one here: Save Some Cash: Optimize Your Browser Cache

An easy solution

Now, all of the caching systems rely in some way on the full request string to identify the content that is being cached.

So, the easiest solution would be to request a new unique URL every time the resource has changed, with a new version number.

How we do it in ASP.NET MVC

ASP.NET MVC (and ASP.NET Webforms also) doesn’t generate a new version number automatically.  You need to tell it to do so in the AssemblyInfo.cs file.  After a default project setup it contains a line like:

[assembly: AssemblyVersion("1.0.0.0")]

The version number is a four-part string with the following format: <major version>.<minor version>.<build number>.<revision>.  You usually set the major and minor version manually, as they are used as the type library version number when the assembly is exported, and don’t (need to) care of the build and revision number.  Well, now we do.

When you change this line to (or add it if it doesn’t exist):

[assembly: AssemblyVersion("1.0.*")]

We tell the compiler to generate a build and revision number for us. The generated build number is the number of days since 1-01-2000 (so 9-08-2010 gives 3873) and the revision number is the number of two second intervals since midnight local time (so a build at 11:59:12 gives 19776).

Now we have instructed our application to generate a new unique build number for us with every build, and every (possible) change of a resource, we can use this number as a unique parameter value in the URL of the the resource.

First we need to pass this version number from controller to view.  In the constructor of the (base)controller we put the version number in the ViewData Dictionary. With the ViewData you easily can pass data from the controller to the view using a key-value pattern.

protected BaseController(){
ViewData["version"] = Assembly.GetExecutingAssembly().GetName().Version;
}

And finally in the view, all you need to do is append this version number to the URL of the files you want to be prevented from caching:

<script type="text/javascript" language="javascript" src="<%: Url.Content("~/Scripts/commonFunctions.js?" + ViewData["version"]) %>"></script>

This makes sure we have a unique URL for our resources and they are not cached by the browser or a proxy.

Of course, like stated above, there are other ways of preventing files from being cached anywhere between the server and the browser, but the advantage of this method is that you don’t need to poke around in IIS settings (in case when you don’t have access to it) and you can define when and which version of the file you want to be cached.  And you can of course use any other method to generate a unique URL.

One more remark: When building a multi-tier application, make sure you set the version number in the AssemblyInfo.cs of the project where you use it, meaning, that if you put your base controller in a shared assembly, you need to specify the version number in the shared assembly project.

Posted by .Ronald on

Design for Developers

A few years ago I had the opportunity to sit down with a designer possessed of a rare talent. We were both part of the same team and he was creating some UI elements that I was to wire up. As I sat there (in awe) watching him work I realized that much of his considerable skill was rooted in fundamentals not unlike the art of programming. Of course, there are design skills that are intuitive that can’t be "learned." But, that can also be said of the logical clarity found in a really elegant data model or a brilliant inheritance tree. I am certainly no designer, but I have observed the more creative among us for several years and have gained some insight into their world. In this article I’ll share some basic principles that can help raise your design acumen and improve the experience of your users.

SYS-CON MEDIADesign for Developers

Posted by .Ronald on

Performance Optimization of ASP.NET Applications on Client-side

With the advent of new web applications with rich user interface, there is a lot of processing on the client side. Most of this processing is done using scripting languages like JavaScript, etc. Lot of new frameworks are in the market ranging from the ones doing simple manipulation of DOM to the most complex of templates for data and charts. Not only web 2.0 applications like Facebook, myspace are using this technology extensively, but line of business applications are also moving towards this model. Ajax technology provides users with a whole new experience which is very close to desktop applications.

New browsers are concentrating on this model as well with the introduction of new faster JavaScript engines. Chrome’s interface is done in a way that gives the impression of a stand-alone application running on top of desktop instead of an application opened in a browser.

This new model has given rise to many challenges in terms of performance of web applications. Client side frameworks and other resources like custom JavaScript files, style sheets, images, etc. are downloaded on the browser and if not managed properly, can really affect the performance of an application.

This article is more oriented towards how to improve performance of ASP.NET Ajax applications on the client side.

CodeProjectPerformance Optimization of ASP.NET Applications on Client-side

Posted by .Ronald on

Opera: Web Standards Curriculum

As the most standards-compliant Web browser, Opera is dedicated to promoting Web standards across the globe. Web standards make the Web available to anyone, on any device, anywhere in the world.

Opera has created the Web Standards Curriculum (WSC) in association with the Yahoo! Developer Network. This tutorial course takes students from complete beginner to having a solid grounding in standards-based Web design, including HTML, CSS, and JavaScript development. The course is supported by top companies and organizations such as the Web Standards Project (WaSP) and Yahoo!.

Split into more than 50 focused articles, students can follow the curriculum from start to finish or simply read articles that interest them the most. Each article contains essential theory, practical examples, and exercise questions. The first 41 articles are now published, and roughly ten ones covering JavaScript basics will follow ASAP, to complete the course.

Why should you incorporate the Opera WSC into your curriculum? Web standards in a Web site promote efficiency, ease of maintenance, accessibility, device compatibility, and search optimization. The Opera WSC features the most up-to-date practices in Web standards. Best of all, the course is free, requiring no expensive textbooks.

OperaWeb Standards Curriculum

Posted by .Ronald on

Migrate apps from Internet Explorer to Mozilla

When Netscape started the Mozilla browser, it made the conscious decision to support W3C standards. As a result, Mozilla is not fully backwards-compatible with Netscape Navigator 4.x and Microsoft Internet Explorer legacy code; for example, Mozilla does not support as I will discuss later. Browsers, like Internet Explorer 4, that were built before the conception of W3C standards inherited many quirks. In this article, I will describe Mozilla’s quirks mode, which provides strong backwards HTML compatibility with Internet Explorer and other legacy browsers.

MDCMigrate apps from Internet Explorer to Mozilla