ASP.net: CSS, Images and IIS Virtual Directory Application Deployment

Have you ever tried to deploy an application to a virtual directory in IIS? For example lets say you have your primary website running at http://www.graphicnetdesign.com and you decide it would be nice to have a sub-domain where you can host all of your utility and experimental applications so you create http://dev.graphicnetdesign.com. It is under this sub-domain where you have created virtual directories in IIS to host each of those applications. So you set up a virtual directory for a little application you are playing around with which checks the heart beat of all your websites and web applications and you name it ippoke and your new application’s URL looks like http://dev.graphicnetdesign.com/ippoke.

All excited you quickly FTP your new application to that virtual directory for a little smoke testing. But, sadly, the application which was developing so nicely on your development machine looks like crap. It appears as though your CSS is not loading and if it is it looks like all of the Images embedded in that CSS file are not loading. The Horror! You quickly double check all of your references to the CSS file, you check all of the image URLs within the CSS file and everything looks perfect and it all runs perfectly on your dev machine. What is going on?

After hours of frustration and Google searches you find an article which states that under IIS when using virtual directories to host ASP.net applications IIS does not use the virtual directory as the root of your newly deployed application, instead it uses the root of the main site or http://dev.graphicnetdesign.com. What does that all mean, it means that even though the path to your logo.png file in your ippoke application is ‘/images/logo.png’ and that works on your dev machine and is relative to the root of your application on that machine once deployed to the virtual directory the path required to get that image is has to now be relative to the root of the site under which you created the virtual directory in which your ippoke application is running. In other words the relative path of ‘/images/logo.png’ is no longer a correct path to that image because it is now relative to the root site and resolves to http://dev.graphicnetdesign.com/images/logo.png. In order to make your CSS and images work correctly in the virtual directory you have to change all of your references to resolve from the root so the path needs to be ‘/ippoke/images/logo.png’ instead of ‘/images/logo.png’.

What a pain! So now you have to come up with some way to support the differences between your local dev environment where the site is being developed as a root website in visual studio and what it will eventually be deployed as. In Visual Studio you can actually change the virtual path in the properties of the web application project and there are several other ways you can try to resolve the issue by using separate CSS files for each environment, or by editing the CSS file just before deployment but all of these issues just make more work and make maintenance of the site horrifying. They also do not make the application portable without a lot of editing, for example if I decided to move the application to be its own root site or if I want to change it to a different virtual directory or deploy the site to a completely different server.

The only way I have found to combat the virtual directory CSS and Image bug when it comes to ASP.net applications was to simply create my own custom ASP.net Server Control which is smart enough to know when the application has been deployed as a root site or as a virtual site and armed with that knowledge it creates a copy of the CSS file and updates all image references with the correct virtual directory path so the CSS and Images can be resolved by IIS. This control checks to see if a virtual CSS file exists already and if not it will create one and write it to disk on the first call to the application. If the file already exists it will check to see if the original CSS file has been recently updated and if so the control will create a new version of the virtual CSS in order to capture the changes. Once it has taken care of building the virtual CSS file it then writes out a new tag for the virtual CSS file to the page and that is that. No more missing images, no more missing CSS files and it just works when deployed as either a root site or within a virtual directory.

The code for this control is below, feel free to use it.

Version:0.9 StartHTML:0000000105 EndHTML:0000024318 StartFragment:0000000105 EndFragment:0000024318

using System;

using System.ComponentModel;

using System.IO;

using System.Web;

using System.Web.UI;

using System.Web.UI.WebControls;

namespace GraphicNetDesign.ServerControls.Web.UI

{

    [DefaultProperty("Href")]

    [ToolboxItem(true)]

    [ToolboxData("")]

    public class CssVirtualLink : WebControl

    {

        #region Private Fields

        private string _virtualPath;

        private string _originalCssRelativePath;

        private string _originalCssDirectoryPath;

        private string _virtualCssRelativePath;

        private string _virtualCssDirectoryPath;

        private FileInfo _originalCssFile;

        private FileInfo _virtualCssFile;

        #endregion

        #region Public Properties

        [Bindable(true)]

        [Category("Appearance")]

        [DefaultValue("")]

        [Localizable(true)]

        public string Href

        {

            get

            {

                String s = (String)ViewState["href"];

                return (s ?? "[" + ID + "]");

            }

            set

            {

                ViewState["href"] = value;

            }

        }

        #endregion

        #region Protected Methods

        /// 

        /// Renders the contents.

        /// 

        /// The output.

        protected override void RenderContents(HtmlTextWriter output)

        {

            output.WriteBeginTag("link");

            output.WriteAttribute("rel", "stylesheet");

            //Update the CSS file image paths to match virtual directory

            UpdateFile();

            output.WriteAttribute("href", Href);

            output.WriteEndTag("link");

        }

        #endregion

        #region Private Methods

        protected void UpdateFile()

        {

            //Get application's virtual path

            GetApplicationVirtualPath();

            //Get Original CSS file and its actual path

            GetOriginalCssPaths();

            //Build VCSS file path

            GetVirtualCssPaths();

            //Check to see if the CSS file exists

            if (!_originalCssFile.Exists)

                return;

            //Check to see if the Virtual CSS file exists

            if (!_virtualCssFile.Exists || CompareModifiedDates())

                CreateVirtualCssFile();

            //Update Href to the Virtual CSS file

            Href = _virtualCssRelativePath;

        }

        private void GetApplicationVirtualPath()

         {

             _virtualPath = HttpRuntime.AppDomainAppVirtualPath;

             if (!_virtualPath.EndsWith("/"))

                 _virtualPath += "/";

             Page.Trace.Warn("VCSS", "Application Virtual Path: " + _virtualPath);

         }

        private void GetOriginalCssPaths()

        {

            _originalCssRelativePath = (_virtualPath + Href).Replace("//", "/");

            _originalCssDirectoryPath = Page.Server.MapPath(_originalCssRelativePath);

            _originalCssFile = new FileInfo(_originalCssDirectoryPath);

            Page.Trace.Warn("VCSS", "Original CSS Relative Path: " + _originalCssRelativePath);

            Page.Trace.Warn("VCSS", "Original CSS Directory Path: " + _originalCssDirectoryPath);

            Page.Trace.Warn("VCSS", "Original CSS File Exists: " + _originalCssFile.Exists);

        }

        private void GetVirtualCssPaths()

        {

            _virtualCssRelativePath = (_virtualPath + Href.Replace(".css", "_virtual.css")).Replace("//", "/");

            _virtualCssDirectoryPath = Page.Server.MapPath(_virtualCssRelativePath);

            _virtualCssFile = new FileInfo(_virtualCssDirectoryPath);

            Page.Trace.Warn("VCSS", "Virtual CSS Relative Path: " + _virtualCssRelativePath);

            Page.Trace.Warn("VCSS", "Virtual CSS Directory Path: " + _virtualCssDirectoryPath);

            Page.Trace.Warn("VCSS", "Virtual CSS File Exists: " + _virtualCssFile.Exists);

        }

        private bool CompareModifiedDates()

        {

            if (_originalCssFile.LastWriteTime > _virtualCssFile.LastWriteTime)

                return true;

            return false;

        }

        private void CreateVirtualCssFile()

        {

            try

            {

                if (_virtualCssFile.Exists)

                    _virtualCssFile.Delete();

                Page.Trace.Warn("VCSS", "Loading original CSS file for copy.");

                string contents;

                using (FileStream fs = new FileStream(_originalCssDirectoryPath, FileMode.Open, FileAccess.Read))

                {

                    using (StreamReader sr = new StreamReader(fs))

                    {

                        contents = sr.ReadToEnd();

                        sr.Close();

                    }

                    fs.Close();

                }

                Page.Trace.Warn("VCSS", "Original CSS file successfully loaded.");

                Page.Trace.Warn("VCSS", "Creating Virtual CSS file from Original CSS file.");

                using (StreamWriter sw = new StreamWriter(_virtualCssDirectoryPath, false))

                {

                    //Update all URL elements in the CSS file 

                    //to reflect the correct virtual path

                    sw.Write(contents.Replace("url(\"/", "url(\"" + _virtualPath));

                    sw.Close();

                }

                Page.Trace.Warn("VCSS", "Virtual CSS file creation complete.");

            }

            catch (Exception ex)

            {

                Page.Trace.Warn("VCSS", "Exception Thrown: " + ex.Message);

            }

        }

        #endregion

    }

}

Published by

Tim Clark

Experienced Business Owner, Chief Information Officer, Vice President, Chief Software Architect, Application Architect, Project Manager, Software Developer, Senior Web Developer, Graphic Designer & 3D Modeler, University Instructor, University Program Chair, Academic Director. Specialties: Ruby, Ruby on Rails, JavaScript, JQuery, AJAX, Node.js, React.js, Angular.js, MySQL, PostgreSQL, MongoDB, SQL Server, Responsive Design, HTML5, XHTML, CSS3, C#, ASP.net, Project Management, System Design/Architecture, Web Design, Web Development, Adobe CS6 (Photoshop, Illustrator)

3 thoughts on “ASP.net: CSS, Images and IIS Virtual Directory Application Deployment

  1. Dear Friends,

    I hope you are doing well. I have launched a web site http://www.codegain.com and it is basically aimed C#,JAVA,VB.NET,ASP.NET,AJAX,Sql Server,Oracle,WPF,WCF and etc resources, programming help, articles, code snippet, video demonstrations and problems solving support. I would like to invite you as an author and a supporter. Looking forward to hearing from you and hope you will join with us soon.

    Please forward this email to all of your friends who are related IT. Send to us your feed about site also.

    Thank you
    RRaveen
    Founder CodeGain.com

    Like

  2. When you use “/ippoke/images/logo.png”, the web browser will show the link as you typed it up. With the standard server controls (ASP:Image, ASP:Hyperlink, etc.), “~/” will set the path from the Virtual Directory. So if you say you used this markup,

    ASP.NET would prepend the virtual directory for you rendering an accurate location of your files.

    Also, It helps a lot to manage your styles and images using themes, even if you know your only going to use one design. That helps you separate business logic from design much more easily

    http://msdn.microsoft.com/en-us/library/ykzx33wh.aspx

    Thanks for the the tutorial by the way. I did need to use the virtual directory for one of my own custom server controls, and your tutorial did help a lot.

    Like

  3. You are 100% correct and you make a good point. Using ASP.net server controls with ~/ in the path the virtual directory issue will be taken care of automatically for you.

    But to make sure there is no confusion in this particular post/tutorial I am discussing the fact that image paths contained within your CSS as background images are not handled automatically by ASP.net and pose a problem when deploying ASP.net applications to virtual directories within IIS.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s