All started with a facebook conversation about a little tweak I found to help me massively with a problem I had in a website that I built for my wife.
I had to explain her the structure of her cattery website (varelvens.de) full of custom built modules mixed with Html-module parts.
I had the idea to hack dnn a little bit and with some research I found a possibility to blend in the module title in the drag bar. Only two additional lines of code in a dnn javascript file and three additional properties in a css class were enough too see a perfect structure in design mode and perfect kitten baby presentations in view mode - Yeah, mission completed!
Those are the tweaks:
in \Resources\Shared\scripts\dnn.dragDrop.js: (ca. line 104)
for (var moduleNo = 0; moduleNo < $modules.length; moduleNo++) {
$module = $($modules[moduleNo]);
mid = getModuleId($module);
moduleName = $module.attr("class").split(' ')[1].replace("DnnModule-", "");
//Add a drag handle
if ($module.find(".dnnDragHint").length === 0) {
$module.prepend("<div class=\"dnnDragHint\"> " + moduleName + " (ID:" + mid + ")</div>");
}
//Add a drag hint
$module.find(".dnnDragHint").dnnHelperTip({
helpContent: settings.dragHintText,
holderId: "ModuleDragToolTip-" + mid
});
}
in \Resources\Shared\stylesheet\dnn.dragDrop.css:
.dnnDragHint {
background-color: black;
color: white;
cursor: move;
font-family: Arial;
font-size: 12px;
height: 22px !important;
outline: 1px dashed #ccc;
}
I loved this nice tweak, so I started a facebook post :
A lot of people commented on this thread and came up with many ideas what should also be shown.... but not in the dragbar (very small modules etc. ...).
So this came in my mind:
Yep, nice design. Must be a popup when hovering over the i-Button.
My first idea was to write a web service method which is called by client site code. This should sample the infos from dnn and create some html, styles etc. and show this html in the popup. But from the design-point this is a nightmare. Different styles have to be considered and in every theme it looks different - a bad choice for people who use dnn as admins and have to work with modules on a daily base.
This rotated a few days in my mind and one morning I had the idea! Showing this as an image when hovering over "info" is really easy to do. All we need is an imagehandler like the "profilepic" from dotnetnuke (creates the user profile photos ). Adding the moduleid and the tabid to the image url should be enough to retrieve all the needed information.
My bbimagehandler on codeplex is working much in the same technology. It bases on an 7 years old codeplex project from Microsoft. It was more a good architectural shell for an image handler than a final product:
see here: https://aspnet.codeplex.com/releases/view/16449 in detail.
Over the years (I started my http://bbimagehandler.codeplex.com project in June 2011) ,I restructured the project and added a lot of gimmics here and there. The original code generates bad rendering results and I added another, a better Quantizer (OctreeQuantizer) to the ImageQuantization. Thanks to the guy who posted this class somewhere in the internet ! (This is not my specialization ;-) )
Inside the imagehandler we have three main parts:
- StartTransform: Is generating the main image (profile pic, image from disk, ...)
- Filtertransform: These transforms change the main image (greyscale, resize etc.)
- ImageQuantization: Mathematical operations for filters
- And the "engine" : ImageHandler main and base classes:
- Reading the parameters,
- Initiating the pipeline
- Adding dependend on "mode" parameter the selected start transformation to the pipeline
- Adding dependend on other parameters the additional filters with their corresponding parameter values to the pipeline
- Running the pipeline and return the resulting image
The project is well structured in the same manner so you can see easy whats going on:
Recently I made a short introduction in the last MVP online meeting and the first questions that came up were:
What about caching ?
As said above,the imagehandler is based on an old but robust framework for image generation in asp.net by the Microsoft guys himself. I think they wrote it an the beginning days of ASP.NET to demonstrate the power of the product. It have had also full Visual Studio Designer mode support by a ton of additional [attributes] in the source code. (I threw them out here because we don't need this in our image engine room).
The imagehandler has full support for server site AND client site caching. Server site caching is done by building a unique hash key over the parameters and save this generated image to a subdirectory in _App_Data. Client site caching is done by sending the appropiate 304 Headers - this works all well out of the box.
Did you implement security ?
What about DDOS scenarios generating tons of images with different sizes that let the cache explode (Shaun himself asked this question, remembering some issues they had with the original profile pic handler in DNN) ? Thats a point I had not thought about, but finally I found a solution; its all in the _IPCount.cs class. On every hit to the imagehandler IPCount retrieves the IP address of the requester and looks for a file in _App_data/_IPCount folder with the same name as this IP address. If found, it opens the file and reads the number in it and compares it to the maxAllowed value. If this border is hit, the imagehandler returns a 403 http status code (IP address rejected), if not the number is incremented by 1 and saved back to the file. If no writing appears into the file for a specified time (LastWriteTime) the file is automatically deleted.
What about other websites that "steale" my images ?
I created a config option to allow or disallow invoking the imagehandler as a "standalone" image (as typing the image url directly into the browser) and additionally to define which domains are allowed to incude images into their website.
What is implemented "Out of the box" ?
Enabling the image handler functionality is done by an additional statement in the system.webServer/handlers section of web.config:
<add name="DnnImageHandler" path="DnnImageHandler.ashx" verb="*" type="DotNetNuke.Services.GeneratedImage.DnnImageHandler, DotNetNuke" preCondition="integratedMode" />
The image handler could be used if you enter the following image url:
http://mydomain.com/dnnimagehandler.ashx?mode=xyz¶m1=value1¶m2=value2...
- profilepic – shows the profile picture to a given userid (if allowed) or a generic “no avatar” image
- file – shows an image by given filename with path or an url or filepath and index (alphabetical order)
- securefile – shows the image by a given fileid (if allowed). If no image format shows filetype icon instead
- placeholder – shows a rectangle with image dimensions or text on it
- modinfo – shows some information about a specific module on a page like title, skin etc.
- Resize – defining the dimension of the resulting image with different resize modes, Adding borders and colored background
- Format – define the resulting format of the generated image (jpg, png…)
- Gamma – adjust the gamma value
- Brightness – adjust the brightness value
- Contrast – adjust the contrast value
- Greyscale – convert to greyscale image
- Invert – convert to negative image
- Rotate + Flip – rotate and / or flip the image
Mode: Profilepic
The profilepic mode shows the profile picture to a given userid (if allowed) or a generic “no avatar” image. The functionality is the same as the old profilepic.ashx handler.
Parameters:
Name |
Meaning |
userid |
ID of user whose profile image should be shown. If omitted, the profile pic of the current user is generated. If no user is logged in or the profile pic is not allowed to show, a generic avatar image is generated |
<img src="/dnnimagehandler.ashx?mode=profilepic&w=120" />
<img src="/dnnimagehandler.ashx?mode=profilepic&userid=2&w=120" />
Mode: File
The file mode shows an image by given filename with path or url or filepath and index (alphabetical order)
Parameters:
Name |
Meaning |
file |
Relative path to the file incl. filename, eg: /Portals/0/images/myimage.gif |
or:
Name |
Meaning |
url |
Url to image (url-encoded), eg: http%3A%2F%2Fwww.indisoftware.de%2FPortals%2F0%2Findilogo.png |
or:
Name |
Meaning |
path |
Relative path to a folder containing images |
Index |
Index of the file in the folder (starting with 1, alphabetical order) |
<img src="/dnnimagehandler.ashx?mode=file&file=Portals/0/Images/icon-qa.png&size=xs" />
<img src="/dnnimagehandler.ashx?mode=file&url=http%3A%2F%2Fwww.indisoftware.de%2FPortals%2F0%2Findilogo.png" />
<img src="/dnnimagehandler.ashx?mode=file&path=Portals/0/Images&index=5" />
Mode: Securefile
The securefile mode shows the image by a given fileid (if allowed). If no image format shows filetype icon instead
Parameters:
Name |
Meaning |
fileid |
Shows the image with the given fileid if the logged user has the view permission for this file. If the file is no image file, an icon is generated instead (from file icons/Sigma/ext%EXTENSION%\_32x32\_standard.png) |
<img src="/dnnimagehandler.ashx?mode=securefile&fileid=100" /> (skin.css)
<img src="/dnnimagehandler.ashx?mode=securefile&fileid=101" /> (1-icn.png)
Mode: Placeholder
The placeholder mode shows a rectangle with image dimensions or text on it
Parameters:
Name |
Meaning |
w |
Width in pixel |
h |
Height in pixel |
color |
Foreground color |
backcolor |
Background color |
text |
Text to display on image. If omitted, dimensions are shown |
<img src="/dnnimagehandler.ashx?mode=placeholder&w=150&h=100&text=plugin-modul" />
<img src="/dnnimagehandler.ashx?mode=placeholder&w=150&h=100&color=yellow&backcolor=green" />
Mode: Modinfo
The modinfo mode shows some information about a specific module on a page like title, skin etc.
Parameters:
Name |
Meaning |
moduleid |
Module id of the module |
Tabid |
Tab Id of the page where the module is located |
<img alt="" src="/dnnimagehandler.ashx?mode=modinfo&moduleid=415&tabid=55" />
Filter:Resize
Defining the dimension of the resulting image with different resize modes, Adding borders and colored background.
Parameters:
Name |
Meaning |
w |
Width of Image |
h |
Height of Image |
size |
Predefined size of image: xxs: 16x16, xs:32x32, s:50x50, l:64x64, xl:128x128, xxl:256x256 |
maxwidth |
Instead of Width this can be used to leave the picture as it is until the width reaches the max value. |
maxheight |
Instead of Height this can be used to leave the picture as it is until the height reaches the max value. |
resizemode |
- fit mode maintains the aspect ratio of the original image while ensuring that the dimensions of the result do not exceed the maximum values for the resize transformation. (Needs width or height parameter)
- fitsquare resizes the image with the given width as its longest side (depending on image direction) and maintains the aspect ratio. The image will be centered in a square area of the chosen background color (Needs width parameter, backcolor optional)
- crop resizes the image and removes parts of it to ensure that the dimensions of the result are exactly as specified by the transformation.(Needs width and height parameter)
- fill resizes the image with the given width or height without maintaing the aspect ratio.
|
backcolor |
color of background or/and border when resizemode is fitsquare or fit |
border |
border width in pixels around the image (added to width / height) when resizemode is fitsquare or fit. |
<img src="/dnnimagehandler.ashx?mode=profilepic&userid=2&size=xl&backcolor=%23F58719&border=10" />
<img src="/dnnimagehandler.ashx?mode=file&file=Portals/0/Images/icon-qa.png&h=128&w=40&resizemode=fill" />
Filter:Gamma
Adjusting the gamma value
Parameters:
Name |
Meaning |
gamma |
Value for gamma adjustment between 0.2 and 5 |
<img src="/dnnimagehandler.ashx?mode=profilepic&userid=2&size=xl&gamma=4"/>
Filter:Brightness
Adjusting the brightness value
Parameters:
Name |
Meaning |
brightness |
Value for brightness adjustment between -255 and +255 |
<img src="/dnnimagehandler.ashx?mode=profilepic&userid=2&size=xl&brightness=128" />
Filter: Contrast
Adjusting the contrast value
Parameters:
Name |
Meaning |
contrast |
Value for contrast adjustment between -100 and +100 |
<img src="/dnnimagehandler.ashx?mode=profilepic&userid=2&size=xl&contrast=75" />
Filter: Greyscale
Convert image to greyscale
Parameters:
Name |
Meaning |
greyscale |
If present, image is converted to greyscale (needs dummy value, e.g “1”) |
<img src="/dnnimagehandler.ashx?mode=profilepic&userid=2&size=xl&greyscale=1" />
Filter:Invert
Converts an image to its negative representation
Parameters:
Name |
Meaning |
invert |
If present, image is inverted (needs dummy value, e.g “1”) |
<img src="/dnnimagehandler.ashx?mode=profilepic&userid=2&size=xl&invert=1" />
Filter: Rotateflip
Rotates and / or flips the image
Parameters:
Name |
Meaning |
rotateflip |
- RotateNoneFlipNone: Specifies no clockwise rotation and no flipping.
- Rotate90FlipNone: Specifies a 90-degree clockwise rotation without flipping.
- Rotate180FlipNone: Specifies a 180-degree clockwise rotation without flipping.
- Rotate270FlipNone: Specifies a 270-degree clockwise rotation without flipping.
- RotateNoneFlipX: Specifies no clockwise rotation followed by a horizontal flip.
- Rotate90FlipX: Specifies a 90-degree clockwise rotation followed by a horizontal flip.
- Rotate180FlipX: Specifies a 180-degree clockwise rotation followed by a horizontal flip.
- Rotate270FlipX: Specifies a 270-degree clockwise rotation followed by a horizontal flip.
- RotateNoneFlipY: Specifies no clockwise rotation followed by a vertical flip.
- Rotate90FlipY: Specifies a 90-degree clockwise rotation followed by a vertical flip.
- Rotate180FlipY: Specifies a 180-degree clockwise rotation followed by a vertical flip.
- Rotate270FlipY: Specifies a 270-degree clockwise rotation followed by a vertical flip.
- RotateNoneFlipXY: Specifies no clockwise rotation followed by a horizontal and vertical flip.
- Rotate90FlipXY: Specifies a 90-degree clockwise rotation followed by a horizontal and vertical flip.
- Rotate180FlipXY: Specifies a 180-degree clockwise rotation followed by a horizontal and vertical flip.
- Rotate270FlipXY: Specifies a 270-degree clockwise rotation followed by a horizontal and vertical flip.
|
<img src="/dnnimagehandler.ashx?mode=profilepic&userid=2&size=xl&rotateflip=Rotate90FlipY" />
<img src="/dnnimagehandler.ashx?mode=profilepic&userid=2&size=xl&rotateflip=RotateNoneFlipY" />
<img src="/dnnimagehandler.ashx?mode=profilepic&userid=2&size=xl&rotateflip=RotateNoneFlipX" />
Configuration
There are a few options for the imagehandler that could be set up via configuration in web.config. You could set your options in appSettings-section:
<add key="DnnImageHandler" value="EnableIpCount=false;AllowStandalone=false;LogSecurity=true;AllowedDomains=mydomain.com,otherdomain.net;EnableServerCache=true;EnableClientCache=true;" /></font>
The following options are available :
Name |
Meaning |
EnableIpCount |
Default: true. If true, every generation of an image will be counted. If IPCountMax is hit no more images are generated until IPCountPurgeInterval is up. Physically there is a file in _App_Data/_ipcount for every IP containing the number of hits. When IPCountPurgeInterval is over, this file is deleted. |
IpCountMax |
Default: 200. Max number of hits from an IP allowed in defined time span IPCountPurgeInterval |
IPCountPurgeInterval |
Default: 300. Seconds for measuring the hits from one IP. |
EnableServerCache |
Default: true. Enables the server side caching of images in folder _App_Data/_images |
ServerCacheExpiration |
Default: 180 Seconds until the image is deleted from image cache folder and generated again. |
EnableClientCache |
Default: true. Enables the client side caching of images (answering with 304 http response until ClientCacheExpiration is reached) |
ClientCacheExpiration |
Default: 60 Seconds caching the image on client site. |
AllowStandAlone |
Default: false. If true, Images could be shown standalone via image url. If false, only images integrated in a web page (referrer is not null) are shown, others return 403 (forbidden) http status code. |
LogSecurity |
Default: false. If true, hitting IPCountMax or AllowStandalone bounderies produce a eventlog entry |
AllowedDomains |
Default: empty string. By default, all websites are allowed as referrer. Include here a comma-separated list of domains to restrict the embedding of your images to pages from these domains (e.g. mydomain.com,myotherdomain.net ) |
ImageCompression |
Default: 95. Set the image generation quality. Range is from 0 to 100. |
But: Thats not all! The DnnImageHandler is extensible by third party developers!
Additional Mode Extensions
If a developer creates a dll containing a class (for example: PercentTransform) based on ImageTransform class with two public property named “percentage” and "color":
using System.ComponentModel;
using System.Data;
using System.Data.SqlClient;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Drawing.Text;
namespace Dotnetnuke.Web.GeneratedImage.Transform
{
public class ImagePercentTransform : ImageTransform
{
/// <summary>
/// Sets the percentage value for the radial indicator
/// </summary>
public int Percentage { get; set; }
/// <summary>
/// Sets the Color of the indicator element
/// </summary>
public Color Color { get; set; }
public override string UniqueString
{
get { return base.UniqueString + this.Percentage.ToString() + "-" + this .Color.ToString(); }
}
public ImagePercentageTransform()
{
InterpolationMode = InterpolationMode.HighQualityBicubic;
SmoothingMode = SmoothingMode.HighQuality;
PixelOffsetMode = PixelOffsetMode.Default;
CompositingQuality = CompositingQuality.HighSpeed;
}
public override Image ProcessImage(Image image)
{
Bitmap bitmap = new Bitmap( 100, 100);
using (Graphics objGraphics = Graphics.FromImage(bitmap))
{
// Initialize graphics
objGraphics.Clear(Color.White);
objGraphics.SmoothingMode = SmoothingMode.AntiAlias;
objGraphics.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
// Fill pie
// Degrees are taken clockwise, 0 is parallel with x
// For sweep angle we must convert percent to degrees (90/25 = 18/5)
float startAngle = - 90.0F;
float sweepAngle = ( 18.0F/ 5)*Percentage;
Rectangle rectangle = new Rectangle( 5, 5, 90, 90);
Brush colorBrush = new SolidBrush(Color);
objGraphics.FillPie(colorBrush, rectangle, startAngle, sweepAngle);
// Fill inner circle with white
rectangle = new Rectangle( 20, 20, 60, 60);
objGraphics.FillEllipse(Brushes.White, rectangle);
// Draw circles
rectangle = new Rectangle( 5, 5, 90, 90);
objGraphics.DrawEllipse(Pens.LightGray, rectangle);
rectangle = new Rectangle( 20, 20, 60, 60);
objGraphics.DrawEllipse(Pens.LightGray, rectangle);
// Draw text on image
// Use rectangle for text and align text to center of rectangle
var font = new Font( "Arial" , 13 , FontStyle.Bold);
StringFormat stringFormat = new StringFormat();
stringFormat.Alignment = StringAlignment.Center;
stringFormat.LineAlignment = StringAlignment.Center;
rectangle = new Rectangle( 20, 40, 62, 20);
objGraphics.DrawString(Percentage + "%" , font, Brushes.DarkGray, rectangle, stringFormat);
// Save indicator to file
objGraphics.Flush();
}
return (Image) bitmap;
}
}
}
and copy this dll to the DNN bin folder and also adds an entry to the AppSettings section of web.config like this:
<add key="DnnImageHandler.Percent" value="MyNamespace.PercentTransform, NameOfMyDll" />
he is able to invoke the imagehandler with an URL syntax like this:
http://mydomain.com/DnnImagehandler.ashx?mode=percent&percentage=30&color=green
So this is a really cool extension point where developers can gain leverage from the imagehandler foundation. This is for example one extension I built for a renting module
The possibilities are endless ! Think of watermarking images in an ecommerce application or adding fancy borders to images.
So, what do you think ? Should this go into the core ? Pull request is pending! And regarding the initial idea: I promise to work on it later, even if the imagehandler will be denied. But I really believe that this one could be a really powerfull image engine for DNN and a unique feature point in competition with other CMS Systems.