views:

4697

answers:

7

In normal WebForms scenario, any root-relative URLs (e.g. ~/folder/file.txt) inside CSS files such as:

.form { background-image: url(~/Content/Images/form_bg.gif); }

will automatically get resolved during runtime if I specify

<head runat="server">

In the referencing page.

However, that is no longer happening on an ASP.NET MVC Beta1 website.

Is there a way I could enable this functionality without resorting to hacks or CSS-loader file? Like maybe HttpModules or something?

Or am I not desigining my website correctly? What is supposed to be a good design?

Since original ASP.NET WebForms already has this feature, I'd prefer to utilize any existing functionality if possible. But I don't have much clue.

This web application will be deployed in several environments where the ~ root folder might not be obvious.


EDIT: I mean the url in the file's CONTENT not the file's url itself.

A: 
thismat
i don't think this answers the question; it looks like chakrit is looking to resolve app-relative references _within_ the CSS file, which i can only think to solve by preprocessing.
David Alpert
ah.. I've clarified my question
chakrit
My mistake, I misunderstood the question.
thismat
+2  A: 

Here are some resources on implementing IHttpModule to intercept web requests to your app...

Write/adapt one to check for filetype (e.g. pseudocode: if (request ends with ".css") ...)

then use a regular expression to replace all instances of "~/" with System.Web.VirtualPathUtility.ToAbsolute("~/")

I don't know what this will do to performance, running every request through this kind of a filter, but you can probably fiddle with your web.config file and/or your MVC URL routes to funnel all .css requests through this kind of a filter while skipping past it for other files.

Come to think of it, you can probably achieve the same effect inside an ASP.NET MVC app by pointing all your CSS refrences at a special controller.action that performs this kind of preprocessing for you. i doubt that would be as performant as an IHttpModule though.

David Alpert
A: 

You could use an URL Rewriter to fix the URL as the request comes in, though I am not so sure it is so much elegant as a hack in this case.

Greg Ogle
Ah nice idea! I could use the Routing framework to do that. let me try
chakrit
A: 

I created a PathHelper util class that gives me all the paths I need. For example

<link href="<%=PathHelper.CssUrl("FormulaIndex.css")%>" rel="Stylesheet" type="text/css"/>

Gives me the correct full url with the help of System.Web.VirtualPathUtility.ToAbsolute() and my own convention (content/css/yourFile.css).

I did the same for js, xml, t9n, pics... Its central, reusable and now I only had to change one line to catch the move of the scripts folder from content/js to Scripts in all my websites and pages.

A moronic move if you ask me, but it's reality in the current beta :(

borisCallens
I mean Urls in the file's CONTENT not the file url itself.
chakrit
I c .
borisCallens
+1  A: 

If you're trying to parse the ~/ out of any file, including text files, javascript, etc, you can write a handler that assigns a filter to it and you can use that to search for those paths... for example...

public class StringParsingFilter : MemoryStream {

    public Stream OriginalStream {
        get { return this.m_OriginalStream; }
        set { this.m_OriginalStream = value; }
    }
    private System.IO.Stream m_OriginalStream;

    public StringParsingFilter() : base() {
        this.m_OriginalStream = null;
    }

    public override void Flush() {
        this.m_OriginalStream.Flush();
    }

    public override void Write(byte[] buffer, int offset, int count) {

        //otherwise, parse for the correct content
        string value = System.Text.Encoding.Default.GetString(buffer);
        string contentType = HttpContext.Current.Response.ContentType;

        //Do any parsing here
        ...

        //write the new bytes to the stream
        byte[] bytes = System.Text.Encoding.Default.GetBytes(value);
        this.m_OriginalStream.Write(bytes, offset, count + (bytes.Length - buffer.Length));

    }

}

And you'll write a custom handler to know when to assign this filter... like the following...

 public class FilterControlModule : IHttpModule {

    public void Init(HttpApplication context) {
        HttpApplication oAppContext = context;
        oAppContext.BeginRequest += new EventHandler(_HandleSettingFilter);                        
    }

    private void _HandleSettingFilter(object sender, EventArgs e) {

        //You might check the file at this part to make sure
        //it is a file type you want to parse
        //if (!CurrentFile.isStyleSheet()) { return; }
        ...

        //assign the new filter
        StringParsingFilter filter = new StringParsingFilter();
        filter.OriginalStream = HttpContext.Current.Response.Filter;
        HttpContext.Current.Response.Filter = (Stream)filter;

    }

}

It may have actually been easier just to say "look up IHttpModules" but this is some code that I've used to parse files for paths other than ASP.net files.

You'll also have to change some things in your IIS settings to allow the files to be parsed by setting the ASP.net ISAPI to be a wildcard for all of the files that get handled. You can see more at this website, if you're using IIS6 that is...

You can also use this to modify any file types so you could assign some filters for images, some for javascript or stylesheets or ... really anything...

Hugoware
Thanks! I already have wildcard mappings done first thing. I know about http modules but since ASP.NET web form *already* has this so I was thinking that there must be a simpler way.. like linking some existing ASP.NET web form modules that's stripped from MVC. Thanks anyway I could use your code
chakrit
+11  A: 

I would not bother with the auto-root-finding ~ character. I understand that you want the same solution to work where the root directory differs between deployments, but within the CSS document you shouldn't have any problems using relative paths. The paths in the CSS document (to the image URL in your example) will always be relative to the location of the CSS file regardless of the path of any page that loads that CSS file. So if your images are in ~/Content/Images and your stylesheets are in ~/Content/Stylesheets, you'll always be able to use background-image: url(../Images/form_bg.gif); and it will work regardless of the location of the page that loads the stylesheet.

Is there a reason this wouldn't work?

David Kolar
Ah... I was stupid yet again. This should works. I'll try it out. I was used to having tilde (~) relative paths in CSS in web forms. And it always work so I was expecting the same for MVC but duh....
chakrit
It has some problems with old Mac browsers... but I don't think anyone supports them anymore.
chakrit
It also has some problems with old Netscape versions that parsed that background-image style rule in a non-standard way, which is the last time that i ran into this issue, hence my suggestion to use an IHttpModule. In the end, however, perhaps this will do as that Netscape is likely long gone.
David Alpert
It's been a while since this question was asked and since I read it. It's been long enough that I forgot the answer and appreciated being able to find it again. Thanks.
Jeff Siver
+3  A: 

One trick I have used in the past, was to actually make my CSS file have a .ASPX extension, and set the ContentType property in the page signature:

<%@ Page Language="C#" ContentType="text/css" %>

body {
    margin: 0;
    padding: 0;
    background: #C32605 url(<%= ResolveUrl("~/Content/themes/base/images/BodyBackground.png") %>) repeat-x;
    font-family: Verdana, Arial, sans-serif;
    font-size: small;
    color: #d7f9ff;
}

This will ensure that the CSS file goes through the ASP.NET framework, and replaces the server side code with your relative path.

David P