I can't get my head around this.
If I have a ContentType called Contacts. The ContentType has two fields attached to it.
FirstName (textfield) and LastName (textfield).
If I want to create a new contentitem of this type then I can write code like this.
dynamic contact = _services.ContentManager.New("Contacts");
contact.Contacts.FirstName.Value = "John";
contact.Contacts.LastName.Value = "Doe";
_services.ContentManager.Create(contact, VersionOptions.Published);
This does not work. The Contentitem gets created but the fields are empty.
However, if I write it like this it works. Why is that? Must I set the fields values after ContentManager.Create is called?
dynamic contact = _services.ContentManager.New("Contacts");
_services.ContentManager.Create(contact, VersionOptions.Published);
contact.Contacts.FirstName.Value = "John";
contact.Contacts.LastName.Value = "Doe";
What you observed is indeed the intended behaviour and is by design. I've come across this before too and also created an issue about it. As you can see there it was closed by Sebastien, the lead developer stating that this is by design but unfortunately not explaining why.
FYI the standard workflow for managing content items is the following:
Instantiate item.
Create it.
Update its values.
If the update happened through the model binder then check if the ModelState is valid. If not, cancel the transaction and return.
If everything's OK and the content type is set to be draftable, publish the item.
You can see an explained example of this in the Orchard Training Demo module (ContentsAdminController.PersonListDashboardPost()).
Related
I'm using Orchard 1.7.2.
I have created a new content type called PropertyImage of stereotype Media. I also created a part called PropertyPart and attached that part to my PropertyImage content type. This allows a user to pick a product when uploading a PropertyImage (ie to say 'This image is of this property').
So far so good.
Now what I'd like to do is query for all PropertyImages that have a PropertyPart attached to them where the associated property is x, y, or z.
This is what I have so far:
var images = _orchardServices.ContentManager
.Query<PropertyPart, PropertyRecord>()
.Where(p => p.PropertyId == id)
.ForType(new[] { "PropertyImage" });
This however will only return a collection of PropertyParts, which is not what I want, because I want the whole PropertyImage Content Item. How can I do this?
I should point out that properties come from an external source, and are therefore not content items.
Edit
As soon as I asked this question, I realised I could just append my query with this:
.List().Select(p=>p.ContentItem)
Sometimes it just helps to talk your problem through!
As soon as I asked this question, I realised I could just append my query with this:
.List().Select(p=>p.ContentItem)
Sometimes it just helps to talk your problem through!
I have a content item called Event, which has a taxonomy field called Section attached via the content definition area.
What is the easiest way to retrieve the Section field from the content within an alternate? My alternate is not overriding an Event, so Model.ContentItem is not possible. Within my alternate my Event object instance is of type ContentItem which I am retrieving through the ContentManager.
This is what i'm doing at the moment:
ContentItem content = WorkContext.Resolve<IContentManager>().Get(id);
var = content.Parts.ElementAt(13).Fields.ElementAt(0);
I realise that in the above code the index's could change, the only other way I can think of doing this is by inserting Lambda expressions in the place of the integers.
content.Parts.ElementAt(13) returns object of type ContentPart
content.Parts.ElementAt(13).Fields.ElementAt(0) returns object of type TaxonomyField. Whereas I believe I need the TermPart?
If it cannot be achieved in a simple way, why is it so difficult to perform such a simple task?
Thank you in advance.
First you do not need the ContentManager on the template.
On the Model you have the ContentItem. You can retrive the field like this:
var contentItem = Model.ContentItem;
var terms = contentItem.Event.TaxonomyFieldName.Terms;
On terms you have the terms of the ContentItem.
When should one use Fields from the CMS and when to use class properties and database fields?
My scenario:
Created a product content part with fields usage (text) and price (double). Then I have created a contenttype product and added the part.
I could also have created a product record in models and added a product table. And a part with driver etc.
At first glance I dont see difference besides option 2 requiring programming.
However I did encounter a problem:
In option 2 I could use repository and create a lot of items programmatically.
In option 1 I could create product content items but was not able to fill in the fields of the product part (no errors, but fields remained empty)
So when to use option 1 and when option 2?
And is my problem with option 1 related to that option?
EDIT: Clarifiation of problem with option 1
I have created a productpart. To the productpart I have added the field price which is a decimal and I have added a field Usage of product which is a text field.
In the code I have the following:
dynamic item = _cm.New("Product");
item.TitlePart.Title = "Mijn dummy product";
item.BodyPart.Text = "Some dummy text for this product";
item.ProductPart.Price.Value = new decimal(20.5);
item.ProductPart.Usage.Value = "Some dummy usage of this product";
_cm.Create(item);
After running the code, the product is created with the correct title and body text, but Usage and Price come up emtpy.
I also tried it with the item.As<> method. But that does not compile to As since I have not created an object with that name.
A difference is that fields can only be queried against with projections but more generally parts and fields are for different usage: parts are meant to be reusable, not fields. Price for example is going to be a characteristic of any product so it should probably be a part property. Usage, I don't know, it sounds fairly specific to what you're doing and may be better done as a field.
As for your problems with option 1, it's hard to say without seeing your code but chances are you're doing it wrong.
Apparently the item has to be created first before accessing the contentpart and fields. At least it worked for me.
Please verify if my analysis is correct. If so I will mark it an answer, as well as the answer of Bertrand.
The code now looks like this:
var item = _cm.New("Product");
item.As<TitlePart>().Title = "Mijn dummy product";
item.As<BodyPart>().Text = "Some dummy text for this product";
//Create first before accessing the contentparts that are dynamic.
_cm.Create(item);
//And then access the product via dynamic object.
dynamic dyn = item;
item.ProductPart.Price.Value = new decimal(20.5);
item.ProductPart.Usage.Value = "Some dummy usage of this product";
Could anybody help me to get List Item by unique Id in SharePoint without using List? I know the List Item unique Id, but I haven't information about List. Using Client Object Model I can do the following:
using (SP.ClientContext clientContext = new SP.ClientContext(...))
{
**SP.List list = clientContext.Web.Lists.GetById(listId);**
SP.ListItem listItem = list.GetItemById(listItemId);
clientContext.Load(listItem);
clientContext.ExecuteQuery();
...
}
Unfortunately, in this code I use listId, but the problem is that in my task I don't know listId, I have only ListItem Guid. Since I have unique Id, I believe that there must be a way to get this Item without using any additional information.
You've posted a snippet with a Client Object Model, so I suppose it's a preffered object model for the solution. However, if it's acceptable for You, I've found some information on how to achieve this using server side object model, which You may find helpful.
It seems that this can be done using SPWeb.GetFile(Guid itemGuid) if the item exist in the root site.
If you want it to work also for subsites, you can use SPWeb.GetSiteData method to invoke caml retrieving an item as explained in this article: http://www.chakkaradeep.com/post/Retrieving-an-Item-from-the-RootWeb-and-Subwebs-using-its-UniqueId.aspx.
You can also take a look at this thread on SO for some additional information:
SharePoint: Get SPListItem by Unique ID.
If you have any issues using those methods, let me know. I'll also look for alternatives for this to run with Client Object Model and I'll post them if I find some, however, it seems to be more difficult to achieve.
I have a site content type that was used for a handful of lists throughout my site collection. In that content type, I describe an event receiver to handle the ItemAdding event. This works fine. Now I need to update the content type so that ItemUpdating is also handled. Off the top of my head, I tried simply modifying the xml for my content type, since this seemed to allow for easy version tracking. This worked in the sense that my updates were applied to the site content type, but not to my lists that had been using this content type. This much was expected. Then I noticed that the SharePoint SDK takes a grim view of that:
Under no circumstances should you
update the content type definition
file for a content type after you have
installed and activated that content
type. Windows SharePoint Services does
not track changes made to the content
type definition file. Therefore, you
have no method for pushing down
changes made to site content types to
the child content types.
The SDK then points to a couple sections which describe how to use the UI or code to push changes. Since the UI offers no hook into event receivers, I guess I will be choosing the code path.
I thought I'd be able to do something like this and just add a new event receiver to the list's copy of the content type:
SPList list = web.Lists["My list"];
SPContentType ctype = list.ContentTypes["My content type"];
// Doesn't work -- EventReceivers is null below.
ctype.EventReceivers.Add(SPEventReceiverType.ItemUpdating,
"My assembly name", "My class name");
But the catch is that ctype.EventReceivers is null here, even though I have ItemAdding already hooked up to this list. It appears that it was moved to the list itself. So, the list has a valid EventReceivers collection.
SPList list = web.Lists["My list"];
list.EventReceivers.Add(SPEventReceiverType.ItemUpdating,
"My assembly name", "My class name");
So, I have a couple questions:
Is the correct way to do this just to add any new event receivers directly to the list and just forget about my content type altogether?
To accomplish this change, what's the best way to handle this in terms of configuration management? Should I create a simple console app to find all the appropriate lists and modify each of them? Or is somehow creating a Feature a better option? Either way, it seems like this change is going to be off on its own and difficult to discover by future devs who might need to work with this content type.
Did you call ctype.Update(true) after adding the EventReceiver? If you don't it won't be persisted .
And don't use the List content type, use SPWeb.ContentTypes instead.
This code works for me:
var docCt = web.ContentTypes[new SPContentTypeId("0x0101003A3AF5E5C6B4479191B58E78A333B28D")];
//while(docCt.EventReceivers.Count > 0)
// docCt.EventReceivers[docCt.EventReceivers.Count - 1].Delete();
docCt.EventReceivers.Add(SPEventReceiverType.ItemUpdated, "ASSEMBLYNAME, Version=1.0.0.0, Culture=neutral, PublicKeyToken=c5b857a999fb347e", "CLASSNAME");
docCt.Update(true);
The true parameter means it gets pushed down to all child ContentTypes as well. (i.e. to all lists using the content type).
To answer the second part of your question, this a tricky thing because of the fact that changes to contenttypes on the sitecollection won´t be pushed down to the lists where it´s used. A "copy" is essentially made of the fields in the sitecollection and there is no more link between them after you add a contenttype to the list. I think this is due to the fact that you are supposed to make changes to lists without it affecting the sitecollection. Anyhow my contribution to this "problem", and how I have solved it, involves making the xml the "master" and in a featurereceiver I pull up the xml and find all places where the contenttype is used and from there update the contenttypes (really the fieldrefs) on list level to match the one in the xml. The code goes something like:
var elementdefinitions = properties.Feature.Definition.GetElementDefinitions();
foreach (SPElementDefinition elementDefinition in elementdefinitions)
{
if (elementDefinition.ElementType == "ContentType")
{
XmlNode ElementXML = elementDefinition.XmlDefinition;
// get all fieldrefs nodes in xml
XmlNodeList FieldRefs = ElementXML.ChildNodes[0].ChildNodes;
// get reference to contenttype
string ContentTypeID = ElementXML.Attributes["ID"].Value.ToString();
SPContentType ContentType =
site.ContentTypes[new SPContentTypeId(ContentTypeID)];
// Get all all places where the content type beeing used
IList<SPContentTypeUsage> ContentTypeUsages =
SPContentTypeUsage.GetUsages(ContentType);
}
}
The next thing is to compare the fieldrefs in xml xml with the fields on the list (done by the ID attribute) and making sure that they are equal. Unfortunately we can´t update all things on the SPFieldLink class (the fieldref) and (yes I know it´s not supported) here I have actually used reflection to update those values (f.e. ShowInEditForm ).
As far as the second part of your question, I wanted to pass along what we've done for similar situations in the past. In our situation, we needed a couple of different scrips: One that would allow us to propagate content type updates down to all of the lists in all webs and another that would reset Master Pages/Page Layouts to the site definition (un-customized form).
So, we created some custom stsadm commands for each of these actions. Doing it this way is nice because the scripts can be placed into source control and it implements the already-existing stsadm interface.
Custom SharePoint stsadm Commands