I need to programmatically retrieve the columns in a Sharepoint document library, in order to set file properties externally to Sharepoint.
I've found that setting the metadata property is not hard as long as you already know the name of the column, which I cannot expect users to input themselves.
As it does not seem possible to do this through the Sharepoint Web Services I have created my own custom web service so I have access to the Client Object Model.
Using this code I am able to retrieve the custom columns I have created, however I am not able to distinguish between the ones editable in the item properties section (picture above) and those which aren't.
SPList list = web.Lists[specificList];
foreach (SPField field in list.Fields)
{
if (!field.Hidden)
{
var title = field.Title;
var description = field.Description;
var parentList = field.ParentList;
var references = field.FieldReferences; // contains names of fields referenced in computed fields
if (references != null)
{
foreach (string reference in references)
{
var test = parentList.Fields.GetField(reference);
}
}
}
}
I get extra properties such as:
Copy Source
Content Type
Checked Out To
Checked In Comment
Type
File
Size
Edit
Version
Source Version
Source Name
I have also tried retrieving the column fields from the SPFolder item, but again this returns many extra properties and is even less filterable.
foreach (SPListItem folderItem in list.Folders)
{
SPFolder folder = folderItem.Folder;
System.Collections.Hashtable oHashtable = folder.Properties;
System.Collections.ICollection collKeys = oHashtable.Keys;
foreach (var key in collKeys)
{
string keyName = key.ToString();
}
}
Is there a standard way to retrieve the column fields I need? Or will I have to manually exclude the defaults ones such as "Checked out to"?
First you have to know which form you are viewing. Is it the EditForm or NewForm?
You can filter the columns visible on a specific form by getting the fields of the ContentType and then check if they are getting displayed on the NewForm (or whatever form):
SPList list = web.Lists[specificList];
var contentType = list.ContentTypes[0]; // Select first contenttype. Change this if you need a different contentType
foreach (SPField field in contentType.Fields)
{
if (!field.Hidden
&& (field.ShowInEditForm == null
|| !field.ShowInEditForm.Value)) // Replace ShowInEditForm with the form you need
{
var title = field.Title;
var description = field.Description;
var parentList = field.ParentList;
var references = field.FieldReferences; // contains names of fields referenced in computed fields
if (references != null)
{
foreach (string reference in references)
{
var test = parentList.Fields.GetField(reference);
}
}
}
}
I think the best way to go is to get the fields from the content type and not the list itself. That way you'll get only the fields visible in the form.
var list = web.Lists[specificList];
var contentType = list.ContentTypes["Document"];
foreach (SPField field in contentType.Fields)
{
if(!field.Reorderable || contentType.FieldLinks[field.Id].Hidden)
{
continue;
}
//Process fields
}
You may ask "Why Reordable=false?". Well, generally custom fields do not set this property so it is a nice way to filter them.
Also I didn't invent this code. This code is taken from code behind class of SharePoint standard content type fields reorder page (using reflection).
Related
When I try to create a new list item with the basic calendar/list fields everything works perfectly. However, when I try to do so with a "non-standard" field i.e. a field I added, I am getting a "field not recognized" error.
The field is clearly there! Is there some special way I need to populate these custom fields?
// get a specific list
ISiteListsCollectionPage list = await graphClient.Sites["root"].Lists.Request()
.Filter($"DisplayName eq 'Outlook Integration'").GetAsync();
// create a dictionary of [calendar] list properties
Dictionary<string, object> props = new Dictionary<string, object>();
// populate properties, all of these work just fine
props.Add("Title", evt.Subject);
props.Add("Location", evt.Location?.DisplayName);
props.Add("EventDate", utcStart.ToString("yyyy-MM-ddTHH:mm:ssK"));
props.Add("EndDate", utcEnd.ToString("yyyy-MM-ddTHH:mm:ssK"));
props.Add("Description", Regex.Replace(evt.Body.Content, "<.*?>", String.Empty)); // remove HTML content
// populate custom properties
props.Add("ResourceID", evt.Id); // throws error: Field 'ResourceID' is not recognized
// create list item with our properties dictionary
var newItem = new ListItem
{
Name = "My New Event",
Fields = new FieldValueSet()
{
AdditionalData = props
}
};
// call the service and get the result
var newListItem = await graphClient.Sites["root"].Lists[list[0].Id].Items.Request().AddAsync(newItem);
This is the complete list of fields on my list:
Here you can see the display name is "ResourceID" whereas the API name is "O365EventId." However, both result in the same error, "Field not recognized."
Note: ResourceID is one of the fields that I added. How can I set the value of this field via the Graph API?
Marc is right by saying in comment regarding column name, the provided screenshot displays Column.displayName which is
The user-facing name of the column.
but what actually FieldValueSet.AdditionalData expects as a key is Column.name which is:
The API-facing name of the column as it appears in the fields on a
listItem. For the user-facing name, see displayName.
In your case most likely displayName and name properties are different, you could verify it via following endpoint:
https://graph.microsoft.com/v1.0/sites/root/lists/Outlook Integration/columns
and that's the reason why this error occurs.
Via the Graph API client (C#), you can see a list of all columns for any given list like so:
// get specific list by name
ISiteListsCollectionPage list = await graphClient.Sites["root"].Lists.Request()
.Filter($"DisplayName eq 'YOUR_LIST_NAME_HERE'").GetAsync();
// get columns and output them to the log as a serialized object
var listColumns = await graphClient.Sites["root"].Lists[list[0].Id].Columns.Request().GetAsync();
logger.LogInformation($"List Columns Object: {JsonConvert.SerializeObject(listColumns).ToString()}");
Here is my coding. I have researched this through out the website. Many people seem to have the same issue, but not related to mine. I am writing a program to access pictures from my laptop, but this error is not letting me access the Picture Directory. Any help is highly appreciated!!!
currentDir = fb.SelectedPath; // Get the selected folder by the user;
textBoxD.Text = currentDir;
var dirInfo = new DirectoryInfo(currentDir);
var files = dirInfo.GetFiles().Where(c=>c.Extension.Equals(".jpg") || c.Extension.Equals(".jpeg") || c.Extension.Equals(".bmp") || c.Extension.Equals(".png"));
foreach (var image in files)
{
object listBoxImages = null;
//Add Images/Files to the list box
listBoxImages.Items.Add(image.Name);
}
You have two problems. The first, which is the immediate cause of the compilation error is that listBoxImages is defined as an object. So all it has is methods and properties of object - which does not have Items property.
Secondly, You are assigning null to listBoxImages so even if it had the Items property you'd get a NullReferenceException
I'm assuming you want to initialize it as a ListBox instead. When you do so also make sure to define and initialize it outside of the foreach loop because otherwise it will only contain the last item
In addition you can refactor your linq a bit to use a HashSet of the valid extensions:
var validExtensions = new HashSet<string>(new[] { ".jpg", "jpeg", ".bmp", ".png" });
var files = dirInfo.GetFiles().Where(c => validExtensions.Contains(c.Extension));
HashSet Contains is performed in O(1) so changing from the multiple || statements to using this collection doesn't harm performance and is much more extensible and readable
listBoxImages is of type object . Typecast it to the type of collection and use the items property
Problem is you are trying to add items to listboxImage that has not been initialized.
currentDir = fb.SelectedPath; // Get the selected folder by the user;
textBoxD.Text = currentDir;
var dirInfo = new DirectoryInfo(currentDir);
var files = dirInfo.GetFiles().Where(c=>c.Extension.Equals(".jpg") || c.Extension.Equals(".jpeg") || c.Extension.Equals(".bmp") || c.Extension.Equals(".png"));
var listBoxImages = new ListBox(); //I think listBoxImages is of type ListBox control
foreach (var image in files)
{
//Add Images/Files to the list box
listBoxImages.Items.Add(image.Name);
}
How do you save "Value" and "DataServiceCollection" objects that are part of another SharePoint list item? These are the only properties in my model that are not getting saved.
The generated Food SharePoint model has these sort of properties:
public class Food
{
DataServiceCollection<FoodIngredientValue> Ingredient;
FoodStateValue State;
string _StateValue
}
First, I don't know why there are two ways to add a state value in the model generated by SharePoint. I try populating either one and the state value doesn't populate in SharePoint.
Secondly, I tried populating the Ingredient collection through hard coding FoodIngredientValue objects to the food model before saving and also by querying SharePoint and assigning them to the Ingredient property but it doesn't get saved in SharePoint.
I add a new food item to the SharePoint list using the code below and I verified all three properties are populated in my model but none of them get saved.
public bool Insert(Food food)
{
var dataContext = new FoodDataContext(new Uri(EndpointUrl)) { Credentials = CredentialCache.DefaultNetworkCredentials };
dataContext.AddToFoods(food);
var response = dataContext.SaveChanges().FirstOrDefault();
return response.StatusCode == 201;
}
This was a great blog post explaining how to link complex list items (DataServiceCollecton and Value objects) in the SharePoint oData API:
http://blog.heeresonline.com/2014/07/sharepoint-wcf-dataservices-choice-lookup-column-editing/
The important thing to remember is to add the new item to the data context before you begin populating complex fields of type DataServiceCollection or Value objects. In the case of properties of type DataServiceCollection, there is a little more work that needs to be done to link them properly so they are saved in the data context as shown below for Ingredient. Here is an example of the code that finally worked:
var foodItem = new FoodItem();
dataContext.AddToFoods(foodItem); // Add to context before populating fields so the values are tracked.
foodItem = Mapper.Map(newFood, foodItem);
// DataValue Properties like StateValue objects can now be added since it is tracked by the context.
var state = StateValue.CreateStateValue("Montana");
foodItem.StateValue = state;
// Need to link special DataServiceCollection lists like Ingredient using a reference.
if (newFood.Ingredient != null)
{
newFood.Ingredient.ForEach(c =>
{
var ingredient = FoodIngredient.CreateFoodIngredientValue(c);
dataContext.AttachTo("FoodIngredientValue", ingredient);
foodItem.FoodIngredient.Add(ingredient);
dataContext.AddLink(foodItem, "FoodIngredient", ingredient);
});
}
I have been able to programmatically add external (i.e. BDC) lookup fields to a list and I have also been able to add dependent external lookup fields to the same list. What I have not been able to figure out is how to programmatically add the dependent external lookup fields to the list's default view.
This article on MSDN gives an example of how to add a regular dependent lookup field to an SPView -but I have yet to find an example that shows how to programmatically add a dependent external lookup field to an SPView.
Below is the code in the FeatureActivated method of my EventReceiver that I am using to add the dependent external lookup fields to my SharePoint list and my attempt at adding the field to the list's default view.
var web = ((SPSite)properties.Feature.Parent).RootWeb;
var list = web.Lists.TryGetList("MyList");
var fldName = "EmployeeID";
var fld = list.Fields.CreateNewField("BusinessData", fldName) as SPBusinessDataField;
fld.SystemInstanceName = lobSystemInstanceName;
fld.EntityNamespace = entityNamespace;
fld.EntityName = entityName;
fld.BdcFieldName = entityFieldName;
//The dictionary object defined below contains key/value pairs that represent the
//field name as a string along with a boolean flag that specifies whether or not
//the secondary field should be added to the default view.
var secondaryFieldNames = new Dictionary<string, bool>()
{
{"FirstName", true},
{"LastName", true},
{"Title", false}
}
fld.SetSecondaryFieldsNames(secondaryFieldNames.Select(e => e.Key).ToArray());
var view = list.Views.DefaultView;
foreach (var secFld in secondaryFieldNames)
{
var viewFieldName = String.Format("{0}: {1}", fldName, secFld.Key);
if (!view.ViewFields.Exists(viewFieldName) && secFld.Value)
{
view.ViewFields.Add(viewFieldName);
view.Update();
}
}
As mentioned previously, the primary lookup field and all secondary lookup fields are successfully added to the list. The primary lookup field is successfully added to the list's default view. The seconday fields are not.
Storing the viewfield collection in a variable did the trick for me (don't really know why).
foreach (var secFld in secondaryFieldNames)
{
var viewFieldName = String.Format("{0}: {1}", fldName, secFld.Key);
var viewFields = view.ViewFields;
if (!viewFields.Exists(viewFieldName) && secFld.Value)
{
viewFields.Add(viewFieldName);
view.Update();
}
}
I'm using Linq2SQL and I'm pretty new to it.
I've got a User table and a UserData table, the idea being that properties for the User object can be added / removed by adding or removing rows in the UserData table. I did not come up with this particular design but I am more or less stuck with it (as long as I can come up with a solution)
alt text http://www.86th.org/linq2sqlproblem.jpg
I'd like to populate/bind "FirstName" on the User object by something along the lines of setting the value to:
UserData.Value WHERE UserData.ItemID == User.UserID AND KeyName = 'FirstName'
Similarly, LastName would be:
UserData.Value WHERE UserData.ItemID == User.UserID AND KeyName = 'LastName'
Description of the UserData Table:
UserData.ItemID is the FK (User.UserID)
UserData.KeyName is specifying the name of the property
UserData.Value is the actual value.
How would I setup my User object to handle this so I could do the normal CRUD functionality on this object and have the changes carry through to both tables?
Is this even possible?
Is this bad form?
Personally I feel its bad form but I suppose everyone has there way of doing things. Why can't you assign userdata in the users table? I think I might not be understanding the design idea here.
Quick Note
I renamed UserData to ExtendedProperty and this caused the relationship from User to ExtendedProperty to be called ExtendedProperties.
Summary of changes
Created a getter/setter for both FirstName and LastName in the partial User class
Grabbed the correct ExtendedProperty element out of the ExtendedProperties collection and either returned or updated the Value property of it.
Refactored into a reusable format as shown below
partial class User
{
public string FirstName
{
get { return (string)this.getExtendedProperty("FirstName").Value; }
set { this.getExtendedProperty("FirstName").Value = value; }
}
public string LastName
{
get { return (string)this.getExtendedProperty("LastName").Value; }
set { this.getExtendedProperty("LastName").Value = value; }
}
// Grab a related property out of the collection, any changes to it will be reflected in the database after a submit
private ExtendedProperty getExtendedProperty(string KeyName)
{
// grab the properties that fit the criterea
var properties = (from prop in this.ExtendedProperties where prop.KeyName == KeyName select prop);
// return value
ExtendedProperty property = properties.SingleOrDefault();
// if this is a new user then there arent going to be any properties that match
if (property == null)
{
// Define a new item to add to the collection
property = new ExtendedProperty()
{
ItemID = this.UserID,
KeyName = KeyName,
Value = String.Empty
};
// Add the item we're about to return to the collection
this.ExtendedProperties.Add(property);
}
// either way we have a valid property to return at this point
return property;
}
}
I just hope this isn't bloated / grossly inefficient.
Edit
In getExtendedProperty, it would error when setting the FirstName or LastName of a newly created User because it would not have any corresponding ExtendedProperty elements in the ExtendedProperties collection as shown below.
User expected = new User();
expected.UserID = Guid.NewGuid();
expected.UserName = "LJ";
expected.FirstName = "Leeroy"; // It would error here
expected.LastName = "Jenkins";
Because of this I added a check to ensure that new items get added to the ExtendedProperties collection if they are requested and not currently in there.
I also removed setExtendedProperty since I felt it wasn't necessary and was just a method around a 1 liner anyway.
I would really appreciate any feedback before I accept this answer, I'll let it sit for a few days.