Can I create a Controller that simply returns an image asset?
I would like to route this logic through a controller, whenever a URL such as the following is requeste
Solution 1: To render an image in a view from an image URL
You can create your own extension method:
public static MvcHtmlString Image(this HtmlHelper helper,string imageUrl)
{
string tag = "<img src='{0}'/>";
tag = string.Format(tag,imageUrl);
return MvcHtmlString.Create(tag);
}
Then use it like:
@Html.Image(@Model.ImagePath);
Solution 2: To render image from database
Create a controller method that returns image data like below
public sealed class ImageController : Controller
{
public ActionResult View(string id)
{
var image = _images.LoadImage(id); //Pull image from the database.
if (image == null)
return HttpNotFound();
return File(image.Data, image.Mime);
}
}
And use it in a view like:
@ { Html.RenderAction("View","Image",new {id=@Model.ImageId})}
To use an image rendered from this actionresult in any HTML, use
<img src="http://something.com/image/view?id={imageid}>
Using the release version of MVC, here is what I do:
[AcceptVerbs(HttpVerbs.Get)]
[OutputCache(CacheProfile = "CustomerImages")]
public FileResult Show(int customerId, string imageName)
{
var path = string.Concat(ConfigData.ImagesDirectory, customerId, "\\", imageName);
return new FileStreamResult(new FileStream(path, FileMode.Open), "image/jpeg");
}
I obviously have some application specific stuff in here regarding the path construction, but the returning of the FileStreamResult is nice and simple.
I did some performance testing in regards to this action against your everyday call to the image (bypassing the controller) and the difference between the averages was only about 3 milliseconds (controller avg was 68ms, non-controller was 65ms).
I had tried some of the other methods mentioned in answers here and the performance hit was much more dramatic... several of the solutions responses were as much as 6x the non-controller (other controllers avg 340ms, non-controller 65ms).
Use the base controllers File method.
public ActionResult Image(string id)
{
var dir = Server.MapPath("/Images");
var path = Path.Combine(dir, id + ".jpg"); //validate the path for security or use other means to generate the path.
return base.File(path, "image/jpeg");
}
As a note, this seems to be fairly efficient. I did a test where I requested the image through the controller (http://localhost/MyController/Image/MyImage
) and through the direct URL (http://localhost/Images/MyImage.jpg
) and the results were:
Note: this is the average time of a request. The average was calculated by making thousands of requests on the local machine, so the totals should not include network latency or bandwidth issues.
You can create your own extension and do this way.
public static class ImageResultHelper
{
public static string Image<T>(this HtmlHelper helper, Expression<Action<T>> action, int width, int height)
where T : Controller
{
return ImageResultHelper.Image<T>(helper, action, width, height, "");
}
public static string Image<T>(this HtmlHelper helper, Expression<Action<T>> action, int width, int height, string alt)
where T : Controller
{
var expression = action.Body as MethodCallExpression;
string actionMethodName = string.Empty;
if (expression != null)
{
actionMethodName = expression.Method.Name;
}
string url = new UrlHelper(helper.ViewContext.RequestContext, helper.RouteCollection).Action(actionMethodName, typeof(T).Name.Remove(typeof(T).Name.IndexOf("Controller"))).ToString();
//string url = LinkBuilder.BuildUrlFromExpression<T>(helper.ViewContext.RequestContext, helper.RouteCollection, action);
return string.Format("<img src=\"{0}\" width=\"{1}\" height=\"{2}\" alt=\"{3}\" />", url, width, height, alt);
}
}
public class ImageResult : ActionResult
{
public ImageResult() { }
public Image Image { get; set; }
public ImageFormat ImageFormat { get; set; }
public override void ExecuteResult(ControllerContext context)
{
// verify properties
if (Image == null)
{
throw new ArgumentNullException("Image");
}
if (ImageFormat == null)
{
throw new ArgumentNullException("ImageFormat");
}
// output
context.HttpContext.Response.Clear();
context.HttpContext.Response.ContentType = GetMimeType(ImageFormat);
Image.Save(context.HttpContext.Response.OutputStream, ImageFormat);
}
private static string GetMimeType(ImageFormat imageFormat)
{
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();
return codecs.First(codec => codec.FormatID == imageFormat.Guid).MimeType;
}
}
public ActionResult Index()
{
return new ImageResult { Image = image, ImageFormat = ImageFormat.Jpeg };
}
<%=Html.Image<CapchaController>(c => c.Index(), 120, 30, "Current time")%>
You could use the HttpContext.Response and directly write the content to it (WriteFile() might work for you) and then return ContentResult from your action instead of ActionResult.
Disclaimer: I have not tried this, it's based on looking at the available APIs. :-)
Read the image, convert it to byte[]
, then return a File()
with a content type.
public ActionResult ImageResult(Image image, ImageFormat format, string contentType) {
using (var stream = new MemoryStream())
{
image.Save(stream, format);
return File(stream.ToArray(), contentType);
}
}
}
Here are the usings:
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using Microsoft.AspNetCore.Mvc;