Display Profile Fields on Blog Posts

May 17, 2011 at 3:54 PM
Edited May 17, 2011 at 3:55 PM

I added two new fields to the Content Part, Profile. These fields are FullName (TextField) and Thumbnail (ImageField). I would like to display the FullName and Thumbnail at the top of blog posts.

I have added Parts.Common.Metadata-BlogPost.cshtml to my Theme\Views folder and I have changed its content to this:

<div class="contentauthor"><p>@Model.ContentPart.Owner.UserName</p></div>

That works perfectly for displaying the username.

 

But when I try to do either of these it doesn't work: 

<div class="contentauthor"><p>@Model.ContentPart.Owner.FullName</p></div>

<div class="contentauthor"><p>@Model.ContentPart.Owner.Profile.FullName</p></div>

It tells me the fields 'FullName' or 'Profile' don't exist.

 

Did I do somethign wrong by adding these fields to the Content Part of Profile rather than the User type itself? I'm a little confused as to when I would add a field directly to the User type or to the Profile Content Part.

Thanks!

 

 

 

 

Coordinator
May 18, 2011 at 5:35 AM

The problem you're describing happens because the model.contentpart for the shape you're using is of type CommonPart. As you can see in the migrations a blogpost contains such entity (CommonPart) and therefore that's why this part is shown as part of the blogpost.

ContentDefinitionManager.AlterTypeDefinition(
"BlogPost" ,
cfg => cfg
.WithPart("BlogPostPart")
.WithPart("CommonPart", p => p.WithSetting("CommonTypePartSettings.ShowCreatedUtcEditor", "true" ))
.WithPart("PublishLaterPart")
.WithPart("RoutePart")
.WithPart("BodyPart")
);

The new fields you are adding are however part of the ProfilePart and as such are not shown as part of the blogpost.
Also since the fields in the profile were added dynamicaly it wouldn't be very safe to reference them directly in a shape.

Would a scenario like having a link to the user profile page work ?

Example, by adding the following to the shape alternate you are creating:

<div class="contentauthor">
    @Display(New.Profile_LinkFor(UserName: Model.ContentPart.Owner.UserName))
</div>

Notice that there's a bug on the profile_linkfor shape that i just noticed and that it's code should isntead be something like:

@Html.ActionLink(Model.Text != null ? (string)Model.Text : (string)Model.UserName, "Index", new { area = "Contrib.Profile", controller = "Home", username = Model.UserName })

Andre

May 18, 2011 at 3:30 PM

I see how parts can be linked up in the Migration class.

I also see (using the Designer tool) that my Model in this case is an instance of the Parts_Common_Metadata Shape. Parts_Common_Metadata has only one property, ContentPart, which is of type CommonPart.

But what I don't understand is how these two came together. Specifically, how did Parts_Common_Metadata come to have the property ContentPart which is a CommonPart?

 

So CommonPart.Owner is an IUser, which has only UserName and EMail as properties. Is there a way for me to get to the profile for a user programatically?

Unfortunately simply linking to the profile won't do. I'm trying to follow the format used on Gawker blogs like Gizmodo. Here's an example post:

Gizmodo Article

At the very beginning of the post I want to display the authors thumbnail and their full name (not user name). Their name would then be a hyperlink to their profile.

 

 

I had noticed this bug in the profile_linkfor shape and hacked around it by specifying the /Profile path directly. I'll try changing it to what you've shown above.

Coordinator
May 18, 2011 at 7:16 PM

ContentPart is defines on the CommonPart Driver.

 

Regarding showing the field you want, from the dynamic contentpart profile, the following code would allow you to show the profile field you want:

 

<div class="contentauthor"><p>@Model.ContentPart.Owner.UserName</p></div>

 

@{

    ContentItem user = Model.ContentPart.Owner.ContentItem;

    var part = user.Parts.FirstOrDefault(p => p is ContentPart && p.TypePartDefinition.PartDefinition.Name == "ProfilePart");

    var field = part.Fields.FirstOrDefault(f => f.Name == "FullName") as TextField;

}

 

<div class="contentauthorfullname"><p>@field.Value</p></div>

May 18, 2011 at 9:20 PM

Yup, that works smooth. Now you and I have talked outside of this thread a few times so I wanted to add some notes to fill people in who might just be joining us.

The main problem we had here is that there is no concrete C# class for ProfilePart. ProfilePart is 100% dynamic and its definition lives only in the content management system of Orchard.

If there was a concrete type we could have used some of the handy extension methods like Owner.ContentItem.As<ProfilePart> to get the child ProfilePart attached to the User. But it doesn't really make sense to have a concrete class for the profile because it wouldn't have any properties. The proifile ContentPart ships empty and you add whatever fields you want to it. So if you want to access those fields at runtime you have to do it dynamically, and that's the code andrerod shows above.

 

The first trick is looking for a part attached to the user with the name "ProfilePart". Again, there's nothing to cast this to so we're just leaving it as a plain old ContentPart.

Next, we look for the field "FullName". This name is whatever you called your field and the type is whatever type it is. I have a TextField called "FullName" and an ImageField called "Thumbnail".

The field comes back to us as a plain ContentField, so we need to cast it to the proper field type (TextField or ImageField in my case).

Then finally we can read the property values we want.

 

Since I thought I might end up wanting to do this somewhat frequently I added a few extension methods to a Module I made for my site:

static public ContentPart FirstNamed(this IEnumerable<ContentPart> parts, string name)
{
    return parts.FirstOrDefault(p => p is ContentPart && p.TypePartDefinition.PartDefinition.Name == name);
}

static public ContentField FirstNamed(this IEnumerable<ContentField> fields, string name)
{
    return fields.FirstOrDefault(f => f.Name == name);
}

With that in place it was pretty easy for me to get the exact display I wanted for Parts.Common.Metadata-BlogPost.cshtml:

@using Contrib.ImageField.Fields;
@using CrazyMobiler.ContentManagement;
@using Orchard.ContentManagement;
@using Orchard.Core.Common.Fields;
@using Orchard.Core.Common.Models;
@using Orchard.Users.Models;
@{

string fullName=null;
string thumbUrl=null;

ContentItem user = Model.ContentPart.Owner.ContentItem;
ContentPart profilePart = user.Parts.FirstNamed("ProfilePart");

if (profilePart != null)
{
    TextField fullNameField = profilePart.Fields.FirstNamed("FullName") as TextField;
    if (fullNameField != null) { fullName = fullNameField.Value; }
    
    ImageField thumbField = profilePart.Fields.FirstNamed("Thumbnail") as ImageField;
    if (thumbField != null) { thumbUrl = thumbField.FileName; }
}

}
<div class="contentauthor"><img src="@thumbUrl"/>@Display(New.Profile_LinkFor(UserName: Model.ContentPart.Owner.UserName, Text:fullName))</div>

Hope that helps someone else.

Jan 3, 2012 at 7:11 PM

Excellent, this was exactly the information I was looking for.

Thanks for sharing!

Sep 14, 2012 at 6:29 AM
New.Profile_LinkFor(UserName: Model.ContentPart.Owner.UserName, Text:fullName)) outputs a link with incorrect href like
<a href="/Contents/Item?username=admin" shape-id="8">admin</a>
whereas it should be <a href="/Profile/admin" shape-id="8">admin</a> what am i doing wrong here?

Oct 4, 2012 at 8:33 AM

Did you try and override the Profile.LinkFor.cshtml shape template or tried to modify it so that includes the controller name when generating the action link?

E.g. change the template from:

 

@using System.Web.Mvc.Html
@Html.ActionLink(Model.Text != null ? (string)Model.Text : (string)Model.UserName, "Index", new { username = Model.UserName })

To:

@using System.Web.Mvc.Html
@Html.ActionLink(Model.Text != null ? (string)Model.Text : (string)Model.UserName, "Index", "Home", new { username = Model.UserName })

Not sure if that's exactly it, but it must have something to do with routing, since you seem to be rendering the shape with the correct parameters.