Is there a way to get the related pages from a left side of the page relationship that is not the CurrentDocument in Kentico - kentico

I need to access to the Main Page information from any of the Secondary Pages (1,2,3,4...) using a page relationship, and then retrieve the relationship list from the main page, in this case SecondaryPage1,SecondaryPage2,SecondaryPage3, etc..
I have the following structure
MainPage (Page Type e.g. Article) isRelatedTo:
SecondaryPage1 (Page Type: e.g. Item)
SecondaryPage2 (Page Type: e.g. Item)
SecondaryPage3 (Page Type: e.g. Item)
SecondaryPage4 (Page Type: e.g. Item)
SecondaryPage5 (Page Type: e.g. Item)
Is there an easy way to do it? I am using a CMSRepeater to display the items. I am thinking on creating a custom CMSRepeater for this specific scenario but I would like to know if there is a different approach.
So, to recap, The SecondaryPage1 isRelatedTo MainPage on the right side.
MainPage -> isRelatedTo -> SecondaryPage1
I am trying the display whole list from the MainPage, I need to access to that information on any of the secondary pages.
I created this code, it works pretty well I am just trying to discover is there is simpler solution or if there is an alternative.
....
List<CMS.DocumentEngine.TreeNode> mainRelatedItems = new List<CMS.DocumentEngine.TreeNode>();
mainRelatedItems.AddRange(DocumentHelper.GetDocuments().Types(PageTypes.Split(';')).InRelationWith(CurrentDocument.NodeGUID, PageRelationship, relationshipSide));
List<CMS.DocumentEngine.TreeNode> secondaryRelatedItems = new List<CMS.DocumentEngine.TreeNode>();
foreach (CMS.DocumentEngine.TreeNode item in mainRelatedItems)
{
if(ExcludeCurrentDocument)
secondaryRelatedItems.AddRange(DocumentHelper.GetDocuments().Types(PageTypes.Split(';')).InRelationWith(item.NodeGUID, SecondLevelPageRelationship, secondaryRelationshipSide).Where(x => x.NodeGUID != CurrentDocument.NodeGUID));
else
secondaryRelatedItems.AddRange(DocumentHelper.GetDocuments().Types(PageTypes.Split(';')).InRelationWith(item.NodeGUID, SecondLevelPageRelationship, secondaryRelationshipSide));
}
....
CustomRepeater.ItemTemplate = TransformationHelper.LoadTransformation(CustomRepeater, TransformationName);
CustomRepeater.DataSource = secondaryRelatedItems;
CustomRepeater.DataBind();
....

In your document query, you can specify the relationship GUID as well as the side and relationship name. It doesn't have to be the current document.
DocumentHelper.GetDocuments().InRelationWith(guid, "reltionshipname", side)
** UPDATE **
Based on the most recent update of your question, you'd need to get the Main document id/guid by URL parameter or session or some other parameter, then look that node up using something like DocumentHelper.GetDocument(MainDocID). After you have that Main document, you can perform your lookup using the code I provided above.
It doesn't sound like you have a great way to get this info simply because one of those Secondary pages could be related to possibly many other pages or page types. One suggestion may be to create a very specific relationship name and simply query your Main document by that very specific relationship name and the Secondary page ID to get any Main pages that may be related to it.

Related

DDD/CQRS: Combining read models for UI requirements

Let's use the classic example of blog context. In our domain we have the following scenarios: Users can write Posts. Posts must be cataloged at least in one Category. Posts can be described using Tags. Users can comment on Posts.
The four entities (Post, Category, Tag, Comment) are implemented as different aggregates because of I have not detected any rule for that an entity data should interfere in another. So, for each aggregate I will have one repository that represent it. Too, each aggregate reference others by his id.
Following CQRS, from this scenario I have deducted typical use cases that result on commands such as WriteNewPostCommand, PublishPostCommand, DeletePostCommand etc... along with their respective queries to get data from repositories. FindPostByIdQuery, FindTagByTagNameQuery, FindPostsByAuthorIdQuery etc...
Depending on which site of the app we are (backend or fronted) we will have queries more or less complex. So, if we are on the front page maybe we need build some widgets to get last comments, latest post of a category, etc... Queries that involve a simple Query object (few search criterias) and a QueryHandler very simple (a single repository as dependency on the handler class)
But in other places this queries can be more complex. In an admin panel we require to show in a table a relation that satisfy a complex search criteria. Might be interesting search posts by: author name (no id), categories names, tags name, publish date... Criterias that belongs to different aggregates and different repositories.
In addition, in our table of post we dont want to show the post along with author ID, or categories ID. We need to show all information (name user, avatar, category name, category icon etc).
My questions are:
At infrastructure layer, when we design repositories, the search methods (findAll, findById, findByCriterias...), should have return the corresponding entity referencing to all associations id's? I mean, If a have a method findPostById(uuid) or findPostByCustomFilter(filter), should return a post instance with a reference to all categories id it has, all tags id, and author id that it has? Or should my repo have some kind of method that populates a given post instance with the associations I want?
If I want to search posts created from 12/12/2014, written by John, and categorised on "News" and "Videos" categories and tags "sci-fi" and "adventure", and get the full details of each aggregate, how should create my Query and QueryHandler?
a) Create a Query with all my parameters (authorName, categoriesNames, TagsNames, if a want retrive User, Category, Tag association full detailed) and then his QueryHandler ensamble the different read models in a only one. Or...
b) Create different Queries (FindCategoryByName, FindTagByName, FindUserByName) and then my web controller calls them for later
call to FindPostQuery but now passing him the authorid, categoryid, tagid returned from the other queries?
The b) solution appear more clean but it seems me more expensive.
On the query side, there are no entities. You are free to populate your read models in any way suits your requirements best. Whatever data you need to display on (a part of) the screen, you put it in the read model. It's not the command side repositories that return these read models but specialized query side data access objects.
You mentioned "complex search criteria" -- I recommend you model it with a corresponding SearchCriteria object. This object would be technnology agnostic, but it would be passed to your Query side data access object that would know how to combine the criteria to build a lower level query for the specific data store it's targeted at.
With simple applications like this, it's easier to not get distracted by aggregates. Do event sourcing, subscribe to the events by one set of tables that is easy to query the way you want.
Another words, it sounds like you're main goal is to be able to query easily for the scenarios you describe. Start with that end goal. Now write your event handler to adjust your tables accordingly.
Start with events and the UI. Then everything else will fit easily. Google "Event Modeling" as it will help you formulate ideas sound what and how you want to build these style of applications.
I can see three problems in your approach and they need to be solved separately:
In CQRS the Queries are completely separate from the Commands. So, don't try to solve your queries with your Commands pipelines repositories. The point of CQRS is precisely to allow you to solve the commands and queries in very different ways, as they have very different requirements.
You mention DDD in the question title, but you don't mention your Bounded Contexts in the question itself. If you follow DDD, you'll most likely have more than one BC. For example, in your question, it could be that CategoryName and AuthorName belong to two different BCs, which are also different from the BC where the blog posts are. If that is the case and each BC properly owns its own data, the data that you want to search by and show in the UI will be stored potentially in different databases, therefore implementing a query in the DB with a join might not even be possible.
Searching and Reading data are two different concerns and can/should be solved differently. When you search, you get some search criteria (including sorting and paging) and the result is basically a list of IDs (authorIds, postIds, commentIds). When you Read data, you get one or more Ids and the result is one or more DTOs with all the required data properties. It is normal that you need to read data from multiple BCs to populate a single page, that's called UI composition.
So if we agree on these 3 points and especially focussing on point 3, I would suggest the following:
Figure out all the searches that you want to do and see if you can decompose them to simple searches by BC. For example, search blog posts by author name is a problem, because the author information could be in a different BC than the blog posts. So, why not implement a SearchAuthorByName in the Authors BC and then a SearchPostsByAuthorId in the Posts BC. You can do this from the Client itself or from the API. Doing it in the client gives the client a lot of flexibility because there are many ways a client can get an authorId (from a MyFavourites list, from a paginated list or from a search by name) and then get the posts by authorId is a separate operation. You can do the same by tags, categories and other things. The Post will have Ids, but not the extra details about those IDs.
Potentially, you might want more complicated searches. As long as the search criteria (including sorting fields) contain fields from a single BC, you can easily create a read model and execute the search there. Note that this is only for the search criteria. If the search result needs data from multiple BCs you can solve it with UI composition. But if the search criteria contain fields from multiple BCs, then you'll need some sort of Search engine capable of indexing data coming from multiple sources. This is especially evident if you want to do full-text search, search by categories, tags, etc. with large quantities of data. You will need to use some specialized service like Elastic Search and it won't belong to any of your existing BCs, it'll be like a supporting service.
From CQRS you will have a separeted Stack for Queries and Commands. Your query stack should represent a diferente module, namespace, dll or package at your project.
a) You will create one QueryModel and this query model will return whatever you need. If you are familiar with Entity Framework or NHibernate, you will create a Façade to hold this queries togheter, DbContext or Session.
b) You can create this separeted queries, but saying again, if you are familiar with any ORM your should return the set that represents the model, return every set as IQueryable and use LET (Linq Expression Trees) to make your Query stack more dynamic.
Using Entity Framework and C# for exemple:
public class QueryModelDatabase : DbContext, IQueryModelDatabase
{
public QueryModelDatabase() : base("dbname")
{
_products = base.Set<Product>();
_orders = base.Set<Order>();
}
private readonly DbSet<Order> _orders = null;
private readonly DbSet<Product> _products = null;
public IQueryable<Order> Orders
{
get { return this._orders.Include("Items").Include("Items.Product"); }
}
public IQueryable<Product> Products
{
get { return _products; }
}
}
Then you should do queries the way you need and return anything:
using (var db = new QueryModelDatabase())
{
var queryable = from o in db.Orders.Include(p => p.Items).Include("Details.Product")
where o.OrderId == orderId
select new OrderFoundViewModel
{
Id = o.OrderId,
State = o.State.ToString(),
Total = o.Total,
OrderDate = o.Date,
Details = o.Items
};
try
{
var o = queryable.First();
return o;
}
catch (InvalidOperationException)
{
return new OrderFoundViewModel();
}
}

In Orchard CMS, how can I get all items of a particular content type based in the values of an attached part's properties

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!

Customized Tridion Search Index Handler: Custom vs Standard field for page url?

I was playing around with custom Search Indexing Handlers for SDL Tridion 2011 (GA). I got something working, using the very helpful information provided by Arjen, however I am not sure if my execution is the best option.
The requirement is to be able to search for pages in the CMS by url (eg www.example.com/news/index.html). In order to do this I have the created a class using the ISearchIndexingHandler interface (code below). I am indexing the url in the ContentText field of the item, however I am not sure if this would normally contain something else for a page (I think a page only has metadata so this should be OK). The advantage of using this over a custom field is that I can simply type the url in the search box without having to use <url> IN <fieldname> or something like that.
So my question is, is there any reason not to use ContentText for Pages, and is there any advantage in using a custom field? Also bonus marks go to anyone with good ideas on how to handle BluePrinting (if I create a page in a parent publication, I want the local urls also to be indexed in the child publications), and the case where a Structure group path is altered (I guess I can somehow trigger a re-index of child page items from within my indexing handler).
The code:
using System;
using Tridion.ContentManager.Search;
using Tridion.ContentManager.Search.Indexing.Handling;
using Tridion.ContentManager.Search.Indexing.Service;
using Tridion.ContentManager.Search.Indexing;
using Tridion.ContentManager.Search.Fields;
namespace ExampleSearchIndexHandler
{
public class PageUrlHandler : ISearchIndexingHandler
{
public void Configure(SearchIndexingHandlerSettings settings)
{
}
public void ExtractIndexFields(IdentifiableObjectData subjectData, Item item, CoreServiceProxy serviceProxy)
{
PageData data = subjectData as PageData;
if (data != null)
{
PublishLocationInfo info = data.LocationInfo as PublishLocationInfo;
string url = GetUrlPrefix(data) + info.PublishLocationUrl;
item.ContentText = url;
}
}
private string GetUrlPrefix(PageData page)
{
//hardcoded for now, but will be read from publication metadata
return "www.example.com";
}
}
}
You can store the url in the ContextText Property. Thies field is used to index Template content data.
Tridion does not index shared item(s) of child publication.
Indexing is triggered on Item modification(create, update, delete, localize and unlocalize).
Or you can use reindexing tool to reindex ur item. but there is no way to index shared items in child publication.
I don't think you can include the URL prefix in neither your search query as the indexed item. Because shared items are not indexed, you will probably index the Page from the Website Structure layer, which is never published.
When a Structure Group is moved you would have to make an event handler that triggers re-indexing all child pages using a protected method of the TOM.NET API. This method is not part of the public API, so posting the code for that solution would probably declare me a persona non grata with R&D :)
Before you re-index anything you should store the original publish location url of the Structure Group in the TcmEventArgs.ContextVariables property, so you can verify whether or not a re-indexing action is necessary.

DDD - How Can I Avoid Crossing Aggregate Boundaries Here?

We're working on a new project (re-writing existing app), and I'm running into problems with my domain model / repository design.
Here is a (simplified) version of two key portions in our domain model:
As you can see, I have an abstract concept of a Post, which can be things like a Review, a Discussion, a Photo, a Video, etc. Posts can also have comments.
I also have a abstract concept of a Location, which are obviously things like Streets, Cities, Neighbourhoods, etc.
Now, this naturally looked to me as two clear aggregate roots.
So I created two repositories, one called PostRepository, and another called LocationRepository.
This was all working fine, I can add/get any type of Post (or comment), and add/get any type of Location via one of these two repositories.
But now im in the scenario of a "landing page" for a City (for example).
On this page, I need to basically show "all posts for this location".
How is that defined? Well, a Post can be (optionally) tagged at a Location. Implementation detail, so I don't want to go too deep into data (as that's not what DDD is about), but essentially there is geospatial intelligence to work out which posts are contained in a particular location by the shape file of the location, and the latitude/longitude of the tagged Post.
But how can I retrieve this information without crossing the boundaries?
Which repository do I use? Do I need a new one?
If it matters (or for the curious), this is a web application (ASP.NET MVC), with a SQL Server 2008 database and Entity Framework 4.0.
If you need any clarification, let me know.
EDIT
We currently use a modified version of the Specification pattern in order to retrieve domain models.
For example, this is the code in our BLL to retrieve all Review's where Score >= 4:
var reviews = postRepository // GenericRepository<Post>
.Find() // IQueryable<Post>
.OfType<Review>() // IQueryable<Review>
.Where(x => x.Score >= 4)
.ToList(); // List<Review>
But now I need some code like this:
var reviews = postRepository
.Find()
.OfType<Review>()
.Where( //lat long, or Locations FK )
.ToList();
The problem is I don't know how to do the above query without adding an intermediary join-entity (LocationPost - as it's a many to many), and add a FK to the Post domain model to that.
But by doing that, I am crossing the aggregate boundaries - aren't I?
Why is this a problem?
According to Evans in his book, one AR may very well reference another AR.
(You may not however reference a child element in an AR from another AR)
Also, are locations really aggregate roots?
The definition of an aggregate root is that it acts as a boundary of concistency.
Does that fit the definition of a location?
I'd say a location is a value object.
There are pretty much two camps here regarding repositories and AR associations:
One that says that all aggregate roots have to be fetched through their respective repository, and AR's should use soft relations, eg ID's between them
And one that says that aggregate roots may very well fetch other associated aggregate roots and that a repository is merely a way to find aggregate roots.
I would bind post to the location at creation time so that for each location I can get (through a repository) a list of associated posts. It would look like this:
Creation:
var p = new Post(latitude, longitude);
var locations = locationRepository.FindByCoordinates(latitude, longitude);
foreach (var l in locations)
{
l.AssociatePost(p);
}
session.Save(p);
Retrieval:
var associatedPosts = postRepository.FindByLocation(locationId);
foreach (var p in associatedPosts)
{
Display(p);
}
Under the hood, the association between posts and location would be implemented as a many-to-many table relationship. There is one problem with this solution: adding a new location requires to scan all the posts and assign them to the new location (if applicable).
Hope that helps.
Let's say you used the Specification pattern, could you build a Post Specification using a Location object? Then you just pass the Specification to your Post Repository, and get back the result.

Paging more than one collection on a single page in asp.net mvc

I'm building a search engine for documents using asp.net mvc. The results of the search are two different IList collections (One for the people matched and one for the documents matched with the search parameters). I put these collections in a class and return the class to the view. The view uses HTML.RenderPartial() to bind the respective collection to its own user control. I want to page each of those collections independently. How do I create an action method to change the page of each individual collection without having to rebind the values for the other collection? (Both collections are displayed on the same page).
public ActionResult Search(int pagePeople, int pageDocuments)
Alternatively, you can use the new Html.Action (take a look here). I don't exactly understand of what you have or what you want. Try giving me more info.

Resources