In my previous post I talked about basic access of custom properties in Sitefinity. Namely, strings and numbers. Those alone cover a lot of what you’d use when it comes to custom fields on built-in content types and custom content types. In this post, however, we’re delving into some of the more complex custom properties that can be associated with Sitefinity content. Like in the previous post, we’ll cover both how to query by a complex property (where applicable) and how to fetch the content of the complex property.
Classifications (Tags, Categories, Custom)
Fetching
Tags and Categories within Sitefinity are both different kinds of Taxonomies, and thus they are both accessed in a similar fashion. Let’s say our Press Release custom content type has a “Tags” property, and we have several Tags defined under Content > Tags: “A”, “B”, and “C”. Whenever fetching a content item’s Tags or querying/filtering by Tags, we will be working with the aforementioned stored IDs.
DynamicContent pressRelease = dynamicModuleManager.GetDataItems(pressReleaseType) .FirstOrDefault(d => d.Status == ContentLifecycleStatus.Live && d.Visible); TrackedList<Guid> tags = pressRelease.GetValue<TrackedList<Guid>>("Tags");
Note that the collection returned is not a standard List, but rather a TrackedList. TrackedList is a construct from OpenAccess, the ORM Sitefinity utilizes and what sits between your code and the database. It mostly functions like you would expect a regular List, but has additional methods and properties that are beyond the scope of this post. It is only important to note here so that when you call GetValue, you feed it the correct type. You need to add “using Telerik.OpenAccess;” to the top of your file in order to have the class available. Be aware that even if you restrict the field on the custom content type to only allow a single classification to be selected, you still work with a TrackedList. Much like how in the previous post you still work with the nullable decimal type even when the field can’t be null, Sitefinity stores the classification relationship the same way regardless.
So now we have the IDs of the Tags. If we tried to display them, we’d of course just see GUIDs instead of tag names. As a consequence, there’s an additional step required in order to display the Tags’ Title properties (i.e. the user-friendly names):
TrackedList<Guid> tagIds = pressRelease.GetValue<TrackedList<Guid>>("Tags"); var taxonomyManager = TaxonomyManager.GetManager(); var tagTaxonomy = taxonomyManager.GetTaxonomy(TaxonomyManager.TagsTaxonomyId); IEnumerable<string> tagTitles = tagTaxonomy.Taxa.Where(t => tagIds.Contains(t.Id)).Select(t => t.Title.ToString());
Here, we’re using Sitefinity’s TaxonomyManager, and fetching the Taxonomy for Tags by passing in the TagsTaxonomyId (which is available as a static property of TaxonomyManager). Once we have that, we access that Taxonomy’s Taxa (i.e. the Tags currently in Sitefinity), and tell it to return only the ones that match the IDs assigned to our Press Release. Once we have the tag titles (we call ToString() because Title is an LString), we can assign them to a “Tags” property in a domain object, string.Join() them together as a comma separated list, or do whatever else we like.
NOTE: The taxa have two properties that both look like the display text you wish to use: “Title” and “Name.” “Name” refers to the “name used in code” that developers can set on classifications in the Sitefinity backend. Title is the actual label that we want to display to users throughout the site (and is what Sitefinity uses in its built-in Tag widgets). Whenever working with classifications, use “Title” as the label/friendly version to display instead of “Name.”
For fetching Categories attached to a given custom content type, it’s very much the same procedure. The only two differences are that instead of passing “TaxonomyManager.TagsTaxonomyId” to the GetTaxonomy method, you pass “TaxonomyManager.CategoriesTaxonomyId” instead; and the name of the field on the custom content type will be “Category” instead of “Tags.”
For custom classifications, it’s the same: Acquire the ID of the custom classification taxonomy, change the field name fetched to that of your custom classification on the custom content type, and you’re done!
Querying
Whenever you’d like to query a custom content type by a classification, you have to do so by its ID. If you don’t already have the ID handy (e.g. a user has a box where they can input the Tag or Category by name), you have to go through the TaxonomyManager to fetch the classification’s ID. Once you have the ID, you simply call “Contains” on the retrieved TrackedList collection within your Where() LINQ statement to perform the filter:
Guid tagId = /* GUID either passed in, or fetched from TaxonomyManager. */; DynamicContent pressRelease = dynamicModuleManager.GetDataItems(pressReleaseType) .Where(d => d.Status == ContentLifecycleStatus.Live && d.Visible) .Where(d => d.GetValue<TrackedList<Guid>>("Tags").Contains(tagId));
With this query, you’ll get all Live/Published Press Releases that are tagged with the given Tag. You can use basic or/and logic if you want more complex filtering (e.g. “return any Press Releases that contain any tags” or “return any Press Releases that have all of these tags”) in the LINQ itself.
Just like when fetching classifications, the only difference when trying to query by a different classification (either by Category or a custom one) is the field you query on.
Related Media (Document, Image, Video)
Custom content types can also have fields that store relations to other media content in your Sitefinity site. In this instance, a Related Media field allows users to assign Images, Documents/Files, and Videos to a given piece of custom content. Typically, you don’t query or filter content by what images/documents/videos are associated with it, so in this section we’ll just focus on fetching the related media from custom content types.
When attempting to fetch related media, Sitefinity returns it as a List of IDataItem. If you were to use the GetValue() method like this, that is what you’d get in return. However, there’s a much easier way to work with related media, so you don’t have to do any manual fetching of metadata (like what occurs with classifications): Instead of calling GetValue<List<IDataItem>>(), you can call GetRelatedItems to fetch a strongly-typed object. For Related Media, the three types you use are:
- Telerik.Sitefinity.Libraries.Model.Image
- Telerik.Sitefinity.Libraries.Model.Document
- Telerik.Sitefinity.Libraries.Model.Video
It’s important to verify that you are using the right “Image” (or Document or Video) classes in your code. Many other libraries have an Image class (or the others), so be sure that you are utilizing the one provided in Sitefinity’s Model namespace. putting the namespace up as a using right away is the easiest way to make sure your IDE doesn’t try to include other namespaces. With that in mind, here’s an example of fetching these different kinds of related media from Sitefinity:
IQueryable<Image> images = pressRelease.GetRelatedItems<Image>("MyImages"); IQueryable<Document> documents = pressRelease.GetRelatedItems<Document>("MyDocs"); IQueryable<Video> videos = pressRelease.GetRelatedItems<Video>("MyVids");
With these strongly-typed objects at your disposal, you instantly have access to all the important information for your related media: Titles, Alternative Text (for images), URLs to the media itself (via the “MediaUrl” property), etc.
There are many shared properties across these media types. You can call a more generic method, GetRelatedItems<MediaContent>(), and still get your related media items (MediaContent is in the same namespace as Image/Document/Video). While you won’t have access to specific properties (e.g. Alternative Text for Images), you can use this to your advantage if you’re writing some generic code for media, or if you don’t care about the specific properties and just want titles and media URLs. Here’s a sample where we just care about the latter, and want to extend Dynamic Content so that we can easily access its related media’s URLs:
/* Can define this in an Extensions class */ public static IQueryable<string> GetMediaUrls<TMedia>(this DynamicContent dataItem, string fieldName) where TMedia : MediaContent { return dataItem.GetRelatedItems<TMedia>(fieldName).Select(m => m.MediaUrl); } /* Usage Example */ IQueryable<string> iamgeUrls = pressRelease.GetMediaUrls<Image>("MyImages"); IQueryable<string> docUrls = pressRelease.GetMediaUrls<Document>("MyDocs"); IQueryable<string> vidUrls = pressRelease.GetMediaUrls<Video>("MyVids");
In this simplified example, we are able to leverage the generic “MediaContent” type for Sitefinity Related Media, and provide a quick, useful little extension method for dynamic content, which saves us from having to dip into the objects to get the right property we want, and write cleaner code. You could take this example further, and create a generic “Media” domain object in your business logic, with simple properties like Title and URL, and have the extension method return that instead. Sky’s the limit!
GetRelatedItems Pitfall
One issue I come across is that GetRelatedItems() will, under certain conditions, not return any results. This is because in order for GetRelatedItems() to work, the collection of data items you’re working with must be within an iterator (i.e. a foreach loop) or in memory (i.e. calling ToList() after the Where()). If neither of these conditions are followed, then Sitefinity does not load the related data in its objects, which results in no related items getting returned. The following code example, which returns a collection of image collections (one per Press Release), will not return any Images, even if they have some!
var imageCollections = dynamicModuleManager.GetDataItems(type) .Where(d => d.Status == ContentLifecycleStatus.Live && d.Visible) .Select(d => d.GetRelatedItems<Image>("MyImages")); // Returns nothing!
You can avoid this by either adding “.ToList()” after the Where() but before the Select() (so that Sitefinity loads the related data before you attempt to access it), or iterate over the “dataItems” variable in a foreach loop, and calling GetRelatedItems in there (dropping the Select() altogether). When you do either of these things, make sure that your filtering is not taking place in-memory, but rather before you call ToList() or start iterating. If you’re not careful, you could bring in your entire data set from the database, only to filter 99% of it out! Below are two examples where related items are successfully pulled down:
Using ToList()
var imageCollections = dynamicModuleManager.GetDataItems(type) .Where(d => d.Status == ContentLifecycleStatus.Live && d.Visible) .ToList() // Ensures related items load; do not put this before the Where()! .Select(d => d.GetRelatedItems<Image>("MyImages"));
Using foreach
var pressReleases = dynamicModuleManager.GetDataItems(type) .Where(d => d.Status == ContentLifecycleStatus.Live && d.Visible); foreach (var pressRelease in pressReleases) { // Related items load within the foreach iterator. var images = pressRelease.GetRelatedItems<Image>("MyImages"); }
Next Time: Related Data
One of the most powerful (and complex!) kinds of data you can have on your custom content types is other custom content types. These come in the form of “Related Data” and can be used to associate one kind of content with another, in whatever way makes sense in your own business domain logic. You can even access content types in other custom modules! As an example use case, a Press Release might have a list of Related Companies that are associated with a Release. And “Company” can be its own custom content type with its own properties. In my next post, I’ll tell you all about Related Data, and will also touch on working with custom content types set up in a parent/child relationship (a different method of “relating” data together).
The post Complex Custom Properties for Sitefinity Content appeared first on Falafel Software Blog.