Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
West Wind WebSurge - Rest Client and Http Load Testing for Windows

Getting a Web Resource Url in non WebForms Applications


:P
On this page:

WebResources in ASP.NET are pretty useful feature. WebResources are resources that are embedded into a .NET assembly and can be loaded from the assembly via a special resource URL. WebForms includes a method on the ClientScriptManager (Page.ClientScript) and the ScriptManager object to retrieve URLs to these resources.

For example you can do:

ClientScript.GetWebResourceUrl(typeof(ControlResources), ControlResources.JQUERY_SCRIPT_RESOURCE);

GetWebResourceUrl requires a type (which is used for the assembly lookup in which to find the resource) and the resource id to lookup. GetWebResourceUrl() then returns a nasty old long URL like this:

WebResource.axd?d=-b6oWzgbpGb8uTaHDrCMv59VSmGhilZP5_T_B8anpGx7X-PmW_1eu1KoHDvox-XHqA1EEb-Tl2YAP3bBeebGN65tv-7-yAimtG4ZnoWH633pExpJor8Qp1aKbk-KQWSoNfRC7rQJHXVP4tC0reYzVw2&t=634533278261362212

While lately excessive resource usage has been frowned upon especially by MVC developers who tend to opt for content distributed as files, I still think that Web Resources have their place even in non-WebForms applications. Also if you have existing assemblies that include resources like scripts and common image links it sure would be nice to access them from non-WebForms pages like MVC views or even in plain old Razor Web Pages.

Where's my Page object Dude?

Unfortunately natively ASP.NET doesn't have a mechanism for retrieving WebResource Urls outside of the WebForms engine. It's a feature that's specifically baked into WebForms and that relies specifically on the Page HttpHandler implementation. Both Page.ClientScript (obviously) and ScriptManager rely on a hosting Page object in order to work and the various methods off these objects require control instances passed. The reason for this is that the script managers can inject scripts and links into Page content (think RegisterXXXX methods) and for that a Page instance is required. However, for many other methods - like GetWebResourceUrl() - that simply return resources or resource links the Page reference is really irrelevant.

While there's a separate ClientScriptManager class, it's marked as sealed and doesn't have any public constructors so you can't create your own instance (without Reflection). Even if it did the internal constructor it does have requires a Page reference. No good…

So, can we get access to a WebResourceUrl generically without running in a WebForms Page instance?

We just have to create a Page instance ourselves and use it internally. There's nothing intrinsic about the use of the Page class in ClientScript, at least for retrieving resources and resource Urls so it's easy to create an instance of a Page for example in a static method.

For our needs of retrieving ResourceUrls or even actually retrieving script resources we can use a canned, non-configured Page instance we create on our own. The following works just fine:

public static string GetWebResourceUrl(Type type, string resource )
{
    Page page = new Page();            
    return page.ClientScript.GetWebResourceUrl(type, resource);
}

A slight optimization for this might be to cache the created Page instance. Page tends to be a pretty heavy object to create each time a URL is required so you might want to cache the instance:

public class WebUtils
{
    private static Page CachedPage
    {
        get
        {
            if (_CachedPage == null)
                _CachedPage = new Page();
            return _CachedPage;
        }
    }
    private static Page _CachedPage;

    public static string GetWebResourceUrl(Type type, string resource)
    {
        return CachedPage.ClientScript.GetWebResourceUrl(type, resource);
    }
}

You can now use GetWebResourceUrl in a Razor page like this:

<!DOCTYPE html>
<html
    <head>
        <script src="@WebUtils.GetWebResourceUrl(typeof(ControlResources),ControlResources.JQUERY_SCRIPT_RESOURCE)">
</
script
> </head> <body> <div class="errordisplay"> <img src="@WebUtils.GetWebResourceUrl(typeof(ControlResources),ControlResources.WARNING_ICON_RESOURCE)" /> This is only a Test! </div> </body> </html>

And voila - there you have WebResources served from a non-Page based application.

WebResources may be a on the way out, but legacy apps have them embedded and for some situations, like fallback scripts and some common image resources I still like to use them. Being able to use them from non-WebForms applications should have been built into the core ASP.NETplatform IMHO, but seeing that it's not this workaround is easy enough to implement.

Posted in ASP.NET  MVC  

The Voices of Reason


 

Jarrett
October 17, 2011

# re: Getting a Web Resource Url in non WebForms Applications

I suggest that the GetWebResourceUrl should be an extension method on UrlHelper.

public static string WebResource(this UrlHelper url, Type type, string resource)

Rick Strahl
October 17, 2011

# re: Getting a Web Resource Url in non WebForms Applications

@Jarret - yes that's nice, but that only works for MVC. I think this should be a universally accessible function in ASP.NET (ie. off the HttpContext).

Jeff
November 26, 2013

# re: Getting a Web Resource Url in non WebForms Applications

Is there a way to get a WebResourceUrl without a request? I'm trying to get an embedded resource into a bundle using the web optimization bundling & minification. Since it's not built into it I was trying to do a bundle.include and getting the resource URL using your trick above. But the issue I run into is that the code in the RegisterBundles is run on application start and when I create a "Page" object and call the GetWebResourceUrl I get the exception "Request is not available in this context".

I tried digging into the GetWebResourceUrl code a little and I thought it looked like if I had a ScriptManager in the page that it wouldn't check the request but adding one to Page.Controls or Page.Items did nothing.

Any information on how I can get this working? Or do you know of any way to get an embedded resource from one project into a bundle in another project?

Thanks!

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024