We are trying to create a custom event handler that would fire on ItemAdded event. The event handler then updates the document with a unique ID value to a column in this document library.
Our solution works fine, except when a user on Vista is trying to save a new document from Office 2007. In this scenario the document is stored to document library but Unique ID column is empty, and there is no exception.
Vista Users can upload document(s) to library without a problem. Everything else works fine on XP and Win2k3 operating systems.
Has anyone seen something similar, and what might be the problem here? To demonstrate the problem we are using DateTime.Now as unique ID.
using Microsoft.SharePoint;
public class TestReciever : SPItemEventReceiver
{
public override void ItemAdded(Microsoft.SharePoint.SPItemEventProperties properties)
{
try {
DisableEventFiring();
properties.ListItem("UniqueID Column") = DateTime.Now.ToString();
properties.ListItem.SystemUpdate();
EnableEventFiring();
}
catch (Exception ex) {
// handle exception
}
}
}
We have noticed exactly the same thing happening. When the 2007 doc is added to the library, the properties from the list are added to it (blank).
Then the (synchronous) event handler is called, updating the list with the correct values for the UniqueID column.
Then the inbuilt property mapping to 2007 docs kicks in and overwrites your values with those stored in the 2007 doc (not raising an item updated event again).
This means that the list now has a blank for your column.
If you change to an asynchronous event you may see what we did and that the slight delay for asynchronous meant that the property mapping to 2007 docs happened first (we think) meaning the value was stored correctly.
It is possible to break the property mapping between the list and office, but that is only a workaround.
I cannot for the life of me find where this info is on an MS site, but it is a known problem. Perhaps you can hang in there for SP2 (may even be fixed in SP1, but am unsure).
At the end we contacted Microsoft and they provided us with the following workaround solution. The key here is to delay item update in a separate thread.
private Guid listID;
private Guid itemID;
private Guid siteID;
public override void ItemAdded(Microsoft.SharePoint.SPItemEventProperties properties)
{
DisableEventFiring();
item = properties.ListItem;
listID = properties.ListId;
itemID = properties.ListItem.UniqueId;
siteID = properties.SiteId;
Threading.Thread setDocumentInternalIDThread = new Threading.Thread(SetInternalID);
setDocumentInternalIDThread.Start();
EnableEventFiring();
}
private void SetInternalID()
{
try {
Threading.Thread.Sleep(10000);
using (SPSite site = new SPSite(siteID)) {
using (SPWeb web = site.OpenWeb()) {
SPList list = web.Lists(listID);
SPListItem item = list.Items(itemID);
item(Common.CustomID) = Common.GetAlphaPrefix() + Common.GetDocNumber();
item.SystemUpdate();
}
}
}
catch (Exception ex) {
Log(ex.Message);
}
}
Another workaround is to set the custom field that is being updated in the event receiver to be read only (set the fields ReadOnly property to TRUE). This has a few pros and cons. This is ideal for a unique id solution, because there is no way for the user to modify the value. However, the field is hidden to the UI, so managing it becomes more troublesome, and the field can't be used in labels, or quick parts. You can set the ShowInDisplayForm property of the field to TRUE to have the field display in the view form, but not the edit form. The field can also be added to your views. This is the best workaround I have found for this issue to date.
This is an issue with Office 2007 and the WSS 3.0 xml parser. When the properties are promoted from the Office 2007 document, the parser attempts to assign document properties to the list properties in SharePoint (property promotion). The problem is that this event occurs after any ItemAdded, or ItemUpdated events, so the property gets overwritten with the value in the document, which is of course, blank. Microsoft has raised this as a bug, and I was hoping for a fix in SP2, but no such luck.
Related
Good day!
In my Orchard, I have several content types all with my custom part attached. This part defines to what users this content is available. For each logged user there is external service, which defines what content user can or cannot access. Now I need access restriction to apply everywhere where orchard display content lists, this includes results by specific tag from a tag cloud, or results listed from Taxonomy term. I seems can’t find any good way to do it except modifying TaxonomyServices code as well as TagCloud services, to join also my part and filter by it. Is this indeed the only way to do it or there are other solutions? I would like to avoid doing changes to built-in modules if possible but cannot find other way.
Thanks in advance.
I'm currently bumbling around with the same issue. One way I'm currently looking at is to hook into the content manager.
[OrchardSuppressDependency("Orchard.ContentManagement.DefaultContentManager")]
public class ModContentManager : DefaultContentManager, IContentManager
{
//private readonly Lazy<IShapeFactory> _shapeFactory;
private readonly IModAuthContext _modAuthContext;
public ModContentManager(IComponentContext context,
IRepository<ContentTypeRecord> contentTypeRepository,
IRepository<ContentItemRecord> contentItemRepository,
IRepository<ContentItemVersionRecord> contentItemVersionRepository,
IContentDefinitionManager contentDefinitionManager,
ICacheManager cacheManager,
Func<IContentManagerSession> contentManagerSession,
Lazy<IContentDisplay> contentDisplay,
Lazy<ISessionLocator> sessionLocator,
Lazy<IEnumerable<IContentHandler>> handlers,
Lazy<IEnumerable<IIdentityResolverSelector>> identityResolverSelectors,
Lazy<IEnumerable<ISqlStatementProvider>> sqlStatementProviders,
ShellSettings shellSettings,
ISignals signals,
//Lazy<IShapeFactory> shapeFactory,
IModAuthContext modAuthContext)
: base(context,
contentTypeRepository,
contentItemRepository,
contentItemVersionRepository,
contentDefinitionManager,
cacheManager,
contentManagerSession,
contentDisplay,
sessionLocator,
handlers,
identityResolverSelectors,
sqlStatementProviders,
shellSettings,
signals) {
//_shapeFactory = shapeFactory;
_modAuthContext = modAuthContext;
}
public new dynamic BuildDisplay(IContent content, string displayType = "", string groupId = "") {
// So you could do something like...
// var myPart = content.As<MyAuthoPart>();
// if(!myPart.IsUserAuthorized)...
// then display something else or display nothing (I think returning null works for this but
//don't quote me on that. Can always return a random empty shape)
// else return base.BuildDisplay(content, displayType, groupId);
// ever want to display a shape based on the name...
//dynamic shapes = _shapeFactory.Value;
}
}
}
Could also hook into the IAuthorizationServiceEventHandler, which is activated before in the main ItemController and do a check to see if you are rendering a projection or taxonomy list set a value to tell your content manager to perform checks else just let them through. Might help :)
From code I've automatically created a lot of similar sites (SPWeb) in my site collection from a site template (in Sharepoint Foundation). Every site has a home page on which I've added the "what's new" web part (found under "Social collaboration").
Even though the web part has several "target lists" (I'd have called it "source lists") added to it on the template site, this connection is lost on the sites created from the template. So I need to programmatically find all these web parts and add the target lists to them. Looping the web parts is not an issue - I've done that before - but I can't seem to find a word on the net on how to go about modifying this particular web part. All I have is a brief intellisense.
I've found out that it recides in the
Microsoft.SharePoint.Applications.GroupBoard.WebPartPages
namespace, but on the lists provided on MSDN this is one of very few namespaces that doesn't have a link to a reference documentation.
Does anyone have any experience of modifying this web part from code? If not, how would you go about to find out? I can't seem to figure out a method for this..
Here is how I did it. It worked really well. I had a feature that created several list instances and provisioned the What's New web part. In the Feature Receiver, I looped through all of the list instances, indexed the Modified field, and then added the list to the web part:
private void ConfigureLists(SPWeb web, SPFeatureReceiverProperties properties)
{
List<Guid> ids = new List<Guid>();
SPElementDefinitionCollection elements =
properties.Feature.Definition.GetElementDefinitions(new CultureInfo((int)web.Language, false));
foreach (SPElementDefinition element in elements)
{
if ("ListInstance" == element.ElementType)
{
XmlNode node = element.XmlDefinition;
SPList list = web.Lists[node.Attributes["Title"].Value];
SPField field = list.Fields[SPBuiltInFieldId.Modified];
if (!field.Indexed)
{
field.Indexed = true;
field.Update();
}
ids.Add(list.ID);
}
}
string targetConfig = string.Empty;
foreach (Guid id in ids)
{
targetConfig += string.Format("'{0}',''\n", id);
}
SPFile file = web.GetFile("Pages/default.aspx");
file.CheckOut();
using (SPLimitedWebPartManager manager = file.GetLimitedWebPartManager(PersonalizationScope.Shared))
{
WhatsNewWebPart webpart = null;
foreach (System.Web.UI.WebControls.WebParts.WebPart eachWebPart in manager.WebParts)
{
webpart = eachWebPart as WhatsNewWebPart;
if (null != webpart)
{
break;
}
}
if (null != webpart)
{
webpart.TargetConfig = targetConfig;
manager.SaveChanges(webpart);
}
}
file.CheckIn("ConfigureWebParts");
file.Publish("ConfigureWebParts");
file.Approve("ConfigureWebParts");
}
If you are unsure about the property, export the web part from the browser, then open the .webpart/.dwp file with a text editor. Somewhere in the xml will be a reference to the source list.
*.webparts are usually easier to modify, just set the property.
*.dwps are harder because you sometimes have to get the property (eg ViewXML), then load it into an XmlDocument, then replace the property, and write the xml document string value back to ViewXML.
I am quite stumped with this. In enhancing an existing feature to a SharePoint solution, I found that they were querying the Wss_Content directly. Knowing I should not be using that stored procedure, I used the SharePoint object model to retrieve the users group information from the userName. What's burning me is that it is slower then the stored procedure. Is there a smarter/faster way to get this information? We are using SharePoint 2007. Below is roughly what the function does:
private string GetTitle(string userName)
{
string results = string.Empty;
try
{
SPSite spSite = new SPSite("http://devvm");
SPWeb spWeb = spSite.AllWebs[""];
SPUser user = spWeb.AllUsers["aspnetsqlmembershipprovider:" + userName];
SPGroupCollection groups = user.Groups;
results = groups[0].Name;
}
catch (Exception ex)
{
Console.WriteLine("Unable to find user" + userName);
results = "No Group Found";
}
return results;
}
and the Stored Procedure code is:
SELECT #role=Groups.Title
FROM WSS_Content.DBO.UserInfo Info
LEFT JOIN WSS_Content.DBO.GroupMembership Membership
ON Info.tp_ID=Membership.MemberID
LEFT JOIN Wss_Content.DBO.Groups Groups
ON Membership.GroupID=Groups.ID
WHERE tp_Login='aspnetsqlmembershipprovider:' + #username
FYI the reason I have this in a try-catch is that this is a sub-query to another list that might not have a user associated to the item.
Any help in this would be greatly appreciated.
How are you calling that code? If you are using this inside a console application then yes it will be slower, it needs to fire up the supporting objects that are normally live when using this inside a native sharepoint context.
Also you are referencing an SPSite and SPWeb object without disposing of the object, this has inherent performance issues as well.
Im using subsonic 2.2
I tried asking this question another way but didnt get the answer i was looking for.
Basically i ususally include validation at page level or in my code behind for my user controls or aspx pages. However i haev seen some small bits of info advising this can be done within partial classes generated from subsonic.
So my question is, where do i put these, are there particular events i add my validation / business logic into such as inserting, or updating. - If so, and validation isnt met, how do i stop the insert or update. And if anyone has a code example of how this looks it would be great to start me off.
Any info greatly appreciated.
First you should create a partial class for you DAL object you want to use.
In my project I have a folder Generated where the generated classes live in and I have another folder Extended.
Let's say you have a Subsonic generated class Product. Create a new file Product.cs in your Extended (or whatever) folder an create a partial class Product and ensure that the namespace matches the subsonic generated classes namespace.
namespace Your.Namespace.DAL
{
public partial class Product
{
}
}
Now you have the ability to extend the product class. The interesting part ist that subsonic offers some methods to override.
namespace Your.Namespace.DAL
{
public partial class Product
{
public override bool Validate()
{
ValidateColumnSettings();
if (string.IsNullOrEmpty(this.ProductName))
this.Errors.Add("ProductName cannot be empty");
return Errors.Count == 0;
}
// another way
protected override void BeforeValidate()
{
if (string.IsNullOrEmpty(this.ProductName))
throw new Exception("ProductName cannot be empty");
}
protected override void BeforeInsert()
{
this.ProductUUID = Guid.NewGuid().ToString();
}
protected override void BeforeUpdate()
{
this.Total = this.Net + this.Tax;
}
protected override void AfterCommit()
{
DB.Update<ProductSales>()
.Set(ProductSales.ProductName).EqualTo(this.ProductName)
.Where(ProductSales.ProductId).IsEqualTo(this.ProductId)
.Execute();
}
}
}
In response to Dan's question:
First, have a look here: http://github.com/subsonic/SubSonic-2.0/blob/master/SubSonic/ActiveRecord/ActiveRecord.cs
In this file lives the whole logic I showed in my other post.
Validate: Is called during Save(), if Validate() returns false an exception is thrown.
Get's only called if the Property ValidateWhenSaving (which is a constant so you have to recompile SubSonic to change it) is true (default)
BeforeValidate: Is called during Save() when ValidateWhenSaving is true. Does nothing by default
BeforeInsert: Is called during Save() if the record is new. Does nothing by default.
BeforeUpdate: Is called during Save() if the record is new. Does nothing by default.
AfterCommit: Is called after sucessfully inserting/updating a record. Does nothing by default.
In my Validate() example, I first let the default ValidatColumnSettings() method run, which will add errors like "Maximum String lenght exceeded for column ProductName" if product name is longer than the value defined in the database. Then I add another errorstring if ProductName is empty and return false if the overall error count is bigger than zero.
This will throw an exception during Save() so you can't store the record in the DB.
I would suggest you call Validate() yourself and if it returns false you display the elements of this.Errors at the bottom of the page (the easy way) or (more elegant) you create a Dictionary<string, string> where the key is the columnname and the value is the reason.
private Dictionary<string, string> CustomErrors = new Dictionary<string, string>
protected override bool Validate()
{
this.CustomErrors.Clear();
ValidateColumnSettings();
if (string.IsNullOrEmpty(this.ProductName))
this.CustomErrors.Add(this.Columns.ProductName, "cannot be empty");
if (this.UnitPrice < 0)
this.CustomErrors.Add(this.Columns.UnitPrice, "has to be 0 or bigger");
return this.CustomErrors.Count == 0 && Errors.Count == 0;
}
Then if Validate() returns false you can add the reason directly besides/below the right field in your webpage.
If Validate() returns true you can safely call Save() but keep in mind that Save() could throw other errors during persistance like "Dublicate Key ...";
Thanks for the response, but can you confirm this for me as im alittle confused, if your validating the column (ProductName) value within validate() or the beforevalidate() is string empty or NULL, doesnt this mean that the insert / update has already been actioned, as otherwise it wouldnt know that youve tried to insert or update a null value from the UI / aspx fields within the page to the column??
Also, within asp.net insert or updating events we use e.cancel = true to stop the insert update, if beforevalidate failes does it automatically stop the action to insert or update?
If this is the case, isnt it eaiser to add page level validation to stop the insert or update being fired in the first place.
I guess im alittle confused at the lifecyle for these methods and when they come into play
I'm logged in as the System Account, so it's probably not a "real access denied"!
What I've done :
- A custom master page
- A custom page layout from a custom content type (with custom fields)
If I add a custom field (aka "content field" in the tools in SPD) in my page layout, I get an access denied when I try to edit a page that comes from that page layout.
So, for example, if I add in my page layout this line in a "asp:content" tag :
I get an access denied. If I remove it, everyting is fine. (the field "test" is a field that comes from the content type).
Any idea?
UPDATE
Well, I tried in a blank site and it worked fine, so there must be something wrong with my web application :(
UPDATE #2
Looks like this line in the master page gives me the access denied :
<SharePoint:DelegateControl runat="server" ControlId="PublishingConsole" Visible="false"
PrefixHtml="<tr><td colspan="0" id="mpdmconsole" class="s2i-consolemptablerow">"
SuffixHtml="</td></tr>"></SharePoint:DelegateControl>
UPDATE #3
I Found http://odole.wordpress.com/2009/01/30/access-denied-error-message-while-editing-properties-of-any-document-in-a-moss-document-library/
Looks like a similar issue. But our Sharepoint versions are with the latest updates. I'll try to use the code that's supposed to fix the lists and post another update.
** UPDATE #4**
OK... I tried the code that I found on the page above (see link) and it seems to fix the thing. I haven't tested the solution at 100% but so far, so good. Here's the code I made for a feature receiver (I used the code posted from the link above) :
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;
using System.Xml;
namespace MyWebsite.FixAccessDenied
{
class FixAccessDenied : SPFeatureReceiver
{
public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
FixWebField(SPContext.Current.Web);
}
public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
//throw new Exception("The method or operation is not implemented.");
}
public override void FeatureInstalled(SPFeatureReceiverProperties properties)
{
//throw new Exception("The method or operation is not implemented.");
}
public override void FeatureUninstalling(SPFeatureReceiverProperties properties)
{
//throw new Exception("The method or operation is not implemented.");
}
static void FixWebField(SPWeb currentWeb)
{
string RenderXMLPattenAttribute = "RenderXMLUsingPattern";
SPSite site = new SPSite(currentWeb.Url);
SPWeb web = site.OpenWeb();
web.AllowUnsafeUpdates = true;
web.Update();
SPField f = web.Fields.GetFieldByInternalName("PermMask");
string s = f.SchemaXml;
Console.WriteLine("schemaXml before: " + s);
XmlDocument xd = new XmlDocument();
xd.LoadXml(s);
XmlElement xe = xd.DocumentElement;
if (xe.Attributes[RenderXMLPattenAttribute] == null)
{
XmlAttribute attr = xd.CreateAttribute(RenderXMLPattenAttribute);
attr.Value = "TRUE";
xe.Attributes.Append(attr);
}
string strXml = xe.OuterXml;
Console.WriteLine("schemaXml after: " + strXml);
f.SchemaXml = strXml;
foreach (SPWeb sites in site.AllWebs)
{
FixField(sites.Url);
}
}
static void FixField(string weburl)
{
string RenderXMLPattenAttribute = "RenderXMLUsingPattern";
SPSite site = new SPSite(weburl);
SPWeb web = site.OpenWeb();
web.AllowUnsafeUpdates = true;
web.Update();
System.Collections.Generic.IList<Guid> guidArrayList = new System.Collections.Generic.List<Guid>();
foreach (SPList list in web.Lists)
{
guidArrayList.Add(list.ID);
}
foreach (Guid guid in guidArrayList)
{
SPList list = web.Lists[guid];
SPField f = list.Fields.GetFieldByInternalName("PermMask");
string s = f.SchemaXml;
Console.WriteLine("schemaXml before: " + s);
XmlDocument xd = new XmlDocument();
xd.LoadXml(s);
XmlElement xe = xd.DocumentElement;
if (xe.Attributes[RenderXMLPattenAttribute] == null)
{
XmlAttribute attr = xd.CreateAttribute(RenderXMLPattenAttribute);
attr.Value = "TRUE";
xe.Attributes.Append(attr);
}
string strXml = xe.OuterXml;
Console.WriteLine("schemaXml after: " + strXml);
f.SchemaXml = strXml;
}
}
}
}
Just put that code as a Feature Receiver, and activate it at the root site, it should loop trough all the subsites and fix the lists.
SUMMARY
You get an ACCESS DENIED when editing a PAGE or an ITEM
You still get the error even if you're logged in as the Super Admin of the f****in world (sorry, I spent 3 days on that bug)
For me, it happened after an import from another site definition (a cmp file)
Actually, it's supposed to be a known bug and it's supposed to be fixed since February 2009, but it looks like it's not.
The code I posted above should fix the thing.
Try to publish your MasterPage and Page Layouts, this is the most common reason. Since the system account is godmode, it wont get that error.
In SharePoint Designer you cannot do the last step in the publishing workflow (Approval), so you:
SharePoint Designer:
CheckIn => Publish Major Version, hit the OK button or go to /_catalogs/masterpage on the site .
Then and use the Context Menu to Approve the MasterPage and Layouts.
Some ideas:
Check if any web parts in your custom Page Layout and Master Page are not registered as safe.
Did you define your own custom field type, like write a class which extends SPField? If so, are you using a custom Field Control? If you are, check if it is doing anything which may need elevated privileges.
Likewise, check for any edit mode panels containing web parts of web controls which might be trying to do something which needs elevated privileges.
See the code I've posted in the edit of the post. It fixed my problem.
The problem appears to be caused by an error in the stsadm -o export function in certain versions of SharePoint (I got it doing an export from a 2007 RTM MOSS server). Importing the bogus export file causes the "edit-denied-access" problem in all NEWLY-CREATED lists. The patches for later version from Microsoft fix stsadm -o export, but DO NOT FIX the broken lists; that requires a procedure like tinky05's.