Second Life of a Hungarian SharePoint Geek

December 16, 2010

How we can’t set rating in the name of other users from code?

Filed under: Rating, Reflector, Social computing, SP 2010 — Tags: , , , — Peter Holpar @ 01:32

Most of the time I blog about how one can do something, but today I will post about how you can’t.

In my last post I illustrated how to set rating in SharePoint 2010 from code. In a former post you can see how to get ratings of other users from code. I’ve assumed that if I have enough permissions in the system, I can use the same (or similar code) to set ratings for other users as well, but it turned out to be a bit more complex.

The goal was to create a method that enable me to act as other user from code to rate, tag and comment items, like this:

  1. SPList list = web.Lists["Rated Items"];
  2. SPListItem listItem = list.Items[0];
  3. SPUser user = web.EnsureUser(@"domain\user");
  4. SetRatingForListItem(listItem, 5, "Very good!", user);

My first attempt was to use standard SharePoint impersonation as described in this post:

How to Programmatically Impersonate Users in SharePoint

This is the result for this approach:

  1. private void SetRatingForListItem(SPListItem listItem, int ratingValue, String ratingTitle, SPUser user)
  2. {
  3.     SetRatingForUrl(String.Format("{0}/{1}", listItem.Web.Url, listItem.Url), ratingValue, ratingTitle, user);
  4. }
  5.  
  6. private void SetRatingForUrl(String url, int ratingValue, String ratingTitle, SPUser user)
  7. {
  8.     if (user == null)
  9.     {
  10.         using (SPSite site = new SPSite(url))
  11.         {
  12.             SetRatingForUrl(url, site, ratingValue, ratingTitle);
  13.         }
  14.     }
  15.     else
  16.     {
  17.         // we assume the process has impersonation privilages
  18.         // otherwise you need an extra round of RunWithElevatedPrivileges
  19.         Guid siteId = user.ParentWeb.Site.ID;
  20.         SPUserToken userToken = user.UserToken;
  21.  
  22.         SPSecurity.RunWithElevatedPrivileges(delegate()
  23.         {
  24.             using (SPSite site = new SPSite(siteId, userToken))
  25.             {
  26.                 SetRatingForUrl(url, site, ratingValue, ratingTitle);
  27.             }
  28.         });
  29.     }
  30.  
  31. }
  32.  
  33. private void SetRatingForUrl(String url, SPSite site, int ratingValue, String ratingTitle)
  34. {
  35.     SPServiceContext serviceContext = SPServiceContext.GetContext(site);
  36.     Uri uri = new Uri(url);
  37.  
  38.     SocialRatingManager ratingManager = new SocialRatingManager(serviceContext);
  39.     ratingManager.SetRating(uri, ratingValue, ratingTitle);
  40.     ratingManager.PropagateRating(uri);
  41. }

Based on my experiments this method works, but the rating is made in the name of the interactive user. The impersonation seems to have no effect at all when it used for social activity in SharePoint, like rating, tagging and commenting.

What’s the reason behind this?

You can get the answer easily using Reflector. As discussed in the first post, the “social’ classes, like SocialRatingManager  are implemented in the Microsoft.Office.Server.SocialData namespace in the Microsoft.Office.Server.UserProfiles.dll assembly and are derived from a common abstract base class called SocialDataManager. SocialDataManager contains a method called GetCurrentUserProfile.

This method is called in the derived classes before doing the actual work of social activity, like rating a URL (see for example SetRating method in SocialRatingManager), and its role is to return the UserProfile of the current user using the ProfileLoader.GetUserProfile method. The UserProfile calls the EnsureUserProfile method to get UserProfile instance through the GetUserProfile method of the UserProfileManager class.

This method uses the strCurrentAccountName property of the class to get the name of the current user and retrieve or to create the UserProfile for the user. This property gets the user name from the GetCurrentUserName method of the UserProfileGlobal class.

This method tries to get the user name from the actual context, like:

HttpContext.Current.User.Identity.Name

or

WindowsIdentity.GetCurrent().Name

that means standard SharePoint impersonation has no effect on it.

My second approach was to impersonating the user through the WindowsImpersonationContext as described in this article:

Using WindowsIdentity.Impersonate in SharePoint applicatons

It resulted the following code:

  1. private void SetRatingForListItem(SPListItem listItem, int ratingValue, String ratingTitle, SPUser user)
  2. {
  3.     SetRatingForUrl(String.Format("{0}/{1}", listItem.Web.Url, listItem.Url), ratingValue, ratingTitle, user);
  4. }
  5.  
  6. private void SetRatingForUrl(String url, int ratingValue, String ratingTitle, SPUser user)
  7. {
  8.     if (user == null)
  9.     {
  10.         using (SPSite site = new SPSite(url))
  11.         {
  12.             SetRatingForUrl(url, site, ratingValue, ratingTitle);
  13.         }
  14.     }
  15.     else
  16.     {
  17.         WindowsImpersonationContext impersonationContext = null;
  18.         try
  19.         {
  20.             WindowsIdentity userIdentity = new WindowsIdentity(GetUpn(user));
  21.             WindowsImpersonationContext context = userIdentity.Impersonate();
  22.  
  23.             using (SPSite site = new SPSite(user.ParentWeb.Site.ID))
  24.             {
  25.                 SetRatingForUrl(url, site, ratingValue, ratingTitle);
  26.             }
  27.         }
  28.         finally
  29.         {
  30.             if (impersonationContext != null)
  31.             {
  32.                 impersonationContext.Undo();
  33.             }
  34.         }
  35.  
  36.     }
  37.  
  38. }
  39.  
  40.  
  41. private static string GetUpn(SPUser spUser)
  42. {
  43.     string[] userName = spUser.LoginName.Split('\\');
  44.     System.DirectoryServices.ActiveDirectory.Domain domain =
  45.         System.DirectoryServices.ActiveDirectory.Domain.GetCurrentDomain();
  46.     return userName[1] + "@" + domain.Name;
  47. }
  48.  
  49.  
  50. private void SetRatingForUrl(String url, SPSite site, int ratingValue, String ratingTitle)
  51. {
  52.     SPServiceContext serviceContext = SPServiceContext.GetContext(site);
  53.     Uri uri = new Uri(url);
  54.  
  55.     SocialRatingManager ratingManager = new SocialRatingManager(serviceContext);
  56.     ratingManager.SetRating(uri, ratingValue, ratingTitle);
  57.     ratingManager.PropagateRating(uri);
  58. }

Unfortunately, this approach doesn’t work either.

If the user we would like to impersonate, has no log on locally user right (that is more than typical in the case of a server), or if you grant that right for the user, then you will receive an SqlException "A transport-level error has occurred when sending the request to the server. (provider: Shared Memory Provider, error: 0 – Either a required impersonation level was not provided, or the provided impersonation level is invalid." when trying to get the server or service context from the SPSite object.

To sum up my experience, up to now I found no way to work around the limitation the “social” impersonation. If you have more success in that area, you are welcome to share your results with us.

October 17, 2010

Decoding the content of the BinarySerializedWebPart – The theory

When you save your SharePoint site as a template, the files within the generated WSP package contain the customized web parts as BinarySerializedWebPart, a format not easy to work with (see an example in my former post: Publishing files stored in the file system through external list).

Up to know I found no easy way to look inside the real content of such a web part. You can import the solution into Visual Studio 2010 (see example here), but I feel it a bit uncomfortable. If you can attach to the server using SharePoint Designer 2010, you can check the content there, but sometimes you might be offline, for example, the WSP was created in a restricted network and was sent via e-mail.

The following snippet illustrates a BinarySerializedWebPart:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  3.   <Module Name="FileSystemForms" Url="Lists/Files" RootWebOnly="FALSE" SetupPath="pages">
  4.     <File Url="DispForm.aspx" Type="Ghostable" Path="form.aspx">
  5.       <BinarySerializedWebPart>
  6.         <GUIDMap>
  7.           <GUID Id="33ff2881_489d_4ce2_ac94_e81d64689d2a" ListUrl="Lists/Files" />
  8.         </GUIDMap>
  9.         <WebPart ID="{035cec7d-5f69-4dbf-a551-0b8203467c41}" WebPartIdProperty="" List="{$ListId:Lists/Files;}" Type="4"
  10.           Flags="0" DisplayName="" Version="4" Url="Lists/Files/DispForm.aspx" WebPartOrder="1" WebPartZoneID="Main"
  11.           IsIncluded="True" FrameState="0" WPTypeId="{feaafd58-2dc9-e199-be37-d6cdd7f84690}"
  12.           SolutionId="{00000000-0000-0000-0000-000000000000}" Assembly="" Class="" Src=""
  13.           AllUsers="B6Dt/kMAAAABAAAAAAAAAAIAAAAvX2xheW91dHMvaW1hZ2VzL2l0ZWJsLnBuZwAvRjFTaXRlL0xpc3RzL0ZpbGVzAP8BFCsAJQICAgMCAwEEAAICAhICFAEBAAIEBQtDb250cm9sTW9kZQspiAFNaWNyb3NvZnQuU2hhcmVQb2ludC5XZWJDb250cm9scy5TUENvbnRyb2xNb2RlLCBNaWNyb3NvZnQuU2hhcmVQb2ludCwgVmVyc2lvbj0xNC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj03MWU5YmNlMTExZTk0MjljAQUIRm9ybVR5cGUCBAEAAAIWAoYBCyo0U3lzdGVtLldlYi5VSS5XZWJDb250cm9scy5XZWJQYXJ0cy5XZWJQYXJ0RXhwb3J0TW9kZQICggEFGi9fbGF5b3V0cy9pbWFnZXMvaXRlYmwucG5nAn0FEy9GMVNpdGUvTGlzdHMvRmlsZXMFCFBhZ2VUeXBlCyl3TWljcm9zb2Z0LlNoYXJlUG9pbnQuUEFHRVRZUEUsIE1pY3Jvc29mdC5TaGFyZVBvaW50LCBWZXJzaW9uPTE0LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPTcxZTliY2UxMTFlOTQyOWMEBQdMaXN0VXJsZQUGTGlzdElkKClYU3lzdGVtLkd1aWQsIG1zY29ybGliLCBWZXJzaW9uPTIuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OSQzM2ZmMjg4MS00ODlkLTRjZTItYWM5NC1lODFkNjQ2ODlkMmEFD0xpc3REaXNwbGF5TmFtZWUClQEFJnszM0ZGMjg4MS00ODlELTRDRTItQUM5NC1FODFENjQ2ODlEMkF9BQ1YbWxEZWZpbml0aW9uBcUPDQo8VXNlckNvbnRyb2wgeDpDbGFzcz0iRm9ybVhtbFRvWGFtbC5Vc2VyQ29udHJvbDIiIHhtbG5zOng9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd2luZngvMjAwNi94YW1sIiB4bWxuczpTaGFyZVBvaW50PSJNaWNyb3NvZnQuU2hhcmVQb2ludC5XZWJDb250cm9scyIgeG1sbnM6c3lzdGVtPSJjbHItbmFtZXNwYWNlOlN5c3RlbTthc3NlbWJseT1tc2NvcmxpYiI+PFN0YWNrUGFuZWwgeDpOYW1lPSJGb3JtIj4NCjxTdGFja1BhbmVsLlJlc291cmNlcz4NCjxzeXN0ZW06U3RyaW5nIHg6S2V5PSJGb3JtTW9kZSI+RGlzcGxheTwvc3lzdGVtOlN0cmluZz4NCjxzeXN0ZW06U3RyaW5nIHg6S2V5PSJGb3JtVHlwZSI+TGlzdEZvcm08L3N5c3RlbTpTdHJpbmc+DQo8L1N0YWNrUGFuZWwuUmVzb3VyY2VzPg0KPFN0YWNrUGFuZWwgeDpOYW1lPSJNYWluU2VjdGlvbnMiPjxHcmlkPjxHcmlkLkNvbHVtbkRlZmluaXRpb25zPg0KPENvbHVtbkRlZmluaXRpb24gU3R5bGU9IntTdGF0aWNSZXNvdXJjZSBtcy1mb3JtbGFiZWx9Ii8+DQo8Q29sdW1uRGVmaW5pdGlvbiBTdHlsZT0ie1N0YXRpY1Jlc291cmNlIG1zLWZvcm1ib2R5fSIvPg0KPC9HcmlkLkNvbHVtbkRlZmluaXRpb25zPjxHcmlkLlJvd0RlZmluaXRpb25zPg0KPFJvd0RlZmluaXRpb24gLz4NCjxSb3dEZWZpbml0aW9uIC8+DQo8Um93RGVmaW5pdGlvbiAvPg0KPFJvd0RlZmluaXRpb24gLz4NCjwvR3JpZC5Sb3dEZWZpbml0aW9ucz4NCjxTaGFyZVBvaW50OkZpZWxkTGFiZWwgR3JpZC5Db2x1bW49IjAiIEdyaWQuUm93PSIwIiBDb250cm9sTW9kZT0iRGlzcGxheSIgRmllbGROYW1lPSJOYW1lIiAvPg0KPENvbW1lbnQgRmllbGROYW1lPSJOYW1lIiBGaWVsZEludGVybmFsTmFtZT0iTmFtZSIgRmllbGRUeXBlPSJUZXh0IiAvPg0KPFNoYXJlUG9pbnQ6Rm9ybUZpZWxkIEdyaWQuQ29sdW1uPSIxIiBHcmlkLlJvdz0iMCIgQ29udHJvbE1vZGU9IkRpc3BsYXkiIEZpZWxkTmFtZT0iTmFtZSIgSW5jbHVkZURlc2NyaXB0aW9uPSJUcnVlIi8+DQo8U2hhcmVQb2ludDpGaWVsZExhYmVsIEdyaWQuQ29sdW1uPSIwIiBHcmlkLlJvdz0iMSIgQ29udHJvbE1vZGU9IkRpc3BsYXkiIEZpZWxkTmFtZT0iU2l6ZSIgLz4NCjxDb21tZW50IEZpZWxkTmFtZT0iU2l6ZSIgRmllbGRJbnRlcm5hbE5hbWU9IlNpemUiIEZpZWxkVHlwZT0iSW50ZWdlciIgLz4NCjxTaGFyZVBvaW50OkZvcm1GaWVsZCBHcmlkLkNvbHVtbj0iMSIgR3JpZC5Sb3c9IjEiIENvbnRyb2xNb2RlPSJEaXNwbGF5IiBGaWVsZE5hbWU9IlNpemUiIEluY2x1ZGVEZXNjcmlwdGlvbj0iVHJ1ZSIvPg0KPFNoYXJlUG9pbnQ6RmllbGRMYWJlbCBHcmlkLkNvbHVtbj0iMCIgR3JpZC5Sb3c9IjIiIENvbnRyb2xNb2RlPSJEaXNwbGF5IiBGaWVsZE5hbWU9IkNyZWF0ZWQiIC8+DQo8Q29tbWVudCBGaWVsZE5hbWU9IkNyZWF0ZWQiIEZpZWxkSW50ZXJuYWxOYW1lPSJDcmVhdGVkIiBGaWVsZFR5cGU9IkRhdGVUaW1lIiAvPg0KPFNoYXJlUG9pbnQ6Rm9ybUZpZWxkIEdyaWQuQ29sdW1uPSIxIiBHcmlkLlJvdz0iMiIgQ29udHJvbE1vZGU9IkRpc3BsYXkiIEZpZWxkTmFtZT0iQ3JlYXRlZCIgSW5jbHVkZURlc2NyaXB0aW9uPSJUcnVlIi8+DQo8U2hhcmVQb2ludDpGaWVsZExhYmVsIEdyaWQuQ29sdW1uPSIwIiBHcmlkLlJvdz0iMyIgQ29udHJvbE1vZGU9IkRpc3BsYXkiIEZpZWxkTmFtZT0iTGFzdE1vZGlmaWVkIiAvPg0KPENvbW1lbnQgRmllbGROYW1lPSJMYXN0IG1vZGlmaWVkIiBGaWVsZEludGVybmFsTmFtZT0iTGFzdE1vZGlmaWVkIiBGaWVsZFR5cGU9IkRhdGVUaW1lIiAvPg0KPFNoYXJlUG9pbnQ6Rm9ybUZpZWxkIEdyaWQuQ29sdW1uPSIxIiBHcmlkLlJvdz0iMyIgQ29udHJvbE1vZGU9IkRpc3BsYXkiIEZpZWxkTmFtZT0iTGFzdE1vZGlmaWVkIiBJbmNsdWRlRGVzY3JpcHRpb249IlRydWUiLz4NCjwvR3JpZD4NCjwvU3RhY2tQYW5lbD4NCjwvU3RhY2tQYW5lbD4NCjwvVXNlckNvbnRyb2w+AktkBRFQYXJhbWV0ZXJCaW5kaW5ncwXoAg0KPFBhcmFtZXRlckJpbmRpbmcgTmFtZT0iZHZ0X2Fwb3MiIExvY2F0aW9uPSJQb3N0YmFjaztDb25uZWN0aW9uIi8+DQogICAgICAgIDxQYXJhbWV0ZXJCaW5kaW5nIE5hbWU9IlVzZXJJRCIgTG9jYXRpb249IkNBTUxWYXJpYWJsZSIgRGVmYXVsdFZhbHVlPSJDdXJyZW50VXNlck5hbWUiLz4NCiAgICAgICAgPFBhcmFtZXRlckJpbmRpbmcgTmFtZT0iVG9kYXkiIExvY2F0aW9uPSJDQU1MVmFyaWFibGUiIERlZmF1bHRWYWx1ZT0iQ3VycmVudERhdGUiLz4NCiAgICAgICAgPFBhcmFtZXRlckJpbmRpbmcgTmFtZT0iTGlzdEl0ZW1JZCIgTG9jYXRpb249IlF1ZXJ5U3RyaW5nKElEKSIgRGVmYXVsdFZhbHVlPSIwIi8+DQogICAgICAgIA==" />
  14.       </BinarySerializedWebPart>
  15.       </File>
  16.     </Module>
  17. </Elements>

You can see at the first sight that the most critical part of the XML is the AllUsers attribute of the WebPart node.

Other BinarySerializedWebPart nodes may contain View or PerUser attributes with similar format, and another attribute, WPTypeId that is a GUID.

In this post I try to help you to understand the decoding (or we can call it deserialization but it is not really deserialization in its traditional meaning) process SharePoint applies when reading web parts from content database and on the other side the encoding (or serialization) of the web part when creating the WSP package on site template export.

If you don’t like to track call chains you might want to skip the following section although I think it may help to understand the whole story and gives you a picture about what happens under the cover of the export process and some other areas related to the solution of the problem, like how web parts are reconstructed from content database by a web part manager. In this case you can check my next post that contains the actual code.

First, the public static ExportWeb method of SPSolutionExporter class (Microsoft.SharePoint namespace in Microsoft.SharePoint assembly) is called. The sole task of this method is to instantiate a SPSolutionExporter object to call its private ExportWebAsSolution method. This method calls forward to another private method GenerateSolutionFiles and this later one calls the private ExportWebWebPart method calls the proc_EnumerateWebPartsForWeb stored procedure and reads the web parts from the database including the properties we need.

If you are a regular visitor of the SharePoint content database you may be familiar with the AllWebParts table and its fields. The four fields we focus on are neighbors in this table:  tp_View, tp_WebPartTypeId, tp_AllUsersProperties, tp_PerUserProperties. These fields are bound to the above-mentioned attributes.

The ExportWebWebPart method aggregates the web parts in the WebWebParts property (that is of type SortedList<string, List<WebWebPart>>) of the SPSolutionExporter class.

The information about the web part is stored in the members of the private WebWebPart class, the relevant ones are:

  1. public byte[] AllUsersProperties;
  2. public byte[] PerUserProperties;
  3. public byte[] View;
  4. public Guid WebpartTypeId;

Later the GenerateSolutionFiles method calls ExportModules and ExportLists methods, then both of these methods call the WriteModuleInnerElementsOfElements method. This method calls the WriteModuleElementIntoFeatureManifest method: once for the customized and once for the uncustomized entries. In this method the EmitAllUsersWebpart method is called, that calls forward into the WriteBlobWebpart method.

In WriteBlobWebpart method you can see that most of the attributes are written as simple strings, however byte arrays are written as Base64 encoded values. It means that the attributes with more complex values contain the same binary context as their corresponding database fields in the AllWebParts table.

OK, we already know that the values of these properties must be Base64 decoded to get a byte array. That is not a surprise for most of us, it could have been said at first sight having a bit of experience in Base64. But the question still remains: how could we reconstruct a web part using these binary values?

To answer the question, let’s see what happens internally in the SPWebPartManager (Microsoft.SharePoint.WebPartPages namespace, Microsoft.SharePoint assembly) when it reads the web parts from the content database.

For the sake of simplicity I list only the methods involved in the process as their follow each other, and limit myself only to the methods that lead us to the “decoding algorithm”:

OnInit -> OnPageInitComplete -> LoadWebParts -> CreateWebPartsFromRowSetData

The CreateWebPartsFromRowSetData method calls the internal static  CreateBinaryDeserializer method of the internal  BinaryWebPartDeserializer class (Microsoft.SharePoint.WebPartPages namespace in Microsoft.SharePoint assembly) that returns an instance of BinaryWebPartDeserializer or its derived internal class SPUserCodeWebPartDeserializer. Then its internal Deserialize method is called that finally returns a WebPart instance.

When you follow the call chain in Reflector, at this point you might be a bit misled, as when you click on the Deserialize method in the CreateWebPartsFromRowSetData method, the virtual Deserialize method of the BinaryWebPartDeserializer class is displayed, however, since the SPUserCodeWebPartDeserializer class might be instantiated within the CreateBinaryDeserializer method, you may have to follow the override Deserialize method of the SPUserCodeWebPartDeserializer class. The first step in this method is to call the Deserialize method of the base class.

The Deserialize method of the BinaryWebPartDeserializer class calls the LoadInitialWebPart method that decodes the byte array properties by calling the ByteArrayToObjectArray method of the BinaryWebPartSerialization class (Microsoft.SharePoint.WebPartPages namespace, Microsoft.SharePoint assembly). This method then calls the static DeserializeByteArrayToObject method (same namespace and assembly as former one). Finally the Deserialize and DeserializeValue methods of the ObjectStateFormatter class (System.Web.UI namespace, System.Web assembly) do the actual work of reading object from stream.

Important thing to note. The Deserialize method contains a check that the first byte of the stream must be 255, and the second one must be 1, otherwise an exception of “The serialized data is invalid” will be thrown. The binary data stored in the fields of AllWebParts table do not always start with these bytes, similar to the Base64 decoded XML attributes of the BinarySerializedWebPart node.

In my tests I simply ignored the bytes in the byte array before this specific byte pattern, and using the code showed that splitting the leading bytes produces a useable result. How the actual code does look like will be shown in the next part of the post.

October 2, 2010

Using call stack information to better understand SharePoint functionality

If you would like to get deeper understanding what happens in SharePoint and why,  probably .NET Reflector is your old friend. It helps you to investigate the code of the internal assemblies and to track call chains by clicking on the method name you would like to follow.

The functionality of Reflector is very nice, but it is sometimes simply more useful to see the calling process in action or check the values of the parameters the methods are called with. That is where call stack comes into the picture.

Recently, I study SharePoint external lists and try to understand the process better to be able to find a way to hook up some extensions to the base framework. In this process I find call stacks extremely useful.

By default, you can see something similar when looking to the Call Stack window in Visual Studio 2010.

image

This information helps you not too much as only your own methods are displayed, external calls are hidden.

Fortunately, you can turn on external code display as well as parameter values, as illustrated on the screenshot below.

image

As you can see there is a Go To Reflector menu either, but I really don’t suggest you to use that due to my bad experiences I wrote about earlier.

After enabling these options, Visual Studio displays the whole call stack, that is really what I needed this time. Now you shouldn’t follow method calls with Reflector step by step, you can check directly the method you think is important.

image

As you can see the parameter values are displayed with the method calls. The bad news is that it seems parameter values are displayed by calling the ToString() method on the parameter. At least I found no way to check the value of parameters having more complex types.

Last but not least you can get (almost) the same result from code. The following snippet shows how to do that using StackTrace, StackFrame, ParameterInfo and MethodBase classes. I wrote almost the same, as you can’t get the values of the parameters this way.

  1. using System;
  2. using System.Text;
  3. using System.Diagnostics;
  4. using System.Reflection;
  5.  
  6. namespace YourNameSpace
  7. {
  8.     public static class Helper
  9.     {
  10.         public static void OutputStackTrace()
  11.         {
  12.             StringBuilder sb = new StringBuilder();
  13.             StackTrace stackTrace = new StackTrace();
  14.             StackFrame[] stackFrames = stackTrace.GetFrames();
  15.             foreach (StackFrame stackFrame in stackFrames)
  16.             {
  17.                 MethodBase mb = stackFrame.GetMethod();
  18.                 sb.AppendFormat("{0}(", mb.Name);
  19.                 ParameterInfo[] pis = mb.GetParameters();
  20.  
  21.                 // aggregating parameters
  22.                 Array.ForEach<ParameterInfo>(pis,
  23.                     new Action<ParameterInfo>(pi => sb.AppendFormat("{0} {1}{2}",
  24.                         pi.ParameterType.Name,
  25.                         pi.Name,
  26.                         (pi.Position != pis.Length – 1) ? ", " : String.Empty)));
  27.  
  28.                 sb.AppendFormat(") – {0}\r\n", mb.DeclaringType.AssemblyQualifiedName);
  29.             }
  30.             String callStack = sb.ToString();
  31.             Debug.WriteLine(callStack);
  32.         }
  33.     }
  34. }

All you have to do is to include a call to this static method where you need the call stack information, and it is dumped to the Debug view of the Output window where you can copy it from for later use.

September 7, 2010

I hate “Go To Reflector” in Visual Studio

Filed under: Reflector, Visual Studio — Tags: , — Peter Holpar @ 00:09

If you follow my posts on this blog, you already know that Reflector is one of my favorite tools. A few days ago I was to look up again something in a SharePoint assembly but found that all of my registered assemblies are lost, and to make things even worse, all of my bookmarks, that served me as a map to the SharePoint libraries I worked with, disappeared also.

I remembered that the other day during debugging I accidentally chose the Go To Reflector menu in Visual Studio instead of its neighbor Run To Cursor (yes, you are right, I should have pressed simply Ctrl+F10). At that time I thought it has no consequence at all, but unfortunately I was wrong.

As it turned out, selecting this menu item will clean the registered (non-standard) assemblies from Reflector and registers only the assembly you selected in Visual Studio for investigation. I can reproduce this behavior and think it is not a very developer friendly feature.

Regarding my lost bookmarks – that is really a pain in my heart – I was not able to repro to clean them simply by using this menu, but somehow I suspect it has a relation to this mistake.

Lesson learned: one should backup the Reflector.cfg file regularly.

Blog at WordPress.com.