ASP.NET MVC slow image loading through MVC framework? - c#

On some photobook page i want to show appr 20 thumbnails. These thumbnails are programatically loaded from a database. those thumbnails are already resized. When i show them the images load kinda slow. some take 0.5 seconds to load some wait for 2 secons. The database doesn't matter because when i remove the database layer, the performance issue still exists.When i load the same images directly with html the problem the images do load immediately.
Is loading images/files through the mvc framework slow or am i missing something?
This goes too slow
//in html
<img src='/File/Image.jpg' border='0'>
//in controller
public FileResult File(string ID)
{
//database connection removed, just show a pic
byte[] imageFile = System.IO.File.ReadAllBytes(ID);
return new FileContentResult(imageFile,"image/pjpeg");
}
This goes immediately
<img src='/Content/Images/Image.jpg' border='0'>

I had the same issue. I'm using MVC 3. After pulling my hair out, what I discovered is that once you use Session State in your web app, dynamic image loading seems to get clogged, due to the pounding session requests. To fix this, I decorated my controller with:
[SessionState(System.Web.SessionState.SessionStateBehavior.Disabled)]
This disabled the session state for my Photos controller, and the speed returned. If you are using an earlier version of MVC, you'll need to jump through some hoops and create a Controller/Controller factory to do this. See How can I disable session state in ASP.NET MVC?
Hope this helps!

You are adding processing overhead by exposing the image via MVC. When you directly link to an image, it is handled automatically by IIS, rather than the MVC pipeline, so you skip a lot of overhead.
Also, by loading into a byte array, you're loading the full image from disk into memory and then streaming it out, rather than just streaming directly from disk.
You might get slightly better performance with this:
[OutputCache(Duration=60, VaryByParam="*")]
public FileResult File(string ID)
{
string pathToFile;
// Figure out file path based on ID
return File(pathToFile, "image/jpeg");
}
But it's not going to be quite as fast as skipping MVC altogether for static files.
If the above fixes it for you, you'll probably want to mess around with the caching parameters.

Related

ASP.NET MVC 5 TempData and Session gets cleared on RedirectToAction [duplicate]

I have an interesting problem with the TempData object not passing values to another controller.
I set TempData["Enroll"] in the Enroll Controller's HttpPost method to an Enroll Model. I then read the TempData["Enroll"] object in the Register Controller's HttpGet method, but is empty/null.
I need to persist all of this data across 3 controllers.
Any thoughts?
Here is a code Snippet
//EnrollController.cs
[HttpPost]
public ActionResult Index(EnrollModel model)
{
// ...
TempData["EnrollModel"] = model;
return RedirectToAction("Index", "Register");
}
// RegisterController.cs
public ActionResult Index(string type)
{
RegisterModel model = new RegisterModel();
EnrollModel enrollModel = TempData["EnrollModel"] as EnrollModel;
model.ClientType = enrollModel.ClientType;
// ...
}
I've had an issue where TempData got lost during the redirect on my local machine.
I've checked web.config sessionState Setting which was InProc and therefore no problem.
It turned out that I got another setting in web.config, which was taken from production system. It looked like this:
<httpCookies requireSSL="true" />
After turning the requireSSL to false TempData workes fine.
I had the same problem today.
In this link some guys explain that RedirectAction method returns a HTTP 302 status to the browser, which causes the browser to make a new request and clear the temp, but I tried returning HTTP methods 303 (which is what the RedirectAction should be returning) and 307 also, and it didn't solve anything.
The only way of fixing the issue of TempData in my case was changing the sessionState directive of web.config to use StateServer instead of the default InProc. i.e:
<system.web>
<sessionState mode="StateServer" cookieless="AutoDetect" timeout="30" stateConnectionString="tcpip=localhost:42424"></sessionState>
...
</system.web>
I figured this out when reading this Greg Shackles' article, where he explains how TempData works and build a custom TempDataProvider, which rely on MongoDB database instead of session like the default one.
Hope that my 4 hours researching helps someone to not waste their time.
I have come across these sorts of limitations with TempData before. I found it unrealiable and sporadic at best.
You need to consider what you are trying to achieve. If you do need to store data, in practice the best place to do this is in a db (or store of sorts) it might seem a bit overkill but that is their purpose.
Two other points:
Someone can hit your RegisterController Index method without going to the other before, in which case your code would break.
If you are doing a multiple wizard style process, why not store the data in its partial state in the db, and complete the process only on the last screen? In this way no matter, where they stop/start or pick it up again, you will always know where they are in the process.
Save your results to a cache or db or pass in as posts/querystrings between your controllers. TempData is cleared by several things including a worker process reset which could surely happen between steps.
In addition your code above could get a null ref exception:
EnrollModel enrollModel = TempData["EnrollModel"] as EnrollModel;
if(enrollModel==null)
{
//handle this model being null
}
model.ClientType = enrollModel.ClientType;
Fixing your issue as you have it above though is tough without seeing all code and knowing if there is anything else that may/may not refer to it.

Session gets lost when enabling OutputCache

I have a (working) MVC-application that uses Session properties on multiple parts:
return httpContext.Session[SPContextKey] as SharePointAcsContext;
(ignore that this is sharepoint; This problem isn't be SP-specific)
This works fine until I try to enable Outputcaching:
[OutputCache (Duration =600)]
public ActionResult Select() {
DoSelect();
}
When the content is cached, httpContext.Session becomes NULL.
Is there a way to keep the Session data and also use caching?
I found the solution myself. It took a while until I came to the conclusion that - if the data is cached - there shouldn't be any individual code at all that is run. Cause that should be the main purpose of the cache: Don't run any code when the data is cashed.
That led me to the conclusion that the code causing the problem must be run before the cache. And so the "bad boy" was easy to find. Another attribute (in this case an AuthorizeAttribute) that is before the OutputCache-Attribute in the code is still run when caching applies but cannot access the Session:
[Route("{id}")]
[UserAuth(Roles =Directory.GroupUser)]
[JsonException]
[OutputCache(Duration = 600)]
public ActionResult Select()
{
DoSelect();
}
Putting the UserAuth-Attribute BELOW the OutputCache-Attribute solved the problem

Output caching, VaryByParam/VaryByCustom

OK, there seem to be no end to the number of articles that sing the praises of output caching and how much it will speed up your website. I have now read hundreds of articles and Q&As on the topic, but I still can't seem to make it work. (I think output caching might be stealing my soul)
My requirements are pretty simple, I have 3 pages that I would like to cache based on parameters: Home, Results, and Details. I also have a small area of the page that needs to be varied by user. I also need to cache to a central repository and I have chosen redis to hold my data. I should also mention that this is still an old web forms app.
My original approach was to attempt to supply my own custom string using the "VaryByCustom" option. The Microsoft page seems to make this look simple:
https://msdn.microsoft.com/en-us/library/5ecf4420.aspx
This requires placing an overridden method "GetVaryByCustomString" in the global.asax file. The problem is that most of the examples show using variables from HttpContext that are always available (Browser, Browser Version etc.) and even though Microsoft and others seem to suggest this as the preferred "cache by custom string" method I can't find any working examples that allow the page to define the string. I have seen a few examples of people using session and claiming that it works (session is null for me) and context.user (some people say that user is null) but I don't see any practical way to deliver a string except by using Response.Cache.SetVaryByCustom("my_custom_string"). After a day of strugling with implementation in the main project I decided to build an isolated project for testing/proof of concept. In this project I was able to get my custom string working as long as I passed it as the string to SetVaryByCustom. The problem is that this doesn't really match any examples I've seen. The examples show "SomeKey" with "SomeValue" being returned by GetVaryByCustomString. What worked for me was essentially "SomeValue" with "SomeValue". Here was my code:
public override string GetVaryByCustomString(HttpContext context, string arg)
{
if (arg != null)
{
return arg;
}
return base.GetVaryByCustomString(context, arg);
}
This worked the first time on the page, after setting the value of a dropdown which created a postback, but never after that. So the "OnselectedItemChanged" fired once and created a new set of cache entries based on the selection, but it never fired again. I tried modifying just about every cache parameter I could to make it work but no matter what series of settings I attempted (set cache declaratively, set caching in code, tried various combinations of VaryByParams, adding location, etc) I was never successful.
After attempting for 2 days to get this to work I decided it was time for a different approach. I have gone back to a more traditional/accepted approach.
I created 3 cache profiles in my web.config:
<caching>
<cache disableExpiration="false" />
<outputCache defaultProvider="RedisOutputCacheProvider" enableOutputCache="true" enableFragmentCache="true" sendCacheControlHeader="true">
<providers>
<add name="RedisOutputCacheProvider"
type="LAC.OutputCacheProvider, LAC"
connectionString="192.168.XX.XX:6379,connectTimeout=10000"
dbNumber="5"
keyPrefix="LAC:OutputCache:" />
</providers>
</outputCache>
<outputCacheSettings>
<outputCacheProfiles>
<add name="Home" varyByParam="*" varyByCustom="user" duration="86400"/>
<add name="Results" varyByParam="*" varyByCustom="user" duration="7200" />
<add name="Details" varyByParam="*" varyByCustom="user" duration="7200"/>
</outputCacheProfiles>
</outputCacheSettings>
</caching>
So each page has a custom vary by user string that should help me cache a new page for an authenticated user vs an anonymous one.
Then I added a GetVaryByCustomString that looks like:
public override string GetVaryByCustomString(HttpContext context, string arg)
{
switch (arg)
{
case "user":
var identity = context.User.Identity.Name != "" ? context.User.Identity.Name : "Anonymous";
return string.Format("IsAuthenticated={0},Identity={1}", context.Request.IsAuthenticated, identity);
}
return string.Empty;
}
I am as close as I have ever been to having this working but it's still not 100%. The Home page works fine, the details page works fine, but the results page never caches. The results page has a filter containing 12 different dropdown lists. So it needs to store version of the page based on selections. I added a Timestamp to the top of all of my pages so I could see if the page was caching. The time on the results page changes every time I hit control-F5, which is not true on the other 2 pages. No cache entries are ever created and GetVaryByCustomString is never called. I have triple checked the code for some sort of "Turn cacheability Off" setting but as far as I can tell there is no code anywhere in the page that disables the cache. My global.asax file inherits from the correct class, my custom ouputcache provider seems to be adding the correct entries for the 2 pages that work. Of course now with VaryByParam="*" the key can be as long as the content thanks to viewstate (which has to be left on) BLECH!
So essentially I have caching set on three pages that are similar and one doesn't work. I have no idea where to look next. I am hoping someone like #kevinmontrose who really has a handle on this sort of stuff will take pity on me and give me a shove in a direction that will lead me to a solution.
UPDATE:
I have opened a case with Microsoft.
have you been setting VaryByParam? i forget the details at the moment but i recall getting stuck before finding out that it is mandatory. (in every case i think.(?)) i think duration is also required. (i'm referring to directives in the .aspx page.)
rereading your question i saw the issue: "I can't find any working examples that allow the page to define the string." i was rereading some articles and i'm pretty sure that's the wrong approach.
i haven't tested all of this but for ex, if you take the standard example:
<%# OutputCache Duration="10" VaryByParam="None" VaryByCustom="minorversion" %>
what seems to be happening is that you have just set the string to look for, and that's it.
when a request comes in, you check the HTTP request header - the Vary header, which is what you set in the page - and if it exists you do what you want.
<%# Application language="C#" %>
<script runat="server">
public override string GetVaryByCustomString(HttpContext context,
string arg)
{
// arg is your custom 'Vary' header.
if(arg == "minorversion")
{
return "Version=" +
context.Request.Browser.MinorVersion.ToString();
}
return base.GetVaryByCustomString(context, arg);
}
</script>
i just did a quick test of the basic example and added a label that is updated on page load with the current time. i refreshed the live page several times and the time did not change until 10 seconds had passed. so it definitely works. i think the key is that you set the value and you're done, but you can work with it if you want to.
... on second thought, i think you create the header in the page, and return the value from the global.asax. where?? i think it's sent to the caching system which decides if a new or cached page should be sent. in the example above the browser's minor version is checked and set and since there is a cached version available (after first load) it sends that, until 10 secs are up.
the docs for VaryByCustom is in a section for 'caching different versions of a page' so that's what this is doing. if someone with a different browser shows up, the browser version is checked - then the caching system checks to see if a version of the page for that browser version is available. if not, create a new page; if so, send the cached version.

asp.net MapPageRoute loading page twice

Not sure whats going on, but I have a rewrite with two parameters. For some reason the page is loading twice when it's called. I know that it's the rewrite because it works fine when it's just one parameter. Thanks for any help.
This is in my Global.asax
routeCollection.MapPageRoute("RouteForAlbum", "album/{autoID}/{albumName}", "~/SitePages/AlbumView.aspx");
This is on my page load
if (!Page.IsPostBack)
{
string id = Page.RouteData.Values["autoID"].ToString();
string albuname = Page.RouteData.Values["albumName"].ToString();
}
Wow, found the answer after more searching. If you have javascript reference with ../ this causes issues with URL rewritting.
asp.net Multiple Page_Load events for a user control when using URL Routing
UDPATE:
This can also happen when using CSS3PIE together with ASP.net Routing and the two don't play nicely together.
Any CSS3PIE css styles with a URL in the value can cause the target page to execute the code behind multilple times. For me specifically, it was these two lines:
behavior: url(PIE.htc);
-pie-background: url(bg-image.png) no-repeat, linear-gradient(#FFFFFF, #53A9FF);
Changing the above two lines to start with a leading slash "/" fixed it along with specifying the whole path to the files.
behavior: url(/scripts/PIE-1.0.0/PIE.htc);
-pie-background: url(/scripts/PIE-1.0.0/bg-image.png) no-repeat, linear-gradient(#FFFFFF, #53A9FF);

ASP.NET MVC: loading images from database and displaying their in view

We have some images in our database and want to display their in view. I find two way to do this - the first: we create action method in controller that get an image from database and return FileContentResult:
public ActionResult GetImage( int id )
{
var imageData = ...get bytes from database...
return File( imageData, "image/jpg" );
}
code in view:
<img src='<%= Url.Action( "GetImage", "image", new { id = ViewData["imageID"] } ) %>' />
The second way is to use HttpHandler:
public void ProcessRequest(HttpContext Context)
{
byte [] b = your image...;
Context.Response.ContentType = "image/jpeg";
Context.Response.BinaryWrite(b);
}
and code in view:
<img src="AlbumArt.ashx?imageId=1" />
The first question is what is the most efficient(work more faster) way to implement this functionality (and why it work faster)?
And the second question - is there is a way to put image in our view directly, when we first call action method to return this view? I mean that in action method we get list of images from database and pass their in view as list, and in view use this code:
<%=Html.Image(Model[i])%>
that code must put image into view directly from model.
There won't be much difference in performance between the two methods. Obviously using an http handler will be the fastest you could get because the request doesn't go through the MVC lifecycle (routing, instantiating a controller, model binding, invoking the action) but I think this is a micro optimization and I would personally use the first approach as it is more adapted in an MVC scenario. If you later realize that this is a bottleneck for your application by performing extensive load tests you could always switch to the http handler approach.
As far as your second question is concerned about the helper, the answer is no, you cannot do this easily. The only possibility is to use the data URI scheme but this is not supported by all browsers. This way if your model has the byte array of the image you could write a helper which renders the following:
<img src="data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAA..." alt="Red dot" />
The image data is base64 encoded directly into the page. Another drawback is that those images will never be cached and your HTML pages could become very large.

Categories