Adding links in HTML widget / site hosted as virtual directory / post process content

Topics: Core
Nov 14, 2011 at 6:28 PM

I was curious if it is currently possible to do some post-processing on HTML output.
This would be useful so that I could replace urls like "~/media/default/myimage.png" using Url.ContentUrl(), so that the resolved url would for example be http://someserver.com/mysite/media/default/myimage.png, where "mysite" is a virtual directory.
I saw the same question on Stack Overflow, but couldn't find it or a similar question on Codeplex, hence this post.

Nov 14, 2011 at 6:34 PM

If you mean post-processing BodyPart, you can implement IHtmlFilter to perform post-processing (this is how Markdown and BBCode do their stuff.

Although I should note, I saw an issue on the tracker today that these filters don't operate on body text in RSS feeds.

Nov 14, 2011 at 7:05 PM

That is exactly what I mean.
I will have a look at the IHtmlFilter and how the Markdown module uses it.

Thanks!

Nov 15, 2011 at 10:24 AM
Edited Nov 15, 2011 at 11:35 AM

I am working on a solution by implementing IHtmlFilter and using a regex that finds all ocurrences of attribute values starting with "~/". However, I need to be able to resolve the found urls using the UrlHelper.Content() method.
Is there an existing context or service from which I could get one? Or do I need to new up a new UrlHelper myself?
The current code looks like this:

[OrchardFeature("RelativePathFilter")]
    public class RelativePathHtmlFilter : IHtmlFilter
    {
        private readonly Regex _regex;
        private readonly UrlHelper _urlHelper;

        public RelativePathHtmlFilter(RequestContext requestContext)
        {
            _regex = new Regex("\"(~/[^\"]*)\"");
            _urlHelper = new UrlHelper(requestContext);
        }

        public string ProcessContent(string text, string flavor)
        {
            return _regex.Replace(text, match => _urlHelper.Content(match.Groups[1].Value));
        }
    }

As you can see, I assume that the IoC container will know how to provide me with a RequestContext, but it needs yet to be tested.

Nov 15, 2011 at 10:31 AM
Edited Nov 15, 2011 at 11:35 AM

As it turns out, the IoC container does indeed provide me with a RequestContext, so the current code works just fine for my case.
Just a warning for anyone who want to use this code: there is a minor flaw in the regex: it assumes that all urls are within double quotes (e.g. href="~/about-us"). So if there is any occurence without double quotes (e.g. href=~/about-us), then the regex matches the rest of the input string.

Nov 15, 2011 at 2:28 PM

This is really useful and something I hadn't realised. I've been using a static method to mock out a UrlHelper whenever I needed one!

Just a thought: is the RequestContext correctly injected even in a background process where no actual request exists? The example I can think of would be an email containing links sent from a scheduled task.

Nov 15, 2011 at 3:01 PM

Good question. I haven't tried it, but my guess would be that the IoC container will still inject an instance of RequestContext. However, depending on how the BackgroundTask engine is implemented, some objects like HttpRequest, will probably be null. On the other hand, since this is Orchard, I wouldn't be too surprised if it simply works :)
What you could do in any case is implement your background task in such a way that it makes an HTTP request to one of your actions, which sends out the email messages. Then you're sure you have a valid RequestContext.

Nov 15, 2011 at 3:05 PM
sfmskywalker wrote:

What you could do in any case is implement your background task in such a way that it makes an HTTP request to one of your actions, which sends out the email messages. Then you're sure you have a valid RequestContext.

Yup, that's how the Warmup module generates its cache. But it might not be hugely efficient (imagine sending out 10,000 emails like that...)

Anyway, I'll have to experiment when the requirement actually comes up :)

Nov 15, 2011 at 3:45 PM
randompete wrote:

 But it might not be hugely efficient (imagine sending out 10,000 emails like that...)

If you would use 10,000 HTTP requests, I'd agree. But what about making a single HTTP request, which then sends out the emails (asynchronously of course)?

randompete wrote:

Anyway, I'll have to experiment when the requirement actually comes up :)

I think a newsletter engine is still missing from the gallery, should you be looking for a requirement... ;)

Nov 15, 2011 at 4:46 PM

If you would use 10,000 HTTP requests, I'd agree. But what about making a single HTTP request, which then sends out the emails (asynchronously of course)?

What if each email needs to be slightly personalised ... yes you could use Tokens substitutions, but running an entire HTML page thru tokens could have performance problems or even errors. Example: I send out a newsletter discussing this new "Orchard.Tokens" feature. In my newsletter I include some examples of using Tokens ... so my HTML body actually needs to contain {Foo.Token} which I don't want substituting ...

I think a newsletter engine is still missing from the gallery, should you be looking for a requirement... ;)

This is exactly one of my requirements, but I don't need it yet. I even mentioned this in: http://orchard.codeplex.com/discussions/279425 under "Email Templating" ... there are other use cases of course, for instance a Discussions module that sends out daily summaries of subscribed threads (which would be completely different for each user of course). There are probably dozens of other possibilities :)