What is the correct way to handle embedded resources in Razor View mode?

I am porting some code from the ASPX viewer to Razor, and I stumbled upon a checkpoint.

I have this code:

<link rel="Stylesheet" type="text/css" href=" <%=Page.ClientScript.GetWebResourceUrl (typeof(DotNetOpenAuth.OpenId.RelyingParty.OpenIdSelector), "DotNetOpenAuth.OpenId.RelyingParty.OpenIdSelector.css")%>" /> 

The problem is that with Razor I don't have the page property.

So, I took a step back a second, and I look at it, wondering: what is the right way to get embedded resources in Razor?

I spent a lot of time finding solutions on this issue, but I really did not find anything but "wrap a new page in the assistant"

Is this the only way to do this? Or is there something more correct?

+6
asp.net-mvc-3 razor embedded-resource
source share
2 answers

Unfortunately, web resources are completely tied to the web form infrastructure and cannot be reused without it. So a little hack, but you could write an assistant:

 public static class UrlExtensions { public static string WebResource(this UrlHelper urlHelper, Type type, string resourcePath) { var page = new Page(); return page.ClientScript.GetWebResourceUrl(type, resourcePath); } } 

and in your razor mode:

 <link rel="stylesheet" type="text/css" href="@Url.WebResource(typeof(DotNetOpenAuth.OpenId.RelyingParty.OpenIdSelector), "DotNetOpenAuth.OpenId.RelyingParty.OpenIdSelector.css")" /> 

Another possibility is to write a custom HTTP handler / controller that will read the embedded resource from the assembly and transmit it in response, setting the correct content type.

+7
source share

Besides calling new Page()... , you can directly call the base implementation. Put this code in some static class:

 public static string GetWebResourceUrl(this Assembly assembly, string name) { if (GetWebResourceUrlInternal == null) GetWebResourceUrlInternal = (Func<Assembly,string,bool,bool,System.Web.UI.ScriptManager,string>) typeof(System.Web.Handlers.AssemblyResourceLoader) .GetMethod("GetWebResourceUrlInternal", BindingFlags.NonPublic|BindingFlags.Static, null, new[]{typeof(Assembly),typeof(string),typeof(bool),typeof(bool),typeof(System.Web.UI.ScriptManager)}, null) .CreateDelegate(typeof(Func<Assembly,string,bool,bool,System.Web.UI.ScriptManager,string>)); return GetWebResourceUrlInternal(assembly, name, false, false, null); } volatile static Func<Assembly,string,bool,bool,System.Web.UI.ScriptManager,string> GetWebResourceUrlInternal = null; 

And in Razor mode or in code it is used:

 typeof(SomeClassInTheSameAssembly).Assembly.GetWebResourceUrl("Namespace.Resource.xxx") 

Of course, using WebResource URLs in Razor views is not very useful. Instead, it is recommended that you place the resource in the Content or Scripts folder of your MVC application directly.

But the situation changes if you want to write HtmlHelper functions in shared class libraries that cannot put content in the target project.


Justification

This approach basically avoids creating a new, bulky Page object with every call to GetWebResourceUrl .

Looking at the source code , it turns out that the Page and ScriptManager contexts are easy to use. Therefore, calling AssemblyResourceLoader.GetWebResourceUrlInternal will directly hit the nail on the head. All you need to create a working WebResource.axd URL is Assembly and, of course, the name of the resource .

The disadvantage is that this function is internal, so it must be caused by reflection. However, the aforementioned implementation avoids the overhead of a function call by reflection every time. Instead, CreateDelegate used once to get a regular delegate that can be called with almost no overhead.

A race condition is assumed when accessing the GetWebResourceUrlInternal field. This does not do much harm, because it is very unlikely that this piece of code will encounter many parallel threads on the first call, and even if this happens, the result is still reliable.

0
source share

All Articles