| Subcribe via RSS

Create/Delete site collection with PowerShell

Februar 6th, 2010 | No Comments | Posted in Uncategorized

Today I played some minutes around with Powershell for SharePoint 2010 and easily created a new site collection and also deleted it.

Create new site collection:

New-SPSite  -url http://mymachine/sites/powershell -template STS#0  -OwnerAlias “mydomain\alexander.bruett“  -Name “Powershell Testsite”

Delete site collection:

Remove-SPSite -Identity http://hbv83025270/sites/powershell -Confirm:$False

If I were an IT-Pro, I’d love this :-)

Tags: , ,

SharePoint 2010 and Visual Studio 2010 Beta 2 target Framework problems

Januar 21st, 2010 | No Comments | Posted in Uncategorized

Today, I tried to add a reference to the assembly Microsoft.Office.Server.Search.dll and got this warning:

The primary reference "Microsoft.Office.Server.Search, Version=14.0.0.0,
Culture=neutral, PublicKeyToken=71e9bce111e9429c,
processorArchitecture=MSIL" could not be resolved because it has an
indirect dependency on the framework assembly
"System.Web.DataVisualization, Version=3.5.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35" which could not be resolved in the
currently targeted framework. ".NETFramework,Version=v3.5". To resolve
this problem, either remove the reference
"Microsoft.Office.Server.Search,Version=14.0.0.0, Culture=neutral,
PublicKeyToken=71e9bce111e9429c, processorArchitecture=MSIL" or
retarget your application to a framework version which contains
"System.Web.DataVisualization, Version=3.5.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35

Solution:

Create a .reg file and add this lines:

Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\.NETFramework\
v2.0.50727\AssemblyFoldersEx\Chart Controls]
@="C:\\Program Files (x86)\\Microsoft Chart Controls\\Assemblies"

Run this file and the entry will be added to the registry. Restart Visual Studio and the warning will be gone.

Tags: , , , ,

SharePoint Performance Checklist

September 7th, 2009 | No Comments | Posted in SharePoint

I’ve made lots of performance tunings during the last weeks and figured out that this can be a weird, long and complex task. On the one hand there are many built-in mechanism that can be configured and on the other hand there are lots of things you shound pay attention to when you develop your own web parts.

I’ve assmebled a little checklist you can walk through. I do not cover everything in detail but I’ve linked a couple of online posts and articles where you can start reading if you need more information on specific tasks.

checkbox

Limit global Navigation & Permission breaks

If you have a collaboration site collection with individual permissions on each website or at list level then your navigation controls can slow down your sharepoint performance. SharePoint has to check your permissions for each item listed in the navigation controls what can be very time consuming. Because the global navigation is placed on every web site this impacts your whole site collection.

So if you have lots of permission breaks either limit the items displayed by the navigation controls and eleminate the default navigation treeview or implement your custom navigation provider using caching mechanisms and activate object caching.

checkbox

Activate Blob Cache

The blob cache can be used to reduce the round trips between the WFE and the database server. If activated, all images stored within the site collection are cached on the hard disk of the WFE server. The blob cache can only be configured within the web.config of each web application.

Another big point of the blob cache ist the max-age attribute. It tells the browser not to request that image again for a given period of time. So you can use this attribute to reduce the requests between the browser and the WFE.

In the web.config file search for the <BlobCache …> section and set it to the following example:

<BlobCache location="C:\blobCache" path="\.(gif|jpg|png|css|js)$" maxSize="10" max-age="86400" enabled="true"/>

Now the cache is enabled and the files with the defined extensions are stored on WFE hard disk and the browser will not rerequest them for about 24 hours.

Selvagan posted a great article about the blob cache.

Tip: Store the cached files on a different hard disk as the web server to maximize the I/O throughput.

If you want to clear the blob cache on all WFE servers at once you need this codeplex solution.

checkbox

Activate Output Cache

Output caching enables SharePoint to cache HTML markup of Web Parts or complete web pages. This makes sense most if you have a publishing page with anonymous access enabled. If you enable output caching on a site with authenticated users the output is cached for each user individually. This can become a memory issue.

Read more in the output caching secion of this MSDN article.

checkbox

Activate Object Cache

SharePoint can cache objects like navigation elements and Cross-list query results. This should be activated on every SharePoint site. Microsoft has posted an article on how to activate and configure the object cache.

checkbox

Indexing of columns

If you have large list views that are ordered or filtered you should place an index on those list columns where you set the filter on. An index can also increase the performance if you have a lookup field with many items in the lookup list.

Have a look at this Microsoft article on managing lists and libraries with many items and read the Microsoft White Paper: Working with large lists in Office SharePoint Server 2007.

Andreas Grabner wrote an article on how list column indexing works under the hood.

checkbox

Be careful using the Content Query Web Part

If you use the Content Query Web Part to collect data from large lists or from your whole site collection you have to be knowing what’s happening in detail. You can increase CQWP performance using object cache and implementing your own CQWP. Ranjan Banerji wrote a great article on content query web part performance.

checkbox

Use Warmup Timer Job

If you restart your server for maintenance or reset your IIS during a solution deployment all your web applications are shut down. Now, when the first user requests your website it takes up to a couple of minutes until the web application is reloaded and caches are filled. It would be nice if theres someone who takes a browser and calls every website before the first user does.

Joel Oleson wrote a post about a script that acts like that “someone” and triggers all websites of your site.

Andrew Connell write a post on how to implement the script as a timer job. You can download the timer job from msdn.

checkbox

Watch your crawler

A misconfigured crawler can push your server cpu usage to 100%  from dusk till dawn. This can happen very quickly if you have an intranet website with lots of members creating new content and uploading tons of documents. Then the crawl duration is growing very quickly.

For example if you scheduled your crawler to run every 30 minutes and the crawl duration is longer than 30 minutes your server is crawling all the day. So you should check the crawl log periodically. If your crawl duration becomes too long you have to reconfigure your crawl schedule and crawl your content only once every two hours. If your search index may not be out of date for more than 30 minuts you have to add more search server to the farm.

Patrick Thisseghem wrote a great book about the SharePoint search and indexing engine: Inside the Index and Search Engines: Microsoft Office SharePoint Server 2007

checkbox

Use tools to measure site request performance

Use tools like Fiddler, YSlow and Firebug to find out whats going on between the browser and your SharePoint server.  Find out if there are long waiting times, too many server requests or ineffective java scripts slowing down your website. Use these tools to check if blob caching works.

SharePoint works best if used with Microsoft Internet Explorer but I love the Firefox plugins YSlow and Firebug because they are very easy to use, detailed and give lots of hints if your performance is bad.

checkbox

Measure your Web Part performance

Every custom web part can slow down your SharePoint website. The SharePoint API often performes not as expected and this can impact your Web Part performance. Use the Stopwatch class in your web part to measure critical code parts and write the results into the web part. Implement a Web Part property to switch on/off the stopwatch results.

There are developers out there saying perfomance measures should be part of productive code and rather be made using unit tests or console applications. Those developers are basically right but might have never been to the wild west of SharePoint. A Web Part lives within a SharePoint site and no unit test or console application can simulate that so you have to use your productive web part to make performance tests.

Tags: , , , , ,

What to do if SharePoint throws “500 Internal Server Error”?

Mai 28th, 2009 | No Comments | Posted in SharePoint

Yesterday we added a new server to the SharePoint farm. When calling a site over that new server we got an 500 Internal Server Error. There was no entry in the EventLog and even no information in the server log files. We wer trying lots of things but nothing got better. So what could we do to get more information?

We changed the diagnostics logging level from Unexpected to Verbose:

Diagnostics Logging Level

(Central Administration > Operations > Diagnistic Logging)

Then we recalled the website and finally got an error in the log files:

"Cannot make a cache safe URL for "init.js", file not found.
Please verify that the file exists under the layouts directory."

The file is part of every language pack so in our case we did not install a language pack on the new server that is used by the site.

Although this is a simple option to get more error information it’s forgotten or overseen very often.

Alex
Tags: , , ,

Simplify reading and writing field values of type SPFieldUser, SPFieldUrl and SPFieldLookup using extension methods

Februar 8th, 2009 | 11 Comments | Posted in .NET, SharePoint

To read or write values to fields of type SPFieldUser or SPFielLookup is an annoying task because you have to create field value objects bevore you can write your values into a field. The following post descripes how you can encapsulate those tasks to extension methods and slim your code to make it more readable.

SPFieldUser

The following code displays a couple of extension methods extending the SPListItem class for simple reading and writing access to fields of type SPFieldUser.

using System;
using System.Collections.Generic;
using Microsoft.SharePoint;
 
namespace ExtensionMethods
{
    public static class SPListItemExtensions
    {
        /// <summary>
        /// Returns the login name of an User-Field.
        /// </summary>
        public static string GetFieldValueUserLogin(this SPListItem item, 
          string fieldName)
        {
            if (item != null)
            {
                SPFieldUserValue userValue = 
                  new SPFieldUserValue(
                    item.Web, item[fieldName] as string);
                return userValue.User.LoginName;
            }
            else
            {
                return string.Empty;
            }
        }
 
        /// <summary>
        /// Sets the value of a User-Field to a login name.
        /// </summary>
        public static void SetFieldValueUser(this SPListItem item, 
          string fieldName, string loginName)
        {
            if (item != null)
            {
                item[fieldName] = item.Web.EnsureUser(loginName);
            }
        }
 
        /// <summary>
        /// Sets the value of a User-Field to an SPPrincipal 
        /// (SPGroup or SPUser).
        /// </summary>
        public static void SetFieldValueUser(this SPListItem item, 
          string fieldName, SPPrincipal principal)
        {
            if (item != null)
            {
                item[fieldName] = principal;
            }
        }
 
        public static void SetFieldValueUser(this SPListItem item, 
          string fieldName, IEnumerable<SPPrincipal> principals)
        {
            if (item != null)
            {
                SPFieldUserValueCollection fieldValues = 
                  new SPFieldUserValueCollection();
 
                foreach (SPPrincipal principal in principals)
                {
                    fieldValues.Add(
                      new SPFieldUserValue(
                        item.Web, principal.ID, principal.Name));
                }
                item[fieldName] = fieldValues;
            }
        }
 
        /// <summary>
        /// Sets the value of a multivalue User-Field to 
        /// a list of user names.
        /// </summary>
        public static void SetFieldValueUser(this SPListItem item, 
          string fieldName, IEnumerable<string> loginNames)
        {
            if (item != null)
            {
                SPFieldUserValueCollection fieldValues = 
                  new SPFieldUserValueCollection();
 
                foreach (string loginName in loginNames)
                {
                    SPUser user = item.Web.EnsureUser(loginName);
                    fieldValues.Add(
                      new SPFieldUserValue(
                        item.Web, user.ID, user.Name));
                }
 
                item[fieldName] = fieldValues;
            }
        }
    }
}

The following lines of code demonstrate how these extension methods can be used.

using ExtensionMethods;
 
SPList list = web.Lists["MyList"];
SPListItem item = list.Items[0];
 
// Get a user login of a SPFieldUser field
string userLogin = item.GetFieldValueUserLogin("Author");
 
// Set a SPFieldUser field to a user using the login
item.SetFieldValueUser("Person", "alexander.bruett");
 
// Set a SPFieldUser field that allows multiple values to a list of users
string[] persons = { "alexander.bruett", "paul.panzer" };
item.SetFieldValueUser("Mehrere Personen", persons);
 
// Set a SPFieldUser field to a SPUser
SPUser user = web.Users[1];
item.SetFieldValueUser("Person", user);
 
// Set a SPFieldUser field to a list of SPPrincipal
SPGroup group = web.Groups[0];
SPUser user = web.Users[0];
SPUser user2 = web.EnsureUser("alexander.bruett");
SPUser user3 = web.EnsureUser("Domain Users"); ;
SPPrincipal[] principals = { group, user, user2, user3 };
item.SetFieldValueUser("AssignedTo", principals);

Especially setting fields that allow multiple values now needs only a couple of code lines.

SPFieldLookup

The SPFieldLookup field is a little bit more complicated if you want to set the field only by the lookup string. You have to query the ID of the lookup value before you can set the field value. The following extension methods simplify this task.

/// <summary>
/// Returns the value of a Lookup Field.
/// </summary>
private static string GetFieldValueLookup(this SPListItem item, 
    string fieldName)
{
    if (item != null)
    {
        SPFieldLookupValue lookupValue = 
            new SPFieldLookupValue(item[fieldName] as string);
        return lookupValue.LookupValue;
    }
    else
    {
        return string.Empty;
    }
}
 
/// <summary>
/// Returns the value of a Lookup-Field with multiple values.
/// </summary>
public static IEnumerable<string> GetFieldValueLookupCollection(
    this SPListItem item, string fieldName)
{
    List<string> result = new List<string>();
    if (item != null)
    {
        SPFieldLookupValueCollection values = 
            item[fieldName] as SPFieldLookupValueCollection;
 
        foreach (SPFieldLookupValue value in values)
        {
            result.Add(value.LookupValue);
        }
    }
    return result;
}
 
/// <summary>
/// Returns the SPFieldLookupValue instance of a lookup value. 
/// The ID value will be obtained using SPQuery.
/// </summary>
private static SPFieldLookupValue GetLookupValue(
    SPWeb web, SPFieldLookup field, string lookupValue)
{
    string queryFormat = 
        @"<Where>
            <Eq>
                <FieldRef Name='{0}' />
                <Value Type='Text'>{1}</Value>
            </Eq>
          </Where>";
 
    string queryText = 
        string.Format(queryFormat, field.LookupField, lookupValue);
    SPList lookupList = web.Lists[new Guid(field.LookupList)];
 
    SPListItemCollection lookupItems = 
        lookupList.GetItems(new SPQuery() { Query = queryText });
 
    if (lookupItems.Count > 0)
    {
        int lookupId = 
            Convert.ToInt32(lookupItems[0][SPBuiltInFieldId.ID]);
 
        return new SPFieldLookupValue(lookupId, lookupValue);
    }
    else
    {
        return null;
    }
}
 
/// <summary>
/// Sets the value of a Lookup-Field.
/// </summary>
public static void SetFieldValueLookup(
    this SPListItem item, string fieldName, string lookupValue)
{
    if (item != null)
    {
        SPFieldLookup field = 
            item.Fields.GetField(fieldName) as SPFieldLookup;
        item[fieldName] = GetLookupValue(item.Web, field, lookupValue);
    }
    else
    {
        item[fieldName] = null;
    }
}
 
/// <summary>
/// Set the values of a Lookup-Field with multiple values allowed.
/// </summary>
public static void SetFieldValueLookup(this SPListItem item, 
    string fieldName, IEnumerable<string> lookupValues)
{
    if (item != null)
    {
        SPFieldLookup field = 
            item.Fields.GetField(fieldName) as SPFieldLookup;
 
        SPFieldLookupValueCollection fieldValues = 
            new SPFieldLookupValueCollection();
 
        foreach (string lookupValue in lookupValues)
        {
            fieldValues.Add(
                GetLookupValue(item.Web, field, lookupValue));
        }
        item[fieldName] = fieldValues;
    }
}

That’s been a lot of code but take a look at the following lines of code. Especially the setting task becomes very easy now.

// Reads the value of a SPFieldLookup field
string value = item.GetFieldValueLookup("LookupFieldName");
 
// Read lookup values of a SPFieldLookup field 
// with multiple values allowed
IEnumerable<string> values = 
  item.GetFieldValueLookupCollection("MultiLookupFieldName");
 
// Set the value of a SPFieldLookup field
item.SetFieldValueLookup("LookupFieldName", "LookupValue");
 
//Set the value of a SPFieldLookup field with multiple values allowed
string[] values = {"Hamburg", "London" };
item.SetFieldValueLookup("MultiLookupFieldName", values);

SPFieldUrl

Setting and getting SPFieldUrl field values is very simple:

/// <summary>
/// Returns the value of an Url-Field.
/// </summary>
public static string GetFieldValueUrl(
    this SPListItem item, string fieldName)
{
    if (item != null)
    {
        SPFieldUrlValue urlValue = 
            new SPFieldUrlValue(item[fieldName] as string);
        return urlValue.Url;
    }
    else
    {
        return string.Empty;
    }
}
 
/// <summary>
/// Sets the value of an URL-Field.
/// </summary>
public static void SetFieldValueUrl(this SPListItem item, 
    string fieldName, string url, string description)
{
    if (item != null)
    {
        item[fieldName] = 
            new SPFieldUrlValue() 
                { 
                    Description = description, 
                    Url = url 
                };
    }
}

The following lines of code show how to get and set values of the SPFieldUrl field using the extension methods above:

// Get the url of a SPFieldUrl field
string url = item.GetFieldValueUrl("URL");
 
// Set the url and description of a SPFieldUrl field
item.SetFieldValueUrl("URL", "http://www.alexbruett.net", "Alex' Blog");

Conclusion

Using extension methods to simplify the access to complex and SharePoint list fields makes your code more readable and reduces the lines of code.

You could also use helper classes instead of extension methods but you will have to write more code to call a helper method instead of calling a extension method.

Tags: , , , , , , ,

Query whole site collection using SPSiteDataQuery

Februar 6th, 2009 | 1 Comment | Posted in .NET, SharePoint

Using the SPQuery class you can query one SharePoint list for items.

To set up a query across a whole site collection you can use the SPSiteDataQuery object.

The following method fetches all .doc files from all doclibs of the site collection and prints out a list of urls to those items.

public void TestSiteDataQuery()
{
  using (SPSite site = new SPSite("http://localhost"))
  {
    using (SPWeb web = site.OpenWeb("/"))
    {
      SPSiteDataQuery query = new SPSiteDataQuery();
 
      // Search in doclibs only
      query.Lists = "<Lists BaseType='1' />";
 
      // Only .doc files
      query.Query =
      @"<Where>
          <Eq>
            <FieldRef Name='DocIcon' />
            <Value Type='Computed'>doc</Value>
          </Eq>
        </Where>";
 
      // Select only needed columns: file reference
      query.ViewFields = "<FieldRef Name='FileRef' />";
 
      // Search in all webs of the site collection
      query.Webs = "<Webs Scope='SiteCollection' />";
 
      // Perform the query
      DataTable table = web.GetSiteData(query);
 
      // Generate an absolute url for each document
      foreach (DataRow row in table.Rows)
      {
        string relativeUrl = 
          row["FileRef"].ToString().Substring(
            row["FileRef"].ToString().IndexOf("#") + 1);
        string fullUrl = site.MakeFullUrl(relativeUrl);
 
        // Write urls to console
        Console.WriteLine(fullUrl);
      }
    }
  }
}
Tags: , , ,

Silverlight Host Web Part

Oktober 4th, 2008 | No Comments | Posted in SharePoint, Silverlight

Description
Lot’s of people start developing Silverlight applications these days and I asked myself: Why not host these applications in SharePoint?

I developed a very simple web parts that loads a silverlight application file (.xap) stored within a document library and runs it within its viewspace.

Requirements
Before you can deploy and use the web part in your SharePoint farm you have to complete the following tasks:

  • 1. Install Silverlight 2 Beta 2 on the server.
  • 2. Copy the file System.Web.Silverlight.dll to the GAC (The file is usually located in C:\Program Files\Microsoft SDKs\Silverlight\v2.0\Libraries\Server)
  • 3. Make neccessary adjustments to the web.config. This step is quite complex so I recommend you watch this screencast from Patrick Tisseghem and follow it step by step.
  • 4. Add this MIME type mapping to the IIS Website: .xap -> application/x-silverlight-2-b2

If you want to recompile the source code of this web part you have to install Visual Studio 2008 and the Silverlight Tools for VS 2008 and the Visual Studio Extensions for SharePoint 1.2.

Download, Deploy and Use

  • 1. Download the wsp file (setup.bat included) and install the web part.
  • 2. Download this sample .xap file that you can show within your silverlight web part.
  • 3. Ensure that the feature “Silverlight 2 Beta 2 Host Web Part Feature” is activated within the site collection.
  • 4. Upload the .xap file into a document library.
  • 5. Edit a page and add the web part “Silverlight 2 Beta 2 Host Web Part” to a web part zone.

  • 6. Now edit the web part properties and set the property “XAP URL” with the url to the xap file.

  • 7. Exit edit mode and refresh the page. The web part should show the silverlight application now.

Source Code

You can download the web part project here.

The first step is to ensure that a script manager exists on the page.

protected override void OnLoad(EventArgs e)
{
    base.OnLoad(e);
    ScriptManager sm = ScriptManager.GetCurrent(this.Page);
    if (sm == null)
    {
        sm = new ScriptManager();
        Controls.AddAt(0, sm);
    }
}

The second step is to provide a web part property to consume a URL to the .xap file.

[WebBrowsable(true)]
[Personalizable(PersonalizationScope.Shared), Category("Silverlight")]
[FriendlyName("XAP URL")]
[Description("URL to XAP file")]
public string XapUrl
{
    get { return m_xapUrl; }
    set { m_xapUrl = value; }
}
protected string m_xapUrl = string.Empty;

The third step is to create a Silverlight control and load the Silverlight application into it:

protected override void CreateChildControls()
{
    base.CreateChildControls();
 
    try
    {
        if (!string.IsNullOrEmpty(XapUrl))
        {
            Silverlight ctrl = new Silverlight();
            ctrl.ID = this.ID + "_" + "SilverLightControl";
            ctrl.Source = XapUrl;
            ctrl.Width = new Unit(100, UnitType.Percentage);
            ctrl.Height = new Unit(m_controlHeight, 250);
            Controls.Add(ctrl);
        }
        else
        {
            Label lbl = new Label();
            lbl.Text = "Provide the url to a .xap file.";
            lbl.ID = this.ID + "_" + "LblMessage";
            Controls.Add(lbl);
        }
    }
    catch (Exception ex)
    {
        Label lbl = new Label();
        lbl.Text = string.Format("Error: " + ex.Message);
        lbl.ID = this.ID + "_" + "LblMessage";
        Controls.Add(lbl);                
    }
}

Have a nice Silverlight day,
Alex

Tags: , ,

Getting started with custom SharePoint Event Receivers

Juli 18th, 2008 | 6 Comments | Posted in SharePoint

In this post I describe how to create a custom SharePoint Event Receiver using the Visual Studio Extensions for SharePoint Services.

Software Requirements

Windows Server 2003/2008
SharePoint Services 3.0/Server 2007
Visual Studio 2005/2008
Visual Studio Extensions für SharePoint Services

Download Visual Studio Extensions

Visual Studio 2005: Download page
Visual Studio 2008: Download page
Preconfigured Virtual Machine: Download page

Description
We will develop an event receiver that prevents documents from being deleted by cancelling all delete operations.

Step 1: Set up visual studio project
Open Visual Studio and create an „Empty“ SharePoint Project.

Now add a new item to the project called Event Receiver (Project -> Add new Item -> Event Receiver)

Pick document library.

Now your project should look like this:

Weg got a strong name key to sign the assembly that will be deployed to the global assembly cache (GAC), some SharePoint references and two code files and xml files.

The ItemEventReceiver class contains all events that will be fired when something happens to list items like adding, updating or deleting of files.
The ListEventReceiver class contains all events that will be fired when something happens to the list itself like adding or removing columns

Step 2: Add event code

We want to cancel delete operations so we have to implement an event in the ItemEventReceiver class. Open it and uncomment the ItemDeleting event.

///
/// Synchronous before event that occurs before an existing item 
/// is completely deleted.
///
/// A Microsoft.SharePoint.SPItemEventProperties object that 
/// represents properties of the event handler.
public override void ItemDeleting(SPItemEventProperties properties)
{
  properties.Cancel = true;
  properties.ErrorMessage = "Deleting of Items not allowed!";
}

Build the project to check for errors, there shouldn’t be any.

Step 3: Deploy

Visual Studio can deploy the event receiver for you. Just select Build –> Deploy from the menu.

What happen’s:
Visual Studio creates one feature for each of your event receivers and uses the SharePoint console to check for errors. If there are no errors visual studio creates a deployable solution package (wsp) and a setup.bat script that can be used to deploy and undeploy the solution. Finally visual studio executes the setup.bat.

Your event receiver is now deployed and activated on the root web site, not on sub sites.
If you want to test te event receiver on a sub site you have to activate it first: Navigate to Site Settings -> Site Feature and activate the feature MyEventReceiverItemEventReceiver.
(I’ll show how to change the receiver name later in this article.)

Step 4: Test

Pick an existing document library on the site or create a new one. Upload a document and try to delete it. You should receive a message like this:

If you deactivate the feature MyEventReceiverItemEventReceiver you will be able to delete the file.

Step 5: Feature description

To give the feature a more meaningful description we can modify the feature.xml. To find this file make invisible files visible to your solution explorer:

Discover the pkg folder and open the file MyEventReceiverItemEventReceiver\feature.xml

You can modify the Title attribute and add a Description attribute:

<?xml version="1.0" encoding="utf-8"?>
<Feature 
  Id="ddeefbb1-cff7-4198-9170-f1a477168e5e" 
  Title="File keeper" 
  Description="event receiver that prevents doclib files 
    from being deleted." 
  Scope="Web" 
  Version="1.0.0.0" 
  Hidden="FALSE" 
  DefaultResourceFile="core" 
  xmlns="http://schemas.microsoft.com/sharepoint/">
  <ElementManifests>
    <ElementManifest 
      Location="MyEventReceiverItemEventReceiver\ItemEventReceiver.xml" />
  </ElementManifests>
</Feature>

Now rebuild and deploy your project. In your SharePoint site navigate to Site Settings -> Site features. Here you see the new description so your SharePoint administrators can understand what’s this feature is about.

The event receiver is attached to all document libraries of the site where the feature is activated.
If you want to attach the Event Receiver to one single list you have to code this with a custom
Feature Event Receiver or use this tool:

EventReceiver-Installer for SharePoint

Have fun,
Alex

Tags: ,