Thursday, March 22, 2007

SharePoint Resources, Types, Use and Deployment

In the recent weeks one of my tasks was to work on the localization of a WSS solution and deployment of resources to WSS sites. I found that this topic is still not well explained and there are not too many sources that describe the specifics of using and deploying resources in WSS 3. In this post I'll explain the differences in the use and deployment of several types of resource files.

Types of WSS Resources

There are two groups of resource files in WSS. The first one includes files used during provisioning of sites and features. This group I'll call provisioning resources. The second group includes resource files used by ASPX pages and assemblies at run-time. This group respectively I'll name run-time resources.

The provisioning resources are located in the 12 hive in ..\12\Resources. In WSS the default provisioning resource file is core.resx. This file is used in site definitions (one.xml) files, features, list definitions etc. For example you can open any of the existing site definitions to see how this works. The picture below shows the first rows of the ONET.XML file of the Wiki site definition.

In a WSS feature there are three options for using localized strings from resource files. The default location for the feature's resources is ..\12\TEMPLATE\FEATURES\[FeatureName]\Resources\Resources.[Culture].resx and the strings can be referenced with $ Resources:resource-key, omitting the resource file prefix. Another option is to use the resources in the ..\12\Resources folder, which are available with the full explicit expression form $ Resources:[filename prefix,]resource-key, where the resource file name prefix is specified. The file name prefix can be omitted when the attribute DefaultResourceFile contains a name prefix of a file in ..\12\Resources folder. The attribute RequireResources of the feature element provides additional control over the activation of localized features (see WSS SDK for more info).

Nevertheless the provisioning resource files are not accessible from the ASP.NET web sites. This is where the run-time resources come to play. These files are used in vanilla ASP.NET sites and are located in the web application folder under ..\wss\VirtualDirectories\[port]\App_GlobalResources. They are compiled automatically by the .Net framework in the Resources namespace. These resources are available in assemblies and directly in ASPX pages. To access them from the code use the strongly-typed notation Resources.[file prefix].[resource name]. In ASPX pages use the explicit expression <%$ Resources:[filename prefix,]resource-key %>. The default WSS resource file in this category, used by all WSS sites, is wss.resx.

Deployment of Provisioning Resources

The deployment of provisioning resources can be accomplished using WSS solutions. Unfortunately neither of these deployment facilities support the deployment of run-time resource files directly to the App_GlobalResources folder. In the following samples I'll demonstrate how to access and deploy both types of resource files.

The WSS solution manifest contains two elements with names that imply to have something to do with the deployment of RESX file. They are <ApplicationResourceFiles> and <Resources>. Unfortunately they are not really helpful and basically can be ignored.

To deploy provisioning resources available to all site definitions, features, list definitions etc. use the <RootFiles> element in the WSS solution manifest. The target folder for this element is the 12 hive ..\12\. The files in the example below will be copied to ..\12\Resources\.

To deploy feature specific provisioning resource files, inside your feature's folder, create a sub folder named Resources and rename the resource files to Resources.[language-locale].resx. Remember to omit the file prefix in the string expression ( e.g. $Resources:resource-key; ).

Deployment of Run-Time Resources

Global WSS run-time resources reside in ..\12\CONFIG\Resources. This copy of the files is not used directly. At the time a new web application is created the content of this folder is copied over to ..\[port]\App_GlobalResources. If your organization has one set of localization files used in multiple applications this is the place to put them. You can use the <RootFiles> element in a similar way as described above to deploy such files using WSS solution. However it is very rare that a new web application will be created for a single site or solution. Another option is to use the use stsadm command with the option copyappbincontent to copy the content of ..\12\CONFIG\Resources (among other files and folders) to App_GlobalResources.

None of the above described approaches allows developers to deploy resource files at the time the site or the feature are provisioned. To circumvent the lack of such functionality I hooked up a custom SPJobDefinition to the FeatureActivated event of its event receiver. This is becoming common practice, when it comes to extending the WSS solutions and WSS features. (I got inspired by Vincent Rothwell and his post about extending breadcrumb for pages in _layouts). The job copies the files from the feature's folder to the App_GlobalResources folder of the current web application. Here is how it works.

First let's create a feature to deploy the resource files. The feature defines the event receiver and the assembly.

Next let's create our custom job definition. The constructors look like this:

The first constructor is required and used internally. In the second constructor we simply initialize the source path using the feature's physical path.

The next step is to define the overloaded Execute method, which will copy the files from the feature's folder to the App_GlobalResources.

Once we have the custom job definition, lets hook it up to the events of the feature's event receiver. On the FeatureActivated event we are going to create one new task from our newly defined type and submit it for one-time immediate execution.

As Andrew Connell suggests in this post, before we create the job, we make sure to delete existing instances of the same job type for the current web application. This way we do not end up with competing jobs performing the same action. In a similar train of thought it is nice to clean up the job when the feature is deactivated. So let's add some code to the FeatureDeactivating event.

One future addition to the custom job class will be to delete the deployed resource files, when the feature is deactivated. Since the feature is related to the WSS solution, when the solution gets retracted, the files will be cleaned up nicely as well. The feature can also be extended to be locale aware and deploy only specific resources.

Update 9/21/2007: In this post http://www.mikhaildikov.com/2007/09/sharepoint-resources-types-use-and.html I write about slightly different way to deploy RESX files using feature stapling, which is a bit simpler to implement.

Click here to download a complete sample project.

Got localized?

Dovizhdane!

56 comments:

AndersR said...

Excellent job Mikhail!
Strange though that MS havent thought of creating a feature element to put ressource files where they belong with all that focus on both features and ressource files :-)

Todd Bleeker said...

Mikhail,

I haven't seen the Resources topic covered this well anywhere else. Thanks for taking the time to capture your experience for others.

<Todd />

Katrien said...

Hi Mikhail,
Thanks for the post!

I have an additional question about using localized satellite assemblies in a solution file. My solution should be deployed in two languages (the base one and in Dutch) but I'm not finding any information on how to create the solution schema and cab file. Have you had any luck on this?
The SDK says I should create separate language packs but there is no sample anywhere.
Thanks in advance for your help.

Katrien

Mikhail Dikov said...

Katrien,

The faster alternative to get started with the solution file, without knowing much about the solution schema, is to use Visual Studio 2005 Extensions for WSS (VSeWSS).

You can also have a look at the sample from my code camp session. The project contains most, if not all, elements of the solutions schema in a context.

Mikhail

Katrien said...

Thanks Mikhail, I will give the VseWSS a try to see how it handles satellite assemblies. Good suggestion.

Thanks also for your sample code.

Katrien

Josh Korn said...

Mikhail:

This one's a definite keeper! It deftly solves a problem that was otherwise a real pain. It works even if you're adding features to Central Admin.

I had some problems getting it to work consistently, because the Receiver hooks weren't firing.

What I discovered was that if you activate a feature, then subsequently retract the entire solution containing the feature, the feature remains activated. If you then redeploy the solution, the feature becomes 'activated' without a call to FeatureActivated().

Any idea why that might be?

Josh

Mikhail Dikov said...

Josh,

Did you check the if the SolutionId attribute of your feature matches the GUID of your solution? What is the type (e.g. Farm, Web App...) of your feature?

Mikhail

Matthias Glubrecht said...

Hi Michael,
thanks for the very interesting article which sheds a lot of light on the .resx handling in WSSV3. I am currently also looking for the best solution to deploy files to the App_GlobalResources directory.
Correct me if I'm wrong, but wouldn't it suffice to just copy the .resx files directly within the FeatureActivated event and delete them within the FeatureDeactivating event? What is the reason to do it using an SPJobDefinition?
Kind regards, Matthias

Mikhail Dikov said...

Danke für die gute Frage!

I hope my latest post answers your question:

http://dikov.blogspot.com/2007/05/why-use-spjobdefinition-in-sharepoint.html

Mikhail

Anonymous said...

Try out this tool that builds SharePoint solutions (WSP) files, it also supports the resources files.

http://www.wspbuilder.org

Anonymous said...

does the out of box stsadm -o copyappbincontent do what you try to do in your feature receiver?

Mikhail Dikov said...

Good point! I found that stsadm -o copyappbincontent is not the best option for resx file deployment. Some reasons:

1. It copies not only resource files but also config files and other content. I need my feature to deploy only resx files.

2. Files are copied to ALL web applications. If I deploy my solution to one web application, I don't want the resource files to go to all web applications.

3. I am trying to simplify the deployment from the perspective of the end user. If some code saves a command line, that's saves a support call. Using the above feature eliminates manual actions and encapsulates all necessary steps within the solution deployment.

Mikhail

akhila K said...

Hi Mikhail,

I read your post its really informative . I am a newbie in sharepoint . My question is this
I want to localize a sharepoint site in french though my Central Administration site is in English .

Really Appreciate

akhila

Anonymous said...

Hi!

Wonderful article! I've been trying to use localization in one of my feature, using a resource string to display a title in a ECB menu. It doesn't seem to work, it displays $Resources:menuTitle in the menu. I place three .resx files under a Resources folder in my feature folder. What could be wrong? Thanks

Mikhail Dikov said...

Anonymous,

Verify if you have a language neutral resource file, which contains the same entries as your language specific file. The easiest way to do this is to make a copy of the language specific file (e.g. Resources.en-US.resx) and rename it to Resources.resx.

Mikhail

Mikhail Dikov said...

akhila,

Try to submit your question in one of the SharePoint user groups. You can get excellent response from a number of SharePoint professionals:

http://groups.google.com/groups/dir?sel=33607030

Mikhail

akhila K said...

Hi Mikhail,

Thanx for taking the time to reply .

I got the answer , just had to download a language pack and when thats done i need to select the langauge that I want my site collection to display in .

Recently installed forms server to work on infopath forms had a tuf time installing it as I alreday had other SHarepoint sites in my system , and there were clashes and log errors

But thats okay now too . Just deleted all the content databases and resintalled wss 3.0 onwards till forms server and evryting worked fine

I will surely post any other doubts i get in the link you provided

Thanx again !

Akhila

Anonymous said...

Здравствуйте! Подскажите, пожалуйста, что нужно сделать, чтобы по уму русифицировать Groupboard Workplace (это GBP.wsp).

Попробовал создать файл gbw.ru-ru.resx и положить его рядом с C:\Inetpub\wwwroot\wss\VirtualDirectories\80\App_GlobalResources\gbw.en-us.resx

Автоматически не подцепилось.

kevz said...

Mikhail,

Nice work ... I had some questions. i am new to Sharepoint and wanted to know if i have my own aspx pages and js files which i want to localize in addition to my other .resx files and create a language pack would it be the same process as you mentioned also do i need to create a satellite dll ? I hope i make sense. Any help would be highly appreciated.

Kevz

Mikhail Dikov said...

Kevz,

The language packs are basically solution files that can only be installed after the base solution is installed and they contain files, features etc specific to a given locale. The guid of a language pack solution is the same as the base solution, but the locale is different.

The resx files are convenient as you can have one file with all localization strings and they can be accessed from within in-line ASPX code. They can also be edited on the fly.

The satellite assemblies are good when you access the strings from code-behind, server controls or web parts. I personally prefer to use one localization assembly and modify the automatically generated class from private to public, so that all localization strings are kept in one assembly.

The JavaScript code is a different story. You can emit it from managed code and populate the localization strings. Another way is to create JS language specific folders and use the appropriate reference in your HTML code.

Mikhail

A said...

>>>The language packs are basically solution files that can only be installed after the base solution is installed and they contain files, features etc specific to a given locale. The guid of a language pack solution is the same as the base solution, but the locale is different.


Я правильно понял, что для локализации gbw.wsp надо к нему каким-то образом сваять русский language pack?

Лев

Anonymous said...

Good work Mikhail! Do you know if your solution will work in a Server Farm environment that has more than one front end server?

Anonymous said...

Please ignore my previous post, just read your response on why use SP Job instead of EventReceiver. So is that true that the same job will submit to each front-end server and each front-end server will execute the code once?

Gilles said...

Mikhail, this is a very interesting article. Is it also possible using this approach, to fill DCL with files and Custom lists with items?

For instance, one of our installed features in combination with the onet.xml creates a few lists and 2 DCL's. We would like to add files to those DCL. (.udcx files) and we would also pre-fill the lists with items.

So, do you think it is possible to combine both with this technique? we know it is possible to provision files using WSP, but we would like to have 1 solution for both the files and the content.

Thanx,
Gilles

Sjoert Ebben said...

Good article! Multilingual in MOSS is still pretty much unexplored, so your contribution is a valuable piece of the puzzle.

spohl said...

Thanks for this post.

This saved me a lot of time to make sure my German Features can also be used on an English server.

Just to make the FeatureReceiver a little bit more generic, one should keep in mind, that setting a different scope (e.g. "Site" instead of "Web") also changes the type of the "Parent" property object.

Mikhail Dikov said...

spohl,

Danke fur deinen Kommentar!

I agree that the code is not as generic as it could be, when it comes to feature scopes. In my latest post I am trying to fill this gap. Check it out:
http://www.mikhaildikov.com/2007/11/support-multiple-scopes-in.html

Mikhail

mswin said...

Hi,
I have placed some of the resx files under Inetpub/wwwroot.../80/bin folder. and trying to access them in sharepoint master page by using the below syntax,
$ Resources:sample, Welcome_Message_Label
but irrespective of the browser language settings, I always get the values from english resource file only. Is there any thing else that I nned to do?

Thanks in Advance

AAron nAAs said...

I like the possibilities opened up by your solution. I have some questions:

1) Will the job run on all servers in the farm? (need the resx files on all WFEs :-)

2) I would like to have one resource file for use in the feature and the ASPX files. So, I'm trying to use one resource file for provisioning ([feature]\Resources\Resources.resx) and runtime (\12\Resources\MynamespaceResources.resx).

It makes sense to use the files with longer filenames in "\12\Resources" and the syntax "$Resources,MynamespaceResources,key;" but that won't work until the feature is activated (since activation copies the file to "\12\Resources"). So I'm stuck with "Resources.resx" which works fine in the feature directory, but when copied to "/12/Resources/Resources.resx" the name will collide with all my other applications using the same technique. Is there a way that I can name the provisioning resource file "[feature]\Resources\MynamespaceResources.resx", and reference properly in the feature.xml file (for Feature Name/Description)? That way it can be copied to "\12\Resources\MynamespaceResources.resx" without a problem and be used after activation during runtime (for ASPX).

As far as I can tell, the moment you mention a fileprefix in your "$Resources,[fileprefix,]key;" you are forced into using files in "\12\Resources".

Great work,
-AAron

Mikhail Dikov said...

Aaron,

The answer on your first question is yes. But look the other comments for some tricks on what account you use to activate such feature.

The answer of your second question is that you cannot have the same file to localize the feature and the ASPX files. They are used in different contexts. You can have the same content in two different files though if this will make it easier to maintain the files.

Mikhail

marco said...

Hi all,
I've found a solution, how to use this great code and deploy the resources to a farm, not only one web front-end server.
Read my article about this one little property which makes the difference here:
http://blog.mwiedemeyer.de/2008/05/25/DeployGlobalResourcesInASharePointFarm.aspx

Flores said...

Thanks Marco.. I was looking for that.

Mikhail Dikov said...

Marco, this is great. Thanks for posting this here!

Anonymous said...

Hi
I want to access resource files under App_Localresources in sharepoint thru meta:resourcekey..
Is it possible ?
or i have to use only App_GlobalResources ?
Pls help me in this

Anonymous said...

Mikhail,

This is working good. But when i try to enable this feature using STSADM it does not work but when i activate the feature using UI its working as expected.

Regards,
Sameer Dhoot
http://intellects.in/

Mikhail Dikov said...

I don't see any reason why you cannot use App_LocalResources in a similar way. You can use same deployment technique to deploy any file in any folder in the IIS root, so App_LocalResources is one of them. I have not tried this, as I prefer to use code behind for this kind of job, but please try it and if it works blog about it and post a comment.

Mikhail Dikov said...

Sameer,

This looks like a permission issue, but it usually happens the other way around. Check your job queue and see of a job has been created. If it isn't then try to find out why. If it is try attaching to the timer service and debugging the actual job code.

Mikhail

Anonymous said...

Thanks Marco/ Mikhail...that worked perfectly

Kreideicky said...

Hi Mikhail,
Thanks for the article!

I'm creating a localized site definition. I've created a default template file in the core solution and a language pack solution for one more culture. I manage to deploy both solutions to the server just fine. But when i try to create a new site from the Central Administration Site, in the Template Selection I only see the template for the default culture. I do not have the template for the additional culture. Is it supposed to show up in the Templates selection as the template for the default culture, if everything is done right? Or I've missed something and that is why I can't see this second template? Can you, please, help me with this issue?

Thanks in advance!

kreideicky said...

Hi again!
I figured it out - I hadn't installed the needed language packs. Now I have the language selection dropdown list and I'm able to create sites from my localized templates.

Jeroen Ritmeijer said...

Hi Mikhail,

Interesting article, but I find both the feature stapling as well as the job definition a bit convoluted.

As asking the end user to call an STSADM command to distribute the resources is not really acceptable what I always do is add the following line to the FeatureActivated event.

For Central Administration resources (and sitemaps): SPWebService.AdministrationService.ApplyApplicationContentToLocalServer();

For 'regular' Application Page resources (and sitemaps):
SPFarm.Local.Services.GetValue<SPWebService>().ApplyApplicationContentToLocalServer();

Granted, this also copies the sitemaps, but as most applications contain sitemaps anyway (or should) I see this as an advantage.

Jeroen

vinh2b said...

Hi Mikhail Dikov,

I'm trying to localization all wss to my language (Vietnamese).

I translated all files
1. Resource file: core.resx, wss.resx, spadmin.resx, etc.
2. Javascript file: C:\...\12\TEMPALTE\LAYOUTS\*.js

Almost text is displayed correcty, but Action Bar (containts "New", "Upload", "Actions", "Settings") is english version (~.~).

Can you help me to solve my problem?

Thanks

baxtheman said...

Have you traslated also resx into C:\inetpub\wwwroot\App_GlobalResources
??

Anonymous said...

Is it possible to have a resource file with just the language specified and no culture in its name. And that should be picked by the feature on run-time. i.e. core.nl.resx (instead of core.nl-nl.resx) ??

Aman & Biki said...

thanks for sharing the information about SharePoint Resources. I needed some for a project. It is very hard to earn money chao!

Anonymous said...

The default option is to embed the resources in the dll. Most of the time the issue with embedding is getting the nasty bugger in your runtime literals to point to the right assembly and namespace path so that you don't get a resource not found runtime error. Still hunting for the new new.

barathan said...

Excellent, You done a great job. Thanks alot, you minimized my work

Charu Jain said...
This comment has been removed by the author.
Charu Jain said...
This comment has been removed by the author.
Max said...

Hi Mikhail,
thanks for your post!
A small correction to your code - it was used a property DisplayName of a webapplication, it should probably be Name

gogi said...

plz i need help. i have to convert serbian latin to serbian cyrilic all site with ribbon,web parts. for example, when user open site all site need that it show on serbian cyrilic. which resources file i must convert.
thanks

Max said...

Hi again,
This solution perfectly works at SP 2010, but doesn't work on 2007, I've found out that the job is re-initialized using empty ctor, so path setting becomes empty when this job is executed :(
Could you tell me, why the job class is recreated?

Mikhail Dikov said...

Max, it's been a long time since I wrote this and I am pretty sure I wrote it for SP2007 way before 2010 was out.

Anonymous said...

Hi,

Is it possible to access a Resource File (.Resx) using javascript or jquery?

Vignesh Nagamanian said...

We have a list defined in ONET.XML file as given below. However, the expression for calling a resource is not working as expected.











$Resources:test,Content;

default.aspx

$Resources:Microsoft.Office.Server.Search,DocumentsTabComment;





The highlighted expression always brings back the value from the language neutral-culture neutral resource file. i.e it brings back the result only from test.resx and not from test.en-US.resx or test.fr-fr.resx.

On the other hand, the resource expression for title and description of the List is working as expected.

Please let me know if this is a known bug or if there is any work around.


Thanks,

Vignesh

Reply Quote

Vignesh Nagamanian said...

We have a list defined in ONET.XML file as given below. However, the expression for calling a resource is not working as expected.











$Resources:test,Content;

default.aspx

$Resources:Microsoft.Office.Server.Search,DocumentsTabComment;





The highlighted expression always brings back the value from the language neutral-culture neutral resource file. i.e it brings back the result only from test.resx and not from test.en-US.resx or test.fr-fr.resx.

On the other hand, the resource expression for title and description of the List is working as expected.

Please let me know if this is a known bug or if there is any work around.


Thanks,

Vignesh

Reply Quote