Dienstag, 8. Juli 2014

SPFile.Publish() writes 'System Account' to 'ModifiedBy'-column instead of current user

I had the following scenario:

Documents that are dragged&dropped in a documentlibrary should be enriched with additional metadata and after that they should be published.
The document library has versions enabled with minor versions.

All action takes place in a SPItemEventReceiver, the details for that I'll omit for now.

Now when it came to do spFile.Publish(), after that the "ModifiedBy"-Column has 'System Account' instead of the current user and there seemed no way to set it manually. Neither the Publish-Method had a parameter to set the current user, nor SPListItem.SystemUpdate or SPListItem.UpdateOverwriteVersion worked:

- SystemUpdate won't save changes to CreatedBy/Created or ModifiedBy/Modified-fields.
- UpdateOverwriteVersion would work but creates a minor version (1.1 in my case)

But then I found a solution here.

I couldn't believe that and tried it out and it really worked! The post said, the SPSite-Object should be opened by adding the SPUserToken to the constructor and then SPFile.Publish() will save the user as last modifier. To show a concrete minimal example:

using (SPSite spSite = new SPSite(properties.SiteId, properties.OriginatingUserToken)) {
 using (SPWeb spWeb = spSite.OpenWeb(serverRelativeWebUrl)) {
  SPListItem spListItem = spWeb.GetListItem(properties.Web.ServerRelativeUrl); // make sure, the file is checked in before or try to acces or you get a file-not-found-error here 
  SPFile spFile = spListItem.File; 
  ... add more data to spListItem... 
  spListItem.SystemUpdate(false); 
  ... check for enabled versions, etc... 
  spFile.Publish(); // writes the SPUser related to the SPUserToken from properties.OriginatingUserToken to 'ModifiedBy'-column, if SPSite is instantiated with new SPSite(properties.SiteId) only, 'System Account' is used
 }
}

properties is the default SPItemEventProperties-object from the SPItemEventReceiver.

Pay attention, the "Modified"-Column (DateTime) changes to DateTime.Now. But this was not in the focus of my requirement.

I always thought, using new SPSite(Guid) would open  the SPSite in current user's context but that's not true as a closer look by using Reflector shows. Have a look at two of the default constructors from SPSite-class:

public SPSite(System.Guid id) : this(id, SPFarm.Local, SPUrlZone.Default, SPSecurity.GetDefaultUserToken())


public SPSite(System.Guid id, SPUserToken userToken) : this(id, SPUrlZone.Default, userToken)

Do you note the difference? :-)