Using ASP.NET 2.0 Web Resources in WebParts
[wait…is this a (*gulp*) technical post? sheesh…when’s the last time I actually posted dev/technical content? I was shocked to find that it was March 2005, when I posted three fairly useful items — more than a year ago! Well, then…let’s get going, and let’s get something started, eh?]
Web Resources
Way back in the far-distant past, back when all developers had to work with was ASP.NET 1.1 (lo,many ages hence), those developers who wanted to include actual resources — static files such as images, CSS, etc. — with their Web Parts would have to package up the files into an installer package to be included with the Web Part library assembly. Lucky for us, ASP.NET 2.0 comes with a slick new feature called Web Resources. Web Resources allow resources to be embedded into the assembly and referenced using a special URL (a nice long convoluted querystring, as you’ll see, but easily referenced via some code magic) from within a web project. SharePoint developers working with ASP.NET 2.0 Web Parts — for example, those of you who are using pre-Beta 2 releases of Windows SharePoint Services 3.0 or Microsoft Office SharePoint Server 2007 (MOSS), and those of you who will be using the Beta 2 release when it shows up someday soon — can take advantage of the new Web Resources feature to wrap up static files into Web Part assemblies and clean up install processes quite a little bit. Web Resources include a number of other cool little tricks that make deploying Web Parts with embedded resources simpler and more robust, as well. Plus, you get to use a cool new thingy — and ain’t that all right, too?
In this post, I’m going to create a Web Part of negligible utility — well…okay, I’m going to create a totally pointless Web Part that will, nonetheless, demonstrate a couple of things that can be done with Web Resources, and hopefully serve as a starting point for those of you out there that have actual problems to solve. :-)
All that my Web Part will do is display an image, and have a bit of client-side script that does a rollover effect when you mouse over the image. I’m not even going to get so fancy as to have the image be interesting or functional. We’re just going to have two embedded image resources and one embedded script resource, and have a nice simple Web Part assembly at the end of the day. I’ll be writing a pure ASP.NET 2.0 Web Part — inheriting from the System.Web.UI.WebControls.WebParts.WebPart class — rather than doing anything SharePoint-specific. This will be the recommended approach with WSS 3.0 and MOSS 2007 as well — unless a handful of WSS 2.0/SPS 2003 Web Part relics (V2–style Web Part connections, for example…though I have no idea why you’d use those for a new Web Part in WSS 2.0 or MOSS 2007 when the much richer ASP.NET 2.0 connection framework is available to you) are absolutely necessary, ASP.NET 2.0 Web Parts will be the recommended approach, even for Web Part development that makes use of data in WSS 2.0 or MOSS 2007 via the various APIs.
The Resources
I’m going to use two very simple little button images for the purposes of this example.
The default button image looks like this:
The rollover-state button image looks like this:
The script for doing the rollover is very simple as well:
// Initial JScript source code
function RollOver(imageid)
{
document.all.item(imageid).src = 'button_hover.gif';
}
function RollOut(imageid)
{
document.all.item(imageid).src='button.gif';
}
This is saved in a .JS file. In a minute, I’m going to modify that script to include tags that ASP.NET 2.0 will interpret and substitute with URLs to my embedded image resources at runtime, but for now, it’s enough to see that there’s very little magic going on with the script itself.
Getting the Resources into Visual Studio 2005, and Writing the Code
In Visual Studio 2005, I create a new Web Control Library project. In my case, I’m going to name it “My.BlogParts.” Next, I’m going to add a new Resource File to the project, which I can do by going the the menu and clicking Project -> Add New Item… and choosing “Resources File.” I’m going to name mine “testres.resx.” [I can also create a project default resources file, but for the purposes of this post, it’s just simpler to create a new one] Once I’ve created it, I can double-click on the testres.resx in Solution Explorer to open up the resources designer. From within the designer, I then choose Add Resource -> Add Existing File… and add my two .GIF images and my .JS script file to the Resources file. In the screenshot on the right, you can see what my project’s Solution Explorer looks like after this has been done. Disregard the additional files in there for now (e.g., RollOver.cs and mykeys.snk), I’ll be discussing them below. However, do note the added “Resources” folder, as it will become important later on.
For each of the embedded Resource files, I need to set the Build Action for the resource to “Embedded Resource” in the Properties pane for that item. To do so, I simply select the item from within the Resources folder, look at the Properties pane, and select “Embedded Resource” from the Build Action drop-down menu. I can leave “Copy to Output” set to its default, which is “Do not copy.” See the screenshot below:
Since these are Web Resources, I also need to reference them in my AssemblyInfo.cs file. In this case, here’s the few lines I add:
[assembly: WebResource("My.BlogParts.Resources.button.gif", "image/gif")]
[assembly: WebResource("My.BlogParts.Resources.button_hover.gif", "image/gif")]
[assembly: WebResource("My.BlogParts.Resources.rollover.js", "text/javascript", PerformSubstitution = true)]
The format of these entries is pretty simple. First param = [Namespace].[Resources Folder].[Resource File Name]. Second param = content type. Third (optional) param = PerfomSubstitution (true/false). Because my project, namespace, and assembly will all be named “My.BlogParts,” and my resources folder is named “Resources,” this is pretty easy to figure out: My.BlogParts.Resources.[resource file name]. The third, optional, parameter lets me do some super-cool runtime substitution that I’ll explain more in a moment.
Now that I have my Resources imported into the Project, I’m going to add and write a simple Web Part that will use them. Because I hate default names, I’m going to simply delete the “WebCustomControl1.cs” that was created for me when I created the Project, and add a new Web Custom Control to the Project, named “RollOver.cs.” Visual Studio tries to be nice and adds a bunch of default code into the class file, and in the code snippets below, you might notice that I’ve erased it and started from scratch; for the purposes of this example, though, you could just as well leave all of the default code in the class file and edit the items that I’ve changed and leave the rest since they won’t really hurt anything.
By default, my web control inherits from the System.Web.UI.WebControls.WebControl class. Because I’m creating a Web Part, I need to change this. First, I’m going to add a using statement at the top of the .cs file:
using
System.Web.UI.WebControls.WebParts;
Next, I simply change my class to inherit from System.Web.UI.WebControls.WebParts.WebPart:
public
class RollOver : WebPart
{
…
Because my Web Part is going to be displaying an image, I’m going to declare a System.Web.UI.WebControls.Image object to the class, e.g.:
public
class RollOver : WebPart
{
protected Image imgRoll;
…
Next, I’m going to add an override of the CreateChildControls() method, so that I can add my Image object to the Web Part. This code will be the first to utilize some of the new Web Resource-related methods, and I’ll explain after showing the code:
protected override void CreateChildControls()
{
ClientScriptManager cs = Page.ClientScript;
imgRoll = new Image();
imgRoll.ImageUrl = cs.GetWebResourceUrl(this.GetType(),
"My.BlogParts.Resources.button.gif");
imgRoll.Attributes.Add("onMouseOver", "RollOver(this.id)");
imgRoll.Attributes.Add("onMouseOut", "RollOut(this.id)");
imgRoll.ID = "myimage2";
Controls.Add(imgRoll);
}
So, from the perspective of Web Part development, I first instantiate a new Image object, assign some attributes, and add it to the Web Part’s “Controls” collection. You’ll note that I’m adding some attributes to specify onMouseOut and onMouseOver script calls. The interesting stuff, though, is the ClientScriptManager object and GetWebResourceURL() method. I’m not going to go into detail about these, since you can read about each of them here and here, and I’m sure elsewhere as well. It’s enough to point out that once I’ve created a ClientScriptManager object, I can use its GetWebResourceURL() method to return the URL to an embedded resource. I pass it two parameters: the resource type (I can just use this.GetType() in C# to return the type) and the name of the resource. I need to specify the name of the web resource here like I specified it in AssemblyInfo.cs — [Namespace].[Resources folder].[Resource File Name].
OK, two more code steps: I need to get my script included in the target page, and I need to render the Image control. For the former, I override the OnPreRender event, because I want my script loaded before the page loads (so that it is available to my Web Part once it renders). I use the ClientScriptManager’s RegisterClientScriptInclude() method to add my script as an include, passing the type (again, using this.GetType() in C#), the resource name, and the URL of the resource (the last of which I obtain using GetWebResourceURL() again).
protected
override void OnPreRender(EventArgs e)
{
ClientScriptManager cs = Page.ClientScript;
cs.RegisterClientScriptInclude(this.GetType(), "rollover", cs.GetWebResourceUrl(this.GetType(),
"My.BlogParts.Resources.rollover.js"));
}
For the latter — rendering the Image control — I simply override RenderContents() (if you haven’t removed all of the code that Visual Studio created for you, one already exists, and you can just edit the contents), and render it like I would in WSS 2.0/SPS 2003:
protected
override void RenderContents(HtmlTextWriter output)
{
EnsureChildControls();
this.imgRoll.RenderControl(output);
}
Using PerformSubstition to Replace Placeholders with URLs at Runtime
One last thing. The script file that I outlined earlier included hard-coded values for URLs, e.g.:
document.all.item(imageid).src='button.gif';
This won’t work for an embedded Web Resource, of course (this would look for a static button.gif file in the current directory). Thankfully, ASP.NET 2.0 Web Resources includes a solution, via PerformSubstitution. When I referenced my Web Resources in AssemblyInfo.cs, I included “PerformSubstituion = true” for my embedded .JS script file. This tells ASP.NET 2.0 to look for “tags” (my phrase) in the embedded file, and replace them at runtime. I’m then able to add tags referencing additional embedded Web Resources. Because I’m embedding my image files, I’ll modify my script to include tags so that at runtime, the URL is replaced with the embedded Web Resource URL for the images, e.g.:
// JScript source code
function RollOver(imageid)
{
document.all.item(imageid).src = '<%=WebResource("My.BlogParts.Resources.button_hover.gif")%>';
}
function RollOut(imageid)
{
document.all.item(imageid).src='<%=WebResource("My.BlogParts.Resources.button.gif")%>';
}
The format should look pretty familiar by now — [Namespace].[Resources Folder].[Resource File Name]. At runtime, those tags will be replaced by the Web Resource URL for each of the embedded images.
Build and Deploy the Web Part
That’s it for code. Because I’m going to deploy my Web Part to the GAC for the purposes of this example, I strong-name the assembly when I build it (in this case, by going to the menu and selecting Project –> [Project Name] Properties…, selecting the “Signing” tab, checking “Sign the assembly,” and creating a new strong name key file using the option in the drop-down menu). The resulting assembly contains my Web Part and all of the resources it needs — I can deploy the assembly alone, and don’t have to package up the static files along with it for installation.
I’m not going to get into Web Part deployment in this post, but those are all of the steps needed to create a simple Web Part that uses embedded image and script resources to perform a simple little rollover/hover image effect. I’ve posted the full source code and image files in a ZIP archive if you’re interested.
Happy coding!
Comments
Anonymous
May 01, 2006
You JS would be more browser compatible if you use the following:
image.src='button.gif';
and pass this and NOT this.id :)Anonymous
May 01, 2006
No question, I code for IE, and then walk away from the scene of the crime. :) I'm not sure that I understand -- from your comment -- the change that you'd implement to make this more cross-browser-ready (what is it that the code should pass from within HTML to the script, exactly?).
BTW, there is, for some reason, a very strong South African community here on the "Eastside" of the greater Seattle area, where Microsoft is located. Somehow, I have several friends from SA who speak Africaans (not that I can speak a work of it, myself) as a first language -- small world, as they say. ;)Anonymous
May 02, 2006
The comment has been removedAnonymous
May 06, 2006
Ah! Got it, makes sense. Thanks, man. I'll try it out, and then amend the post to include your revision. There's no reason not to be more cross-browser friendly, especially when the change is relatively trivial, as this seems to be. I appreciate the tip!Anonymous
May 10, 2006
Thanks for the sample!
Any recommendation on how to include external css using similar methods? I couldn't find something similar to cs.RegisterClientScriptInclude().Anonymous
August 29, 2006
I have tried this strategy but have been unable to make it work with MOSS 2007. I have included the resource file and I am able to get a URL from the framework. However, when I attempt to access this url by adding it to an image control or manually go to this URL no content is returned. Any ideas?Anonymous
September 04, 2006
Does this work with compiled user controls? I'm trying to get an image using GetWebResourceUrl but the image doesn't appear an I'm sure the name used in the attribute "[assembly: WebResource... " is correct.I've checked it with reflector. I can even see the image with reflector so i know its in the assembly.I can send a vs project with this if necessary. I really need help on this!Thank you.Anonymous
March 08, 2007
Ryan, Thanks for this post man. I've used this method in my own web parts and its worked great so far but I do have one question I was wondering if you could help me with. We have resources working great inside of a ASP.NET 2.0 web part used inside of MOSS 2007, except we are having a problem(page displays a 403 error) w/ non administrative users getting permissions to the resources out of a controls assembly that our web part is referencing. After an administrator loads the assembly into memory(by viewing the same page) in WSS/MOSS the web part works fine, and the resources are accessible to all, but before that an non-admin user doesn’t have read permissions to the referenced assembly. We have gotten around this problem, by manually changing the permissions on the referenced assembly file in the bin folder of the virtual server, but that doesn’t help us with long term deployment issues. More specifics: We have two assemblies, TAP.WebControls (contains the resources embedded in the assembly), and TAP.WebParts. These are both being deployed to the server via one cab. Whenever our web parts attempt to use the controls or the resources from the controls assembly we get a 403 error if the user trying to access the page is not an administrator on the machine/domain. We have found that if we go into the bin where the assembly is deployed and give a specific user read access they no longer receive the error. Are you aware of any special permissions, trust level, or web.config settings we need to implement before attempting to call the ClientScriptManager.GetWebResourceUrl method? Here is an example of how we are calling it right now… Cs.GetWebResourceUrl(typeof(ClassInWebControlsAssembly), “TAP.WebControls.Resources.someimage.jpg”); Any help or advice you can offer would be greatly appreciated. Thanks, MarkAnonymous
October 18, 2007
ASP.NET 2.0 - Server Control Web ResourcesAnonymous
October 18, 2007
ASP.NET 2.0 - Server Control Web Resources