Monday, July 30, 2012

SharePoint Use Confirmation for Site Owners

A problem in SharePoint is to keep content up to date. Therefore SharePoint offers the possibility to create use confirmations. The only problem is that the confirmation can only be approved by the site collection administrators. Not in all companies the site collection admins are also the site owners. This can be due to permission restrictions or other actions that a normal site owner should not do.

In this post I'll show you how you can create your own confirmation rules and pages without using visual studio or SharePoint solution files.

Standard Site Use Confirmation

SharePoint offers an out of the box site confirmation mechanism. It send an email to the site collection owner and updates a counter of how many times a mail is sent without receiving a confirmation. The mechanism does not check the real use of the site. It simply send an email after a period of time to check the use.

You can configure it in the Central Administration  under "Application Management". There you'll find the link "Confirm site use and deletion".



Here you can configure after how many days a confirmation mail should be send to the sitecollection owner. Secondly you can also configure after how many times without response the sitecollection should be deleted.

As I am a SharePoint Admin in a company with sites that contain important information, I'm of course not a fan of workflows which automatically delete sites. I'm more interested in which sites are being used permanently and which not.

PowerShell Script

For me as an administrator the easiest way to manipulate things in SharePoint is to create PowerShell Scripts. Because of that I created a simple script which sends confirmation mails to users.

The script is relatively simple. It just loops through all site collections and checks their "CertificationDate" property. This property contains the last date the site has been confirmed.

Within a site collection  it loops through the SharePoint groups to find the site owner group. Then max. 2 site random site owners are requested to confirm the site use by an email. Additionally the SharePoint administrator is informed if the confirmation time exceeds a period you predefined. With that you have the full control over the use process.

Here comes the script. Adapt the variables in the "Variables which can be changed" section. Change the URLs, SMTP Server and Files Paths. Also adapt the name of your owners group in line 76 if you have other group names for your owners.

  1. #############################################
  2. # Loading Microsoft.SharePoint.PowerShell #
  3. #############################################
  4. $snapin = Get-PSSnapin | Where-Object {$_.Name -eq 'Microsoft.SharePoint.Powershell'}
  5. if ($snapin -eq $null) {
  6. Add-PSSnapin "Microsoft.SharePoint.Powershell"
  7. }
  8. # >> Get today
  9. $date = Get-Date
  10. # >> Format of the date for the log file
  11. $formatteddate = Get-Date -uformat "%Y_%m_%d_%H_%M"
  12. #############################################
  13. # Variables which can be changed #
  14. #############################################
  15. # >> Confirm mail send after 90 days of last confirmation
  16. $confirmAfterDays = 90
  17. # >> After this amount of days of tolerance the admin is informed
  18. $toleranceDays = 30
  19. # >> Name of the log file
  20. $log = "E:\Appl\SP2010\Scripts\ConfirmUsage_$formatteddate.log"
  21. # >> Name of the log file
  22. $webapplication = "http://sharepoint"
  23. # >> Email of sender
  24. $emailFrom = "sharepoint@mycompany.ch"
  25. # >> Email of admin
  26. $emailAdmin = "sharepoint@mycompany.ch"
  27. # >> Location of the useconfirmation page
  28. $pathPage = "/_layouts/MyCompany.UseConfirmation/useconfirmation.aspx"
  29. # >> SMTP Server
  30. $smtp = new-object Net.Mail.SmtpClient("mail.mycompany.ch")
  31. #############################################
  32. # Start Process #
  33. #############################################
  34. # >> Logging!!
  35. Start-Transcript -path $log
  36. # >> Create the stopwatch
  37. [System.Diagnostics.Stopwatch] $sw;
  38. $sw = New-Object System.Diagnostics.StopWatch
  39. $sw.Start()
  40. Write-Host "Starting User Confirmation Check."
  41. Write-Host "------------------------------------------"
  42. Write-Host ""
  43. try {
  44. $webapp = Get-SPWebApplication $webapplication
  45. $webapp.Sites | foreach {
  46. Write-Host "Checking Site: " $_.Url
  47. $diffDate = ($date - $_.CertificationDate).Days
  48. if ($diffDate -gt $confirmAfterDays ) {
  49. Write-Host " >> Site: " $_.Url " - Last use was for $diffDate days."
  50. $users = @()
  51. #############################################
  52. # Get Site Owners from groups #
  53. #############################################
  54. foreach($group in $_.RootWeb.SiteGroups){
  55. if ( $group.Name -like "* Owners") ) {
  56. foreach( $user in $group.Users){
  57. $users += $user.Email
  58. }
  59. }
  60. }
  61. #############################################
  62. # Select Users #
  63. #############################################
  64. $countUser = $users.Length
  65. $emailTo = ""
  66. if ($countUser -gt 2){
  67. # By more than 2 site owners select random 2
  68. $i1 = Get-Random -minimum 0 -maximum $countUser
  69. $i2 = Get-Random -minimum 0 -maximum $countUser
  70. while ($i1 -eq $i2) { $i2 = Get-Random -minimum 0 -maximum $countUser }
  71. $emailTo = $users[$i1] + ";" + $users[$i2]
  72. }else {
  73. $emailTo = $users -join ";"
  74. }
  75. #############################################
  76. # Send E-Mail to Users #
  77. #############################################
  78. if ($emailTo -ne ""){
  79. $subject = "Confirm SharePoint Web site in use"
  80. $body = "Dear Site Owner, `n"
  81. $body += "Please click on the link " + $_.Url + $pathPage +" to confirm that your site is still in use."
  82. $body += @"
  83. If the site is not being used, you can archive or delete it. Kontakt your SharePoint-Administrator for further process: sharepoint@mycompany.ch
  84. You will receive reminders of this until you confirm the site is in use, or delete it.
  85. Your Sharepoint Team
  86. "@
  87. Write-Host " >> Sending Email To: " $emailTo
  88. $smtp.Send($emailFrom, $emailTo, $subject, $body)
  89. }
  90. #############################################
  91. # Send E-Mail to Admin if site use is not #
  92. # confirmed for x days #
  93. #############################################
  94. if ( ($diffDate - $confirmAfterDays) -gt $toleranceDays ) {
  95. Write-Host " >> Sending Notice To: $emailAdmin that site use is not confirmed since $toleranceDays days."
  96. $subject = "Site Use is not confirmed since $toleranceDays days."
  97. $body = "The site use of '" + $_.Url + "' is not confirmed since $toleranceDays days. Please contact the site owner or archive the site"
  98. $smtp.Send($emailFrom, $emailAdmin, $subject, $body)
  99. }
  100. }
  101. }
  102. }
  103. catch [System.Exception] {
  104. $_.Exception.ToString();
  105. Write-Host "Error while sending confirmation usage mails."
  106. }
  107. finally {
  108. $sw.Stop()
  109. Write-Host "Time Elapsed: " $sw.Elapsed.ToString()
  110. Stop-Transcript
  111. }


Create Task Schedule

The next step is to create a task schedule which activates the script every 2 days.  Therefore go to your Application Server and open the Task Scheduler. Be sure that you are logged as Farm Account on the server.

Create a new folder in the task schedule library. Name it SharePoint.

Creat new folder in Task Scheduler


Now create a new task.

Create a new Task Scheduler Task

Name it "Site Use Confirmation". In the "Action" tab choose :
  • Action: Start a program
  • Program/script: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
  • Add argument: -command "C:\Scripts\SiteUseConfirmation.ps1"

In the "Triggers" tab create a new trigger and set the schedule you want. Save the task by clicking "OK"

Confirmation Page

Finally we need the confirmation page. The standard confirmation page of SharePoint cannot be used because only a sitecollection admin can confirm the use. But we want that everbody can do that.

So I copied the standard confirmation page and modified it for my purposes. Create a new "MyCompany.UseConfirmation" folder within the /14/TEMPLATE/LAYOUTS/ folder. Copy the useconfirmation.aspx file into the new folder.

We have to make some modifications so the new confirmation page works.

1.) Replace the DynamicMasterPageFile and Inherits attributes by  MasterPageFile="/_layouts/v4.master".

2.) Create a Page Load event. Add this piece of code above the first asp:Content element.

  1. <script runat="server">
  2. void Page_Load(object sender, System.EventArgs e)
  3. {
  4. SPSecurity.RunWithElevatedPrivileges(delegate()
  5. {
  6. using(SPSite site = new SPSite(SPContext.Current.Site.ID))
  7. {
  8. site.ConfirmUsage();
  9. Label_UseConfirmation.Text = Label_UseConfirmation.Text.Replace("%1", site.RootWeb.Title);
  10. }
  11. });
  12. }
  13. </script>

This code fragment ensures that everyone can confirm the site use. Now adapt the link in the powershell script to your new confirmation page.

Deployment

You have to copy the confirmation page to all of your frontend servers into the /14/Templates/Layouts/MyCompany.UseConfirmation/ folder.

Monday, July 23, 2012

SharePoint 2013 Developer Training Site

Found this useful site in the microsoft homepage. It contains multiple videos for SharePoint and Office 13 developers:

http://msdn.microsoft.com/en-US/office/apps/fp123626

Tuesday, July 17, 2012

Sorted Dropdown for MatchPoint Form WebPart with Distinct Elements

MatchPoint is a handy framework for creating fast custom solutions. In this post I'll show you how you can bypass some limitations with the Form WebPart.

A customer of mine wanted a sorted select in his form webpart in which he can choose the assigned user of a task list. Something like that:

Example of a sorted Dropdown

1.Attempt - The SPListChoiceProvider

You can do this easily with a ChoiceField in the Form WebPart. As provider (Source of the Dropdown) a SPListChoiceProvider would be a logical choice.

In the SPListChoiceProvider you can select the source list, a key and a value column for the dropdown. Additionally you can also select "UseDistinctValues" to remove duplicate entries.

One drawback or missing feature here is the ability to choose an order for the elements. Another is that you can only choose a single field as key. There is no possibility to combine fields.


2. Attempt - The ExpressionChoiceProvider

My second attempt was to use the ExpressionChoiceProvider. With this provide you can enter a MatchPoint Expression as source. So I tried:

Web.Lists["Jeve Masterplan 2011"].Where("AssignedTo", "!=", null).Select({"AssignedTo"}).OrderBy("AssignedTo", "Ascending").Distinct()

The result was something like that:



The order was correct bu it seems that the Distinct() Method of Linq is not working as expected. I couldn't find any solution here.

3.Attempt - Composite Field

The third attempt was to select a composite field. A composite can be designed as you want. So I selected the composite field gave it the name "fldAssignedTo" and checked the "EnableSelection" checkbox.

As provider I chose the ListDataProvider.

To design the composite I chose the XslTransformer because it's much more powerfull than the PatternTransformer. The Composite field gives you a lot flexibility. So the first thing I made was to map fields, so I can use them in the XSL.

Mappings


Within the Composite Field  you can mark selectable html elements  with the attribute Selectable="1". With Selectable="1" the html elements get clickable and you can connect them to other MatchPoint WebParts. Herefore EnableSelection must be activated on the field properties.

Then I tried the following XSL Code. It creates a simple small div with clickable p elements.

 <xsl:stylesheet version="1.0" .. >  
  <xsl:key name="Persons" match="//Row" use="Person" />  

  <xsl:template match="/">  
   <div style="height:60px; width:200px; overflow:auto">     
    <xsl:for-each select="//Row[generate-id(.) = generate-id(key('Persons', Person))]">  
     <xsl:sort select="Person"/>  
     <xsl:apply-templates select="." />  
    </xsl:for-each>  
   </div>  
  </xsl:template>  

  <xsl:template match="Row">    
   <p Selectable="1"><xsl:value-of select="Person" /></p>   
  </xsl:template>  

 </xsl:stylesheet>  


I used the Muenchian Method to group the the items by Person. So I could remove distinct values. With the sort within the foreach loop I could also sort the selected items.

The cool part was that the elements were now distinct, ordered and selectable. The bad part was, that I connected the form with another webpart and when I selected an element in the form, wrong items were shown in the connected webpart. Without grouping and ordering there were no problems.

Then I look a little into the javascript of the MatchPoint framework and realized that the Composite WebPart creates an array rowIds with the Item IDs of the returned XML. When you change the order of the items in the XML the items and the associated ids in the array are not properly connected anymore.

Row IDs

Another problem is also the Selectable attribute. The Composite is not designed to create a dropdown in your form. Therefore you've other fields. The problem is that the Selectable attribute has no effect on option fields in html. (in Internet Explorer)

 <xsl:stylesheet version="1.0" .. >  
  <xsl:key name="Persons" match="//Row" use="Person" />  
  <xsl:template match="/">  
   <select>     
    <xsl:for-each select="//Row[generate-id(.) = generate-id(key('Persons', Person))]">  
     <xsl:sort select="Person"/>  
     <xsl:apply-templates select="." />  
    </xsl:for-each>  
   </select>  
  </xsl:template>  
  <xsl:template match="Row">    
   <option Selectable="1"><xsl:value-of select="Person" /></option>   
  </xsl:template>  
 </xsl:stylesheet>  


Internally the MatchPoint Framework searches all html elements with the Selectable attribute and adds them a click event. A click event on option element has unfortunately no effects.

I was so close to solve this problem, but none of the above methods could be use

4 Attempt - JavaScript Hacking

This is always the last way I help me out. The theory is to use the underlying MatchPoint Framework and add some more features in the way that I don't change the default behaviour. The decorator pattern is here answer.

My idea was to add a "Changeable" attribute to mark a select box as an item, which has elements connected to other webparts. So I extended the MP.Composite function with my abilities:


 var OriginalMPComposite = MP.CompositeWebPart;  
 MP.CompositeWebPart = function() {  
   OriginalMPComposite.apply(this, arguments);

   this.Setup = function() {       
     this.BindEventHandlers();  
     this.BindChangeEvent();  
   }

   this.BindChangeEvent = function() {  
     this.$container = $(this.Control).find('#RowContainer');  
     if (this.EnableSelection)  
     {  
       var me = this;  
       this.$container.find("[Changeable]").each(function()  
       {   
         var $row = $(this);            
         var rowId = this.options[0].value;    
         $row.change($$.Delegate.Create(me, me.Row_Change));            
       });  
     }  
   }

   this.Row_Change = function(e) {  
     var $row = $(e.currentTarget);  
     var rowId = $row.val();  
     if (this.SelectedRowId == rowId)  
     {  
       this.SelectedRowId = null;  
     }  
     else  
     {  
       this.SelectedRowId = rowId;  
     }  
     var me = this;  
     this.Callback.SelectionChanged(function() { MP.ConnectionManager.NotifyConsumers(me, "SelectedRow"); });   
     RefreshCommandUI();  
   }    
 }  

In this JavaScript I call the original function an extend the main function with the BindChangeEvent and Row_Change methods.

You can add this JavaScript somewhere in the site or directy into the XsltTransformer. Now you have only mark the select with the Changeable attribute and add the Item IDs into the value of the options.

Now add a Form WebPart to your page and select the Composite Field. Choose a provider for your field. Don't forget to check the "EnableSelection" Checkbox on your field.


Now add a XlsTransformer for your field and add two mappings.



Add the following XSLT to your transformer. Change the element names if you used other mapping names.

 <xsl:key name="Persons" match="//Row" use="Person" />  
  <xsl:template match="/">  
   <select Changeable="1">  
    <option value=""></option>  
    <xsl:for-each select="//Row[generate-id(.) = generate-id(key('Persons', Person))]">  
     <xsl:sort select="Person"/>  
     <xsl:apply-templates select="." />  
   </xsl:for-each>  
   </select>  
   <script type="text/javascript">  
    var OriginalMPComposite = MP.CompositeWebPart;  
    MP.CompositeWebPart = function() {  
   OriginalMPComposite.apply(this, arguments)  
   this.Setup = function() {       
      this.BindEventHandlers();  
      this.BindChangeEvent();  
     }  
   this.BindChangeEvent = function() {  
      this.$container = $(this.Control).find('#RowContainer');  
      if (this.EnableSelection)  
      {  
       var me = this;  
       this.$container.find("[Changeable]")  
         .each(function()  
         {                    
           var $row = $(this);            
           var rowId = this.options[0].value;    
           $row.change($$.Delegate.Create(me, me.Row_Change));            
         });  
      }  
     }  
   this.Row_Change = function(e)  
   {  
    var $row = $(e.currentTarget);  
    var rowId = $row.val();  
    if (this.SelectedRowId == rowId)  
    {  
     this.SelectedRowId = null;  
    }  
    else  
    {  
     this.SelectedRowId = rowId;  
    }  
    var me = this;  
    this.Callback.SelectionChanged(function() { MP.ConnectionManager.NotifyConsumers(me, "SelectedRow"); });   
    RefreshCommandUI();  
   }    
 }  
 </script>    
  </xsl:template>  
  <xsl:template match="Row">    
   <option value="{ItemID}"><xsl:value-of select="Person" /></option>   
  </xsl:template>  


Now you have an ordered dropdown with distinct items which can be selected and connected to other webparts. You can also have other text in the option elements or sort in the order and with the field you want.

Hint: 
Because I've a user field  I used the following Expression in connected WebParts in conjunction with the contains operator:

ConnectionData.fldAssignedTo.SelectedRow.ListItem.AssigendTo.ToString().Split({"#"})[1]





Wednesday, July 11, 2012

Localization with SharePoint 2010 and Visual Studio 2010


A good resource management is essential when you working with SharePoint. Most custom solutions are not really localized although it's not so difficult to implement. In this post I'll show you which resource types exists while developing SharePoint solutions.

Resource Types

When you develop SharePoint solutions you'll be creating different types of solution items. This includes:
  • Features
  • WebParts
  • ASPX Pages
  • Custom Code
Each of these three items can output something to the user, so each of them must have localization capabilities.

Resource Locations

You have multiple locations where you can position your resource files. Each location is used by another item and has other code methods.
  1. \14\Template\Features\<Feature Name>\Resources\
  2. \14\Resources\
  3. \14\Config\Resources\
  4. <Virtual Directory>\App_GlobalResources\
In this post you'll understand in which cases which folder is used. Only Nr. 3 (\14\Config\Resources) folder is not used in any of the following examples, because in real life I never use this folder either.

The meaning of the \14\Config\Resources folder is that all resource files within the folder are copied to the <Virtual Directory>\App_GlobalResources\ in following cases

  • You use the stsadm command : stsadm -copyappbincontent. You have to execute this command on all frontend servers
  • When provisioning a new webapplication also all resources files are copied from this folder
Until now I never used this folder. Perhaps you'll find a use case herefore.



Part 1: Localization of Features

Method 1

With Visual Studio 2010 it is more easier to localize features, Click on the feature you want to localize and select "Add Resource Feature".

Add new Feature Resource

Select the invariant culture, A new Resource.resx file is created within your feature folder.

The new Resource in your Feature folder

Open the Resource.rex file and create two entries with keys "FeatureTitle" and "FeatureDesc". Next doubleclick on the Feature Folder to open the Design view.

Modify Feature Settings

Click on Manifest at the bottom. On the next screen expand the "Edit Options". Edit here the title and description properties of the feature by entering a new title and description with reference to the resource file.


That's it. If you want to add a new culture you can't copy the resx file and paste it again. You have to do the first step again by selecting the new culture in the upcoming dropdown box.

Method 2

Another method how you can localize a feature or a webpart is to deploy the resource file into the Resources folder under the 14hive. Right click on your project name and select "Add" >> "SharePoint Mapped Folder".
Add new mapped folder

Select the "Resources" folder in the next dialog and click O.K.

Now add a new resource file to this folder by right-clicking on the folder name and selecting "Add" >> "new Item". Select Resource File in the next dialog and name it Features.resx.

You can tell now your feature to take this resource file as default resource file. To do this double-click on your feature to open its property panel. There you'll find the attribute "Default Resource File"

Set Default Resource File

Part 2: Localization of WebParts

The localization of WebParts is very easy when you've understood the localization of features, To localize a webpart follow the steps of the 2. method from the feature localization. This means
  1. Add a new SharePoint Mapped Folder to the project
  2. Select the "Resources" folder
  3. Add a new resource into this folder
Now you can adress the resource within the webpart settings. Assume we have a resource file called "Features.resx" in the mapped folder with keys WebPartTitle and WebPartDesc , then you can modify your webpart like that:

<?xml version="1.0" encoding="utf-8"?> 
<webParts> 
  <webPart xmlns="http://schemas.microsoft.com/WebPart/v3"> 
    <metaData> 
      <type name="Gollum.WebPart, $SharePoint.Project.AssemblyFullName$" /> 
      <importErrorMessage>$Resources:core,ImportErrorMessage;</importErrorMessage> 
    </metaData> 
    <data> 
      <properties> 
        <property name="Title" type="string">$Resources:Features,WebPartTitle</property> 
        <property name="Description" type="string">$Resources:Features,WebPartDesc</property>
      </properties> 
    </data> 
  </webPart> 
</webParts> 

If you want to know how to localize the webpart code, then look at step 4.


Part 3: Localization for ASPX Pages

You often develop custom pages for displaying or managing information. Usually you deploy them to the /_layouts/ folder. This virtual path is mapped to the \14\TEMPLATE\LAYOUTS folder in the server.

When you open an aspx file in this folder you will see that the expression builder "Resources" is mostly used to localize a page.

<SharePoint:EncodedLiteral runat='server' text='<%$Resources:wss,common_nofieldempty_TEXT%> .. />

This is a common way to seperate code and UI. Within the aspx page you can say which resource should be used by using the expression builder syntax. The markup for using the "Resource" expression is:

<%Resources:*Name of the resource file*, *Key in the resource file*%>

When you have a resource file like Gollum.resx and a key "SayHello" with value "Say hello to the world" then

<%Resources:Gollum,SayHello%> 

would output "Say hello to the world".

Hint: Expressions can only be used within ASP Controls. You cannot place them anywhere in the HTML Code like:
<table>
  <td> <a href="www.xyz.com"><%$Resource:Gollum, Link1%></a> ..

In this case you can use the ASP:HyperLink or ASP:Literal Control.

<table>
  <td> <asp:HyperLink runat="server" NavigateUrl="www.xyz.com" Title="%$Resource:Gollum, Link1%" /> ..


Where I have to put the resx file ?

Assume we have a resx file named Gollum.resx. To be able to use this file with the expression syntax it must be located in the "App_GlobalResources" folder of the current web application in the IIS webserver.

Usually the folder sits in a path something like that:
  • C:\inetpub\wwwroot\wss\VirtualDirectories\80\App_GlobalResources
  • C:\inetpub\wwwroot\wss\VirtualDirectories\sharepoint\App_GlobalResources
  • C:\inetpub\wwwroot\wss\VirtualDirectories\intranet\App_GlobalResources

A resource file within the App_GlobalResources folder is often called a Global Resource, because all pages within a web application can used it.

How do I deploy resource files to App_GlobalResources folder ?

The type of your Visual Studio project must be a "SharePoint Project". Now you click right on your project name and choose "Add" >> "New Item"

Adding a new item

In the next dialog select the "Empty Element" and name it e.g. "GlobalResources"

Adding a new empty module

Now click right on the new "GlobalResources" folder (Module) and add a new resource file. 

Add new item into the new folder
Name it like you want.

Add new resource file

Doubleclick the resx file and open it. Enter some keys and values. Save it afterwards.

Add some resource
In the next step click on the Gollum.resx file to display the properties. Change the "Deployment Type" attribute to "AppGlobalResource". This will force the solution package to deploy the file to the IIS folder App_GlobalResources.
Change Deployment Type


When you select the value "AppGlobalResources" the "Deployment Location" attribute gets activated, where you can select the folder to which the resx file should be deployed. You can select here any folder or leave the standard entry. This has no effect to the expression syntax.

Change Deployment Location
Now add some resources to our page.


We are already done. Deploy the solution and check if everything works fine. When you open the page now error messages hould be shown. Also the resx file should reside within the App_Globalresources folder.

Location of the resx file
Now you can start to localize your page. Make a copy of the Gollum.resx file and add it to the GlobalResource Module folder. Rename it for your desired language:
  • Gollum.tr-TR.resx (example for Turkish)
  • Gollum.en-US.resx (example for American english).
  • Gollum.de-DE.resx (example for German)

Part 4: Reading resource by code


Reading Global Resources by code

To read a global resource (resource files in App_GlobalResources) folder by code use the following syntax:

  • HttpContext.GetGlobalResourceObject("Gollum","Page_Title")
  • HttpContext.GetGlobalResourceObject("Gollum","Page_Title",CultureInfo.CurrentUICulture)

Reading Resources in 14Hive/Resource folder

To read resources within the Resource folder for example in webparts, you can use the helper method

uint lang = SPContext.Current.Web != null ? SPContext.Current.Web.Language : 1033;
lblHeaderTitle.Text = SPUtility.GetLocalizedString("$Resources:Features", "WebPartTitle",lang)


Final Words

Before you start creating a SharePoint Project you should think about of which items your project will consist and which type of resource handling is appropriate.

As an example you have a project with multiple pages and features. But one of these pages is used in the Central Administration site and the other on you normal SharePoint web application.

To use Global Resources you had to deploy your solution to all webapplications, because the Central Admin has its own web application with its own App_GlobalResource folder. Perhaps it would make more sense to deploy it into the 14Hive/Resource folder and use code to localize in this case to prevent multiple deployments.