0
votes

Im getting a headache from the Orchard Token Providers and any help would be a blessing. Ive mostly been copying the Comments Tokens.

The target is for an email to be sent when a 'Question' content item is published.

Here is my solution for the Content Type 'Question':

public interface ITokenProvider : IEventHandler
{
    void Describe(dynamic context);
    void Evaluate(dynamic context);
}

public class QuestionTokens : ITokenProvider

{
    public QuestionTokens()
    {
        T = NullLocalizer.Instance;
    }
public Localizer T { get; set; }

public void Describe(dynamic context)
{
    //presume this is correct 
    context.For("Content", T("Content Items"), T("Content Items"))
        .Token("QuestionText", T("Question Text"), T("Text of the question"))
        .Token("QuestionAuthor", T("Question Author"), T("Author of the question"));
    //presume this is incorrect? correct for the content type?
    context.For("Question", T("Questions"), T("Questions from users"))
        .Token("QuestionText", T("Question Text"), T("Text of the question"))
        .Token("QuestionAuthor", T("Question Author"), T("Author of the question"));
}

public void Evaluate(dynamic context)
{

    Func<IContent, object> questionTextAccessorFromContent = (content) => { 
        var part = content.As<QuestionPart>();
        return part.QuestionText; 
    };

    Func<QuestionPart, object> questionTextAccessor = (part) =>
    {
        return part.QuestionText;
    };

    Func<IContent, object> authorAccessorFromContent = (content) => { 
        var part = content.As<QuestionPart>();
        return part.Author; 
    };

    Func<QuestionPart, object> authorAccessor = (part) =>
    {
        return part.Author;
    };

    //doesnt work 
    context.For<IContent>("Content")
        .Token("QuestionText", (Func<IContent, object>)
    (content => content.As<QuestionPart>().Record.QuestionText))
        .Token("QuestionAuthor", (Func<IContent, object>)
    (content => content.As<QuestionPart>().Record.Author));
    //doesnt work 
    context.For<IContent>("Question")
        .Token("QuestionText", (Func<IContent, object>)
    (content => content.As<QuestionPart>().Record.QuestionText))
        .Token("QuestionAuthor", (Func<IContent, object>)
    (content => content.As<QuestionPart>().Record.Author));
    //doesnt work 
    context.For<QuestionPart>("Question")
        .Token("QuestionText", (Func<QuestionPart, object>)
    (content => content.Record.QuestionText))
        .Token("Author", (Func<QuestionPart, object>)
    (content => content.Record.Author)); ;
}

private static string QuestionText(IContent question) 
{
    var questionPart = question.As<QuestionPart>();
    return questionPart.QuestionText;
}

private static string Author(IContent question) 
{
    var questionPart = question.As<QuestionPart>();
    return questionPart.Author;
}

}

This is the Action (when the Question is published), it sends a email with the body text: (updated to test Medeiros suggestion)

<p>A question has been created by: {Content.QuestionAuthor}</p>
<p>A question has been created by: {Content.QuestionAuthor.Text}</p>
<p>A question has been created by: {Content.QuestionAuthor.Value}</p>
<p>A question has been created by: {Question.QuestionAuthor}</p>
<p>A question has been created by: {Question.QuestionAuthor.Text}</p>
<p>A question has been created by: {Question.QuestionAuthor.Value}</p>
<p>Message: {Content.QuestionText}</p>
<p>Message: {Content.QuestionText.Text}</p>
<p>Message: {Content.QuestionText.Value}</p>
<p>Message: {Question.QuestionText}</p>
<p>Message: {Question.QuestionText.Text}</p>
<p>Message: {Question.QuestionText.Value}</p>

All 'my' tokens are replaced with blank text. Other tokens like: {Content.ContentType} {User.Email} work just fine. Any mistakes or hints that anyone notices will be very useful.

Thanks, Matt

3

3 Answers

1
votes

IVe re-written the token provider and it works sort of... (and now works completely)

public class QuestionTokens : Orchard.Tokens.ITokenProvider
{
    private readonly IContentManager contentManager;
    private readonly IWorkContextAccessor workContextAccessor;

    public QuestionTokens(IContentManager contentManager, IWorkContextAccessor workContextAccessor)
    {
        this.workContextAccessor = workContextAccessor;
        this.contentManager = contentManager;
        T = NullLocalizer.Instance;
    }

    public Localizer T { get; set; }

    /// <summary>
    /// Describes the specified context.
    /// </summary>
    /// <param name="context">The context.</param>
    public void Describe(DescribeContext context)
    {
        context.For("Content", T("Content Items"), T("Content Items"))
            .Token("QuestionText", T("Question Text"), T("Text of the question"))
            .Token("QuestionAuthor", T("Question Author"), T("Author of the question"));
    }

    /// <summary>
    /// Evaluates the specified context.
    /// </summary>
    /// <param name="context">The context.</param>
    public void Evaluate(EvaluateContext context)
    {
        context.For<IContent>("Content")
            .Token("QuestionText", content => QuestionText(content, context))
            .Token("QuestionAuthor", content => Author(content, context));
    }

    private string QuestionText(IContent question, EvaluateContext context) 
    {
        var questionPart = question.As<QuestionPart>();
        return questionPart.QuestionText;
    }

    private string Author(IContent question, EvaluateContext context) 
    {
        var questionPart = question.As<QuestionPart>();
        return questionPart.Author;
    }

}

I can now debug the code at least in the private QuestionText and Author methods. The value retrieved is 'null' due to the published event firing when this line is fired in the controller: services.ContentManager.Create("Question");

so the content item has not been populated and the 'published' event is firing before its life has begun. Time to play about with its content type settings a bit to see if that makes a difference...

Finally all working as needed. The Events section 'published' event is still happening when i dont expect it to so I've changed the life of the content.

From a controller: (some code into a service removed so I don't have a post more code)

var item = services.ContentManager.New<Models.QuestionPart>("Question");
this.TryUpdateModel(item);
services.ContentManager.Create(item.ContentItem); //where the event is firing  
Services.ContentManager.Publish(item.ContentItem); //where i expected the event to fire :) 

Originally it was create, update and save. But the events restricted that. So now its new, update and create. As for the tokens. They are simply: {Content.QuestionAuthor} and {Content.QuestionText}. Nice and simple when everything is working, and everything is right.

0
votes

Matthew, try adding a .Value for the: {Content.QuestionAuthor.Value}

If this doesn't work, try .Text.

Hope this helps.

0
votes

The code in your original question looks accurate. You follow the correct Orchard Events bus convention of creating an interface inheriting from IEventHandler link.

Your change in the controller code is likely the reason you are getting values. My experience debugging the code is that it depends on what Event your rule is subscribing to. For example an event of type "Content Created" hits the debugger in the Evaluate method but all custom part properties are null. But an event of "When a Custom Form is submitted" hits and has values populated.

I spent a full day troubleshooting my code which looks similar to yours above and it wasn't until I started testing the other event types in my rule that it had data populated in the private functions.

Here's my code:

public interface ITokenProvider : IEventHandler
{
  void Describe(dynamic context);
  void Evaluate(dynamic context);
}

public class PersonTokens : ITokenProvider
{
  private readonly IContentManager _contentManager;

  public Localizer T { get; set; }

  public PersonTokens(IContentManager contentManager)
  {
    _contentManager = contentManager;
    T = NullLocalizer.Instance;
  }
  public void Describe(dynamic context)
  {
    context.For("Content", T("Content Items"), T("Content Items"))
      .Token("FirstName", T("First Name"), T("The Person's First Name"))
      .Token("LastName", T("Last Name"), T("The Person's Last Name"))
      .Token("EmailAddress", T("Email Address"), T("The Person's Email Address"))
      .Token("PhoneNumber", T("Phone Number"), T("The Person's Phone Number"));
  }

  public void Evaluate(dynamic context)
  {
    // Orchard.Tokens.Implementation.TokenManager.EvaluateContextImpl
    context.For<IContent>("Content")
      .Token("FirstName", (Func<IContent, object>)FirstName)
      .Token("LastName", (Func<IContent, object>)(content => content.As<PersonPart>().Record.LastName))
      .Token("EmailAddress", (Func<IContent, object>)(content => content.As<PersonPart>().EmailAddress))
      .Token("PhoneNumber", (Func<IContent, object>)PhoneNumber); // left the PhoneNumber out of sample
  }

  private string FirstName(IContent person)
  {
    var personPart = person.As<PersonPart>();
    return personPart.Record.FirstName;
  }
}