Separate thumbnail/resize creator module

Topics: Announcements, General, Writing modules
Nov 13, 2011 at 2:24 PM

Is there already a separate module for resizing images on the fly using a service or a url like:

<img src=/ImageResizer.ashx?image=beautiful.jpg&width=100" />

I heart that the Media Garden Images will include a image resize functionality but i would be nice if we have a generic separate module so you can use this also for fields like imagepickerfield, etc.

If it not exist yet i will try to build such a module because for me this is the only missing crucial piece for me in Orchard to fully start building all my sites in Orchard.

 

Nov 13, 2011 at 4:31 PM
Edited Nov 14, 2011 at 7:39 AM

ok i've begon to build a module for this and will publish it this week to the gallery. I'll also create a codeplex project for this so others can participate

Nov 14, 2011 at 10:47 AM

Znowman: Media Garden already has this and it's working great - see the MediaGarden.ImageResizing module :)  It works for any media garden images, there's probably a fairly easy way to leverage it for Orchard's Image shape as well.

Nov 14, 2011 at 2:59 PM

yes i saw that you integrated this in the Media Garden module and that's very cool! But i think is nice if this functionality is also available as a standalone module which can be used simply by url. In my current project i don't use the media garden but use other alternatives (to bad not using it) so i'm using the standalone version. Because the first version is already finished i'm gonna upload it to the gallery for the people who want to use it. For the users who are using the Media Garden module better use the built in version (will create comment in module).

Nov 14, 2011 at 4:11 PM
Edited Nov 14, 2011 at 4:11 PM

When I started the integration, I wanted to write a separate module that simply wrapped the ImageResizing API. Then, I wanted to make a MediaGarden.ImageResizing module that integrated that API into Media Garden.

This would of course still be possible, the problem is dependencies. It's already causing so many complications for people trying to install my modules because I have dependencies on Science Project (and external dependencies are starting to creep into some of those projects, e.g. I have a Contrib.Cache dependency in one project so I can trigger cache eviction). So in this case it seemed easier to just keep things contained.

It's a real shame that Module Gallery limitations actually make it hard to build properly modular functionality!

Just a side note: the on-demand resizing (i.e. ImageResizer.ashx) is something I'm a little afraid of. As far as I can see it presents a Denial of Service opportunity. I could flood your server with requests for thousands of images at different sizes; blocking CPU, memory, HDD, whatever (further, Image Resizing has a feature of limiting the number of files cached on-disk, so I could theoretically keep hammering it forever even with a width/height limit...) Actually I just contacted the ImageResizing developer about this, maybe I'm just paranoid and it's already being handled ;) So that's why I implemented things such that there has to be a server-side call for an image at specific width and height in order for it to get generated; that works nicely with Media Garden but would be harder with e.g. TinyMCE integration.

Nov 14, 2011 at 7:49 PM

yea that's really a shame. you know it there are plans to improve that?

You've got a point there that this could be a opportunity for denial of services attacks but on the other hand i see other CMS systems do the same. For example: the most Umbraco websites uses ImageGen, which does basicly the same thing. If it's not already included in the resizing library i'll have to build my self a protection.

 

Nov 14, 2011 at 8:01 PM

Bertrand has stated it won't be in Orchard next (but it's something I *really* hope will be considered immediately after that, I'm seeing a lot of modules hitting dependency nightmares and about half the support requests / threads for my modules have been related to it. In my opinion it stifles good module development if we can't easily build on each others' work and have to go to such lengths to inform users about dependencies...)

I hope Nathaniel doesn't mind me quoting his response on the DoS issue:

The image resizer does have default size limits (3200x3200), but there are
hundreds of ways to defeat caching. In my opinion, DoS attacks are
quite feasible on any ASP.NET website whether or not it uses the
ImageResizer. To quote Scott
Hanselman<http://www.hanselman.com/blog/NuGetPackageOfWeek11ImageResizerEnablesCleanClearImageResizingInASPNET.aspx&gt;on
the topic, it's a problem that should be handled at a different
network
layer. You can always do server-local DOS prevention, but it won't be as
efficient as firewall-level filtering.

Which seems reasonable.

Coordinator
Nov 14, 2011 at 8:09 PM

Let's start a thread on what people want for 1.5. I've heard a few things that sound reasonable already.

There: http://orchard.codeplex.com/discussions/279425

Nov 14, 2011 at 9:30 PM

update:the module is finished (first version)

I tried to create a account on http://orchardproject.net/gallery/Contribute/Index but unfortunately i didn't receive a confirmation mail so i have to wait till tomorrow to see if i received him. So hopefully i can upload it tomorrow to the gallery

Nov 15, 2011 at 9:53 AM
Edited Nov 15, 2011 at 10:40 AM

ok i created a codeplex for it so for the one who want to test it before i'll upload it to the module gallery here is the link (try download source):

http://orchardimageresizer.codeplex.com

let me here what you think :)

Nov 15, 2011 at 3:17 PM

I've had a look through the source and here are my comments:

1) Integration with Orchard. It seems like the way to use this module is quite manual; if I wanted to resize an image from the Media Picker, I'd have to figure out the correct Url. Also, users would have to convert any existing .cshtml templates to use the call to the HTML helper.

There are a few ways you could consider making this more automatic and friendly;

a) Intercept the "Image" shape (which already exists in Orchard core) - you can do this by implementing IShapeTableProvider. Then you can replace the image's url with a resize url based on the width/height; you could even support optional things like cropmode, scalemode, etc., because shapes are dynamic. So anyone who has already used the Image shape then knows straight away that it will now resize images, and they only have to learn about the new options, if they even need to.

b) Override parts of the Media and Media Picker modules. This means users can generate img tags with the resizer url automatically. Of course, Media Picker also integrates with TinyMCE so then you get it in the body editor as well.

c) IHtmlFilter. This interface can be used to perform processing on BodyPart before it gets rendered; you could actually perform a regexp here to find img tags in the body and convert them to resized urls, so user's existing content would take advantage of resizing for free without any editing!

2) Service pattern. Right now you perform the resizing work in a static method ImageResizeHelper.ResizeImage(...). This limits you because a) you can't inject any dependencies from Orchard and b) other users couldn't replace the handler with their own version (import for testing and modularity). The way to do this in Orchard is to first define an interface, "IImageResizeService" - and that inherits from IDependency. Now you create a default implementation of that service, "ImageResizeService". Finally you inject that dependency into your controller so you have access to the service where you need it (and you could also, for example, inject that same dependency into the IShapeTableProvider that I mentioned in 1a, and other users of your module can inject the dependency into their services instead of calling the static method). What advantage would this give you? Well, other than static methods generally being "not the way to do things" in an IoC application, consider this code from your ImageResizeHelper:

private static readonly string CacheFolderPath = HttpContext.Current.Server.MapPath("~/Modules/So.ImageResizer/Cache"); //maybe configurable in admin?
Unfortunately, while your code is a static class, you can't access site settings. So by implementing the service pattern you will then be able to inject any core services of Orchard, which means you can pull settings out of the Site object. There are other Orchard services you might want to call on; virtual paths / file system, media service, file locking, etc.

3) ImageResizer's HttpModule. This comes after some lengthy email discussions with Nathaniel who wrote ImageResizer. The "default" way to configure things is to modify Web.config to include the HttpModule, this gives you the usual ImageResizer.ashx handler. You've worked around this manually, and in my implementation I did a very similar thing by manually calling the API to generate images when I needed them. What I've learned from Nathaniel, however, is there are many other things that the HttpModule does! It handles file locking, manages cache directory sizes, improves performance by letting IIS serve the file directly, and other things. I don't have an optimal solution for this, but it is possible to insert things into the HTTP pipeline in ways other than Web.config (although I don't exactly know how this would play with multitenancy). Nathaniel has talked about a way to leverage more of his API without including the HttpModule but it's limited. It's something I want to put more thought into and have further dialogue with Nathaniel about.

Anyway, I hope I don't sound critical. What I'd like is to enable Media Garden to have a dependency on this instead of being a separate implementation - otherwise we'd start seeing problems with conflicting dll versions if someone did want to install both - and whilst module dependencies are frustrating, hopefully that won't be the case forever. But I think these points are the ways to maximise what this module can do.

Nov 15, 2011 at 4:36 PM

1: ok thanks you for your comments and advise. No you don't sound to critical and i think you have great ideas arround this concept. I haven't done enough like intercepting shapes,etc but that's sounds very good!

2: about the service pattern. I was in doubt to create a service for this functionality but i thought this would be overkill for such a small functionality. After you comment i think it's indeed better to create this in a IDependency service.

3: I was indeed affraid that my wrapper arround the library is a little bit unnecessary because you can directly contact the  library with the ImageResizer.ashx handler.

My conclusion based on your comment:

- 1 Use the ImageResizer.ashx handler instead of own wrapper arround it. Possible to include this in the web.config of the module?

- 2 Keep the html helper for the ones who still want to use this. and result the imageresizer.ashx?

- 3 Create a IHTmlFilter and or Override parts of the Media and Media Picker modules and or Intercept the "Image" shape

I think that especially nr3 of the conclusion is/are fantastic ideas and really make use of the power orchard.

I'm little bit affraid that when i'll intergrate all that functionalty, that were doing exactly the same and that would be a waste of time.

What are your plans around this and do you have plans to integrate (especially nr 3) in to your media module?

Nov 15, 2011 at 5:35 PM

2: I find myself creating an IDependency even for very small and trivial things; it's almost always more useful in the long run and it's never hard to do. In fact, very few classes I write are not an IDependency of some sort!

Conclusions;

- 1: The problem is figuring out how to get the ashx handler working! The module's Web.config won't solve this - it needs to be in the root Web.config (i.e. Orchard.Web's) - so Web.config is completely out of the question unless you want to ask users to manually modify it.

I can see two options;

a) Finding a successful way to programmatically modify the Http pipeline. There are some answers here: http://stackoverflow.com/questions/239802/programmatically-register-httpmodules-at-runtime - and there's WebActivator to consider - but how any of these methods might work in the context of Orchard is questionable, and the additional problem that it could bypass any per-tenant settings - i.e. the handler would be enabled for *all* tenants with no choice in the matter.

b) If the above isn't possible for any reason; then have it so that by default things will work thru the controller, and there is an optional feature or setting, so if the user wants to manually modify Web.config then they can enable support for the ashx handler and get improved features and performance (will require some clever use of IDependency so you can plug in different behaviours for generating urls depending on which handler is available)

- 3: I think supporting all three (IHtmlFilter, Media/Media Picker, Image shape) would be a fantastic way to go, because they all solve slightly different problems. IHtmlFilter fixes existing content. Media/Media Picker allows you to expose more options when someone writes new content (specifying crop and scale mode, even ImageResizer plugins). And the Image shape gets used from .cshtml files so it's completely separate to Media.

What are your plans around this and do you have plans to integrate (especially nr 3) in to your media module?

This is why I gave such detailed feedback. These are all things that I'd eventually have added in my own integration... but I'm mainly focusing on Media Garden and that's enough for my immediate projects. So in the long run it's better to have a separate module for ImageResizing, and then I can just have a dependency on your module (actually, I think I don't even need a dependency ... if I just use the Image shape then with all the above, your module should fix those images for me...) So yeah, I won't do any further integration on my side, so we won't be duplicating any work. It's much better to have just one module without any dependencies that does this stuff in a way that's easy for anything else to use. Of course if you want a hand with anything I've mentioned I'm happy to help, it's saving me time in the long run :)

Final comment:

4. Search Engine Optimisation. This is something that's a problem with ImageResizing.ashx anyway; but I don't know if you're aware how beneficial image filenames can be for SEO. True story: a friend had a fairly small Wordpress web/graphic design site. They were on something like page 50 in Google for "web design". On the other hand, if you did an image search for "business card", his card appeared on the first page, internationally. That's a slightly silly example; but Google and other search engines are certainly generally known to take image filenames into account when building up a keyword profile of your page.

I'm not sure the best way to handle this. For Media Garden, it generates caches in a subfolder based on the name of the image, so you get urls like: /Media/Default/photo-of-me/400x300.jpg - this certainly isn't optimal because again the keywords aren't in the filename. I was planning to use a Route to manipulate this, so you'd get e.g. /images/photo-of-me-400x300.jpg

Since you already have the route it might be pretty easy to transform the urls to something like this?

Nov 15, 2011 at 5:38 PM

Additional comment on that point 4 ... that mapping will also be possible with the Alias module, although then you need to create an Alias record for each version of an image.

Dec 7, 2011 at 11:02 AM

i have thought the ideas over and checked the licenses of the image resize library:

The problem is that when i'm using the http module directly you don't have the possibility of caching the resized images on the server, unless i or the user of my module buys a license at http://imageresizing.net/licenses and enables the caching module in the web.config

so i think it's cheeper to use the wrapper instead so it's stays free and u can use the caching mechanism which i built in. Off course my code has to be refactored.

what's your opinion?

Dec 7, 2011 at 3:14 PM

I think it's the simplest way, unless a lot changes on ImageResizing. However I thought the advanced cache you need the license for is a bit different - an in-memory cache for the images requested most often; I think an on-disk cache is always there in the free version. But calls to File.Exists() and especially reading files aren't cheap, but you'd only really need to think about this under heavy load.

Another thing the module handles nicely is file locking, which again could be a problem under load.

Still, both these systems can be implemented on the Orchard side, and could even be done as separate modules to provide services to other features.

Jan 5, 2012 at 2:37 PM
Edited Jan 5, 2012 at 4:20 PM

I found a little bit of time today to work at this project and i've got an update:

I've managed to get the IHtmlFilter working. So now all the images which are defined in a Body part will automatically resized. This makes sure that editors don't have to bother about the image size! The next thing i'm gonna do is implement Image Shape interception. I'll have to refactor here and there but i'll commit to the repository afterwards. I'll let you know.

Jan 5, 2012 at 7:06 PM

Great stuff! Overriding the Image shape shouldn't be too tricky by comparison ;)  ... I wonder, what is the story currently for customising Resizer options/defaults? Could you add some event hooks so that e.g. plugins could be added and other options set?

Jan 6, 2012 at 8:24 AM

Well i have build an option in the back-end settings to choose whether you want the fast option by manually edit the web.config (so using the .net imageresizer library) or use the default way (my own wrapper around this library). No currently there are not events hooks or that kind of things. Though i do have overloads on the methods. Can you give some advice on how you would add these events hooks?

Also i'm trying to find the Image core shape but i can't find it. Are you sure that there is such a shape? or do you mean the image field? Can you point me to some direction?

Coordinator
Jan 6, 2012 at 8:50 AM

I don't think there's one in core. ImageField and MediaPickerField may have one?

Jan 6, 2012 at 9:07 AM

Don't think that either. Is it on the other hand possible to suppress for example the MvcHtmlString Image extensions and "override" this one with my own custom version which uses the resize functionality? I think this is possible if i copy the total class HtmlHelperExtensions.cs en change that method but i don't like to override all those methods. Do you guys have some ideas about how i can resize all the other (body part html output already working) outputted images by overriding or intercepting code?

Coordinator
Jan 6, 2012 at 9:16 AM

By intercepting all shapes maybe in a shape table provider? there are a vvariety of other applications of this idea, such as shape-level caching. It should work.

Jan 6, 2012 at 9:49 AM

Well now i intercepted the "Widget" and "Content" shape and replacing all src attributes of the image url's with the resized image url. By using this the IHtmlFilter for the bodypart becomes unnecessary,right?

Jan 6, 2012 at 1:05 PM
Edited Jan 9, 2012 at 11:54 AM

Update: I've just committed the latest changes of the module to Codeplex. Not available yet as module because it's still in development phase.

http://orchardimageresizer.codeplex.com/SourceControl/list/changesets

Try it and let me hear what you think.

Jan 6, 2012 at 1:53 PM

Interesting ... there's an Image shape used in Orchard.Experimental.Controllers.HomeController line #99. It's not used anywhere else. Maybe there used to be one and it was removed?

Coordinator
Jan 6, 2012 at 4:42 PM

Well, Experimental is... experimental.

Jan 6, 2012 at 4:48 PM

At some point I thought I saw an Image shape used elsewhere ... but maybe it was in dev branch or something, it was quite a while ago now!

Jan 9, 2012 at 11:54 AM

has anybody already tried it?

Feb 22, 2012 at 5:32 AM

Hi, this is Nathanael - I'm interested in making sure Orchard is fully supported, while at the same time supporting the ImageResizer plugins. 

The InterceptModule could easily be subclassed to perform Config lookup instead of using Config.Current, in fact, you'd only need to override the .conf property, as everything funnels through there. I could even place the Config instance lookup into an event, if that would be easier. 

Coordinator
Feb 22, 2012 at 5:52 AM

One thing is that HttpModules are difficult in Orchard because of the requirement to centrally declare them. This is a problem because modules can't modify web.config or global.asax, and things need to be multi-tenant-friendly.

Feb 22, 2012 at 5:58 AM

Is there another way to hook into these two events?

* PostAuthorizeRequest
PreSendRequestHeaders
If so, no HttpModule would be needed. 
If not, tenants could opt-in to enabling the HttpModule for their request, as long as there's a way to register the HttpModule. Requiring a web.config change is a last resort, but still an option, I guess.
Is there an Orchard API to find out which tenant is active and get/store a object containing configuration data?

Feb 22, 2012 at 12:25 PM

The MVC IAuthorizationFilter / IActionFilter / IResultFilter are the closest analogs of those events. And of course Routing and Controllers themselves. Perhaps you can describe in more detail exactly what you need to do at what points? (I have a general idea of course but better to hear it again, and I'm not up to speed on any recent changes you might have made to your API...)

Feb 22, 2012 at 1:47 PM
Znowman wrote:

Update: I've just committed the latest changes of the module to Codeplex. Not available yet as module because it's still in development phase.

http://orchardimageresizer.codeplex.com/SourceControl/list/changesets

Try it and let me hear what you think.

FYI; reading through this topic I'm not with you guys on the different techniques mentioned above (yet) as I'm just starting in Orchard. However, I'm looking forward to the results of this.

I'll give the code a try and see how it pans out for me.  

Imho when you are a person using Orchard from the source code you probably won't mind changing the web.config for the advanced features. If you work purely from the admin then you could settle with the basic version.

Feb 22, 2012 at 6:52 PM

I just posted a page describing how InterceptModule works

http://imageresizing.net/docs/interceptmodule

Let me know if that helps. 

While you could implement a subset using MVC routes & controllers, it would really limit a lot of things and break most plugins. Making the HttpModule extensible enough so that Orchard-specific smarts can get plugged in makes more sense to me.