Clone IPublishedContent, just for front-end purpose
Hi
I know this might sound a little weird for a request, but i would like to know if its possible, to clone the IPublishedContent object, or a similar object-type in templates or another C# code.
The object should not go back into Umbraco, should not be stored or change anything in Umbraco. Its purly for Front-End purpose that i need to clone it.
I will give an example:
I have a job-application and i need to clone it for each office its assigned to.
The job-application will be presented through templates that are begin used by other doc-types, which is why i want to stick to IPublishedContent - object, so it can keep using the same templates (since the templates use the methods GetPropertyValue() etc.)
Basically for each office on a job-application i want to clone the IContent(Job-application) and change the office property to only hold that specific office. So I end up with more job-applications, but only one office in each.
Does that make sense?
I can understand thats its properly some kind of hack, since the Umbraco code properly dont want me to clone IPublishedContent, but if anyone can come up with a hack that would be fine with me.
I'm sure I don't understand where you want to go with this but let's find out with some questions ;-)
So you have a bunch of offices and a bunch of job applications.
On a job application you pick a office that has this application?
If job application has office 1, 2 and 3 selected you want to
present the application 3 times once for office 1, once for office 2
and once for office 3?
This can be done in a partial view with a simple loop or lamda expression.
So what am I missing that the cloning is needed? Can you show us a simple structure of the site?
Correct, the reason why i want to do it with the data, is because i want to avoid changing my templates. So the presentation of this data can continue as it has been made so far.
So the goal is to avoid changing the templates, by making the duplicates before, (which is still in some way in templates, but without manipulating all the sub-templates(partials) that are begin used.)
Its also because i need to do a sorting on it afterwards.
Currently i´m trying the following approach, still not working, but it might give you a more clear idea about how i see it could be done.
IContent jobAsContent = ApplicationContext.Current.Services.ContentService.GetById(jobApplication.Id);
foreach (dynamic office in offices) {
IContent clonedContent = ApplicationContext.Current.Services.ContentService.Copy(jobAsContent, languageId, false);
clonedContent.Id = jobApplication.Id;
clonedContent.SetValue("offices", office.ToString());// need someway to set just one of the offices on this cloned object.
allJobApplicationsList.Add(MyProject.Extensions.ContentExtensions.ToPublishedContent(clonedContent));
}
You shouldn't use IContent for the frontend. This hits the database and is only for CRUD operations. Also try to keep away from dynamics since it's considered bad practice. Both operations slow down your site considerably
If you can give a (simplified)example of your site structure and what you try to get in your view I can give you a example how to get to that result.
I´m not sure how much of the site structure you want or need to get a good understanding? Its maybe because i don't understand completely how its relevant.
Let me try to do my best to describe how its constructed. :-)
I get the JobApplications from Examine(To be correct i get the IDs from Examine), and turn that into a list of IPublishedContent.
Each Job-Application is a node (aka. page), each has a MultiNode TreePicker used to pick an office-tag.
Each Office-tag is as well a node, located in a settings node in the root.
I dont know if thats enough for you to get an impression of the structure? its a quite complex site with many DocTypes, so I think describing the site structure will just be confusing. Thats why I wanted to focus on the specifics, which I think is; How to Runtime-Clone a node/IPublishedContent and change one of its property values, without creating it in the database or Umbraco. Just C# runtime clone, so I can parse that clone acting as if it was any other IPublishedContent object. So i can avoid changing the rest of templates. (cause the job-applications are using templates that many other DocType are using as well)
I think the fastest, easiest and correct way is to use a different template for this specific doctype or just load a different partial if a certain document type is the current one. We use these kind of structures a lot without any issue.
Umbraco gives you a lot of freedom to structure the site as you want and there are a lot of people making "interesting" choices. That's why I was curious about the structure.
IContent dataAsContent = ApplicationContext.Current.Services.ContentService.GetById(content.Id);
string officeString = dataAsContent.GetValue<string>("offices");
if (officeString != null)
{
string[] offices = officeString.Split(',');
if (offices.Length > 1)
{
// if more than one office, we want to dublicate the content, so there is a content for each office, and each content should then only represent that specific office.
foreach (string office in offices)
{
IContent clonedContent = new Content(dataAsContent.Name, languageId, dataAsContent.ContentType);// creating a new content object based on the original.
clonedContent.Properties = dataAsContent.Properties.DeepClone() as PropertyCollection;// Need to clone the property collection, do no want to change the properties for the other clones.
clonedContent.Properties["offices"].Value = office;// Set the office property value to the specific offie that we are cloning for right now.
clonedContent.Id = content.Id;// Needed to set the id to the original contents ID, to get URLs correct.
items.Add(Nolz.Extensions.ContentExtensions.ToPublishedContent(clonedContent));
}
}
else
{
// just insert the item, no cloning needed.
IPublishedContent data = umbraco.TypedContent(content.Id);
items.Add(data);
}
}
else
{
// just insert the item, no cloning needed.
IPublishedContent data = umbraco.TypedContent(content.Id);
items.Add(data);
}
As I understand it you are using this to render views right?
This will give you performance issues as it will hit the database instead of taking the content from cache. This is also mentioned in the common pitfalls secition in the documentation.
There is a reason why a mntp returns IPublishedContent.
The performance issue is related to using the contentservice.GetById() which returns IContent using database calls and nu using the umbracohelper to get IPublishedContent from cache.
Instead of cloning the IPublishedContent via the IContent you can just reuse the same.
It looks like you are only changing the office name so you could get a IPublshedContent item and for each office add a Tuple<string, IPublishedContent> to the list where string is the office name and IPublishedContent is the content.
This doesn't hit the database and can be rendered however you want.
Another solution is to make a small Model with a property office and a property content.
The reason why i want to avoid changing the data-structure is because the rest of the code is relying on getting a list of IPublishedContent, so i want to keep it that way.
So my question would be if i can use a IPublishedContent, and still do the new Content(.. thing that I´m doing in the example..
If IPublishedContent is inheriting from IContent, then i guess i should be able to do the same trick?
I don't think you can get this done without altering your views.
If I were you I would look into Modelsbuilder. If you use that you can prevent these kinds of problems by extending the model you are using. It makes that you can easily add properties to your model.
Clone IPublishedContent, just for front-end purpose
Hi
I know this might sound a little weird for a request, but i would like to know if its possible, to clone the IPublishedContent object, or a similar object-type in templates or another C# code.
The object should not go back into Umbraco, should not be stored or change anything in Umbraco. Its purly for Front-End purpose that i need to clone it.
I will give an example:
I have a job-application and i need to clone it for each office its assigned to. The job-application will be presented through templates that are begin used by other doc-types, which is why i want to stick to IPublishedContent - object, so it can keep using the same templates (since the templates use the methods GetPropertyValue() etc.)
Basically for each office on a job-application i want to clone the IContent(Job-application) and change the office property to only hold that specific office. So I end up with more job-applications, but only one office in each.
Does that make sense?
I can understand thats its properly some kind of hack, since the Umbraco code properly dont want me to clone IPublishedContent, but if anyone can come up with a hack that would be fine with me.
Thanks
I'm sure I don't understand where you want to go with this but let's find out with some questions ;-)
So you have a bunch of offices and a bunch of job applications.
This can be done in a partial view with a simple loop or lamda expression.
So what am I missing that the cloning is needed? Can you show us a simple structure of the site?
Hi Frans
Correct, the reason why i want to do it with the data, is because i want to avoid changing my templates. So the presentation of this data can continue as it has been made so far.
So the goal is to avoid changing the templates, by making the duplicates before, (which is still in some way in templates, but without manipulating all the sub-templates(partials) that are begin used.)
Its also because i need to do a sorting on it afterwards.
Currently i´m trying the following approach, still not working, but it might give you a more clear idea about how i see it could be done.
I´m using this class, to convert IContent into IPublishedContent: https://gist.githubusercontent.com/jbreuer/dde3605035179c34b7287850c45cb8c9/raw/570cbaa30365653dbcf4142e988eba4fc692ecad/ContentExtensions.cs
I hope this gives an idea about what i try to accomplish, Thanks
You shouldn't use IContent for the frontend. This hits the database and is only for CRUD operations. Also try to keep away from dynamics since it's considered bad practice. Both operations slow down your site considerably
If you can give a (simplified)example of your site structure and what you try to get in your view I can give you a example how to get to that result.
I´m not sure how much of the site structure you want or need to get a good understanding? Its maybe because i don't understand completely how its relevant.
Let me try to do my best to describe how its constructed. :-)
I get the JobApplications from Examine(To be correct i get the IDs from Examine), and turn that into a list of IPublishedContent.
Like this:
Each Job-Application is a node (aka. page), each has a MultiNode TreePicker used to pick an office-tag.
Each Office-tag is as well a node, located in a settings node in the root.
I dont know if thats enough for you to get an impression of the structure? its a quite complex site with many DocTypes, so I think describing the site structure will just be confusing. Thats why I wanted to focus on the specifics, which I think is; How to Runtime-Clone a node/IPublishedContent and change one of its property values, without creating it in the database or Umbraco. Just C# runtime clone, so I can parse that clone acting as if it was any other IPublishedContent object. So i can avoid changing the rest of templates. (cause the job-applications are using templates that many other DocType are using as well)
Thanks
I don't know of a way to clone IPublishedContent.
I think the fastest, easiest and correct way is to use a different template for this specific doctype or just load a different partial if a certain document type is the current one. We use these kind of structures a lot without any issue.
Umbraco gives you a lot of freedom to structure the site as you want and there are a lot of people making "interesting" choices. That's why I was curious about the structure.
I succeeded figuring it out my self.
This is my solution:
As I understand it you are using this to render views right?
This will give you performance issues as it will hit the database instead of taking the content from cache. This is also mentioned in the common pitfalls secition in the documentation.
There is a reason why a mntp returns IPublishedContent.
Is the performance problem related to the first line in my code?
or to: new Content(...)
The performance issue is related to using the contentservice.GetById() which returns IContent using database calls and nu using the umbracohelper to get IPublishedContent from cache.
Instead of cloning the IPublishedContent via the IContent you can just reuse the same.
It looks like you are only changing the office name so you could get a IPublshedContent item and for each office add a
Tuple<string, IPublishedContent>
to the list where string is the office name and IPublishedContent is the content.This doesn't hit the database and can be rendered however you want.
Another solution is to make a small Model with a property office and a property content.
There should be no reason to go to the database.
I Agree, I dont want to go to the Database. :-P
The reason why i want to avoid changing the data-structure is because the rest of the code is relying on getting a list of IPublishedContent, so i want to keep it that way.
So my question would be if i can use a IPublishedContent, and still do the new Content(.. thing that I´m doing in the example..
If IPublishedContent is inheriting from IContent, then i guess i should be able to do the same trick?
I don't think you can get this done without altering your views.
If I were you I would look into Modelsbuilder. If you use that you can prevent these kinds of problems by extending the model you are using. It makes that you can easily add properties to your model.
Hi Niels,
I'm curious if you solved your issue and especially how.
Frans
Hi Frans
Yes, I have the solution above. But I did not find an faster alternative, so I´m accepting that it might be slow.
The "Nolz.Extensions.ContentExtensions.ToPublishedContent" is basically just this class: https://gist.githubusercontent.com/jbreuer/dde3605035179c34b7287850c45cb8c9/raw/570cbaa30365653dbcf4142e988eba4fc692ecad/ContentExtensions.cs
is working on a reply...