I am struggling to find a way to take a screenshot of a website in MVC4. I have seen two potential solutions, which neither work well for MVC.
The first is using the WebBrowser, tutorial found here, but this gives me a ActiveX control '8856f961-340a-11d0-a96b-00c04fd705a2' cannot be instantiated because the current thread is not in a single-threaded apartment error.
The other is using a 3rd party called Grabz.It, but I haven't found a way to integrate it into MVC.
Any other ideas/solutions?
Thanks.
Given your additional details, you should be able to do this with any number of tools. CodeCaster's idea is fine, and PhantomJS also offers similar webkit-based image generation of an arbitrary url (https://github.com/ariya/phantomjs/wiki/Screen-Capture). It offers several output format options, such as PNG, JPG, GIF, and PDF.
Since PhantomJS is using WebKit, a real layout and rendering engine, it can capture a web page as a screenshot. Because PhantomJS can render anything on the web page, it can be used to convert contents not only in HTML and CSS, but also SVG and Canvas.
You would need to execute the phantomjs.exe app from your MVC app, or probably even better by some service that is running behind the scenes to process a queue of submitted urls.
Why do you want to integrate this in MVC? Is it your website's responsibility to take screenshots of other websites? I would opt to create the screenshot-taking-logic in a separate library, hosted as a Windows Service for example.
The WebBrowser control needs to run on an UI thread, which a service (like IIS) doesn't have. You can try other libraries though.
You could for example write some code around wkhtmltopdf, which renders (as the name might suggest) HTML to PDF using the WebKit engine.
You need to specify that the thread is in STA (single threaded apartment mode in order to instantiate the web browser).
public ActionResult Save()
{
var url = "http://www.google.co.uk";
FileContentResult result = null;
Bitmap bitmap = null;
var thread = new Thread(
() =>
{
bitmap = ExportUrlToImage(url, 1280, 1024);
});
thread.SetApartmentState(ApartmentState.STA); //Set the thread to STA
thread.Start();
thread.Join();
if (bitmap != null)
{
using (var memstream = new MemoryStream())
{
bitmap.Save(memstream, ImageFormat.Jpeg);
result = this.File(memstream.GetBuffer(), "image/jpeg");
}
}
return result;
}
private Bitmap ExportUrlToImage(string url, int width, int height)
{
// Load the webpage into a WebBrowser control
WebBrowser wb = new WebBrowser();
wb.ScrollBarsEnabled = false;
wb.ScriptErrorsSuppressed = true;
wb.Navigate(url);
while (wb.ReadyState != WebBrowserReadyState.Complete)
{
Application.DoEvents();
}
// Set the size of the WebBrowser control
wb.Width = width;
wb.Height = height;
Bitmap bitmap = new Bitmap(wb.Width, wb.Height);
wb.DrawToBitmap(bitmap, new System.Drawing.Rectangle(0, 0, wb.Width, wb.Height));
wb.Dispose();
return bitmap;
}
Related
My app used the SidePanel menu as navigation and when I show new form or open sidebar panel, the app takes more and more memory. Possible, it depended on using some Image processing (to mask image to circle) in SideBar and a lot of using URLImage class for downloading images. But most likely due to the fact that I did not free memory of the previous form.
How I can free this memory?
Code of changing forms:
public void showForm(FormBuilder form) {
if ( current == null ||
( ! form.getForm().getTitle().equals(current.getTitle()) )
) {
current = form.getForm();
if (!(form instanceof splash)) {
try {
sideMenu.addMenu(current);
} catch (IOException ex) {
}
}
current.show();
}
}
void sideMenu.addMenu(Form form); - Static function for add SideBar menu to form.
Previous forms "should" be GC'd. However, if you have a reference to one element in the previous form the whole form and all its content will be kept. This is because every component has a reference to its parent all the way up to the parent form.
You can use tools like the NetBeans memory profiler and also our performance profiler tool in NetBeans to track down memory usage. Image masking is a bit expensive but if you used the one built into URLImage all the memory overhead is GC'd so it shouldn't be a problem.
How to get HTML Elmement (or DOM) in Vaadin ?In GWT I can use as DOM.getElementById("myId");
I can set id attribute on my Vaadin components by setId() method. For example:
Button button = new Button("Say Hello");
button.setId("myButton");
So, how can I retrieve this DOM Element in Vaadin ?
You can use this:
public static Component findComponentById(HasComponents root, String id) {
for (Component child : root) {
if (id.equals(child.getId())) {
return child; // found it!
} else if (child instanceof HasComponents) { // recursively go through all children that themselves have children
Component result = findComponentById((HasComponents) child, id);
if (result != null) {
return result;
}
}
}
return null; // none was found
}
Source: https://vaadin.com/forum/#!/thread/3199995/3199994
Vaadin 10 (Vaadin Flow)
The new Vaadin Flow generation replaces the internal use of GWT for Web Components.
This new architecture provides us with easy direct access to the DOM from the Java-based server-side, if you so desire. You can read the DOM, and you can manipulate elements in the DOM. Read about the new Element API in the manual.
Vaadin 6, 7, & 8 (Vaadin Framework)
This Answer expands on the comment by Vaadin expert, Henri Kerola.
Vaadin is a server-side app framework. It's purpose is to shield the app developer from the details of HTML, CSS, JavaScript, DOM, GWT, HTTP, WebSocket, and such web technologies. The app developer writes in pure Java (and maybe a tiny touch of CSS for tweaking). Vaadin transparently and auto-magically generates the HTML-CSS-JavaScript-GWT-DOM necessary to render a representation of the app’s user-interface within a web browser.
So there is no way to access the DOM from that Java server-side, nor any need to do so generally.
If you want to take control of the web technologies then Vaadin is probably not the best framework for you.
In Vaadin8 you may try this:
JavaScript.getCurrent().execute("document.getElementById('refreshButton').click()");
I have been researching this topic for awhile now. I know there are other questions relating this topic but a lot of the answers I haven't gotten to work or lead to 404 Not Found Webpages like this one http://www.kendoui.com/code-library/dataviz/chart/kendo-ui-chart-export.aspx. Does anyone have a current way to export a Kendo Chart to JPG or PNG? I have tried looking to Inkscape but I am not familiar with it at all.
Using the nuGet library svg and posting a tidbit that might be useful for you. You can obtain the svgFileContents from and chart.
public BitMap RenderReportFromSVG(string svgFileContents)
{
System.Drawing.Bitmap _bitmap=null;
byte[] byteArray = Encoding.ASCII.GetBytes(svgFileContents);
using (var stream = new MemoryStream(byteArray))
{
XmlDocument xdoc=new XmlDocument();
xdoc.LoadXml(svgFileContents);
var svgDocument = SvgDocument.Open(xdoc);
_bitmap = svgDocument.Draw();
}
return _bitmap;
}
Mobile display modes in ASP.NET MVC 4 stop serving the correct views after about an hour of uptime, despite browser overrides correctly detecting an overridden mobile device.
Recycling the application pool temporarily solves the problem.
The new browser override feature correctly allows mobile devices to view the desktop version of a site, and vice-versa. But after about an hour of uptime, the mobile views are no longer rendered for a mobile device; only the default desktop Razor templates are rendered. The only fix is to recycle the application pool.
Strangely, the browser override cookie continues to function. A master _Layout.cshtml template correctly shows "mobile" or "desktop" text depending on the value of ViewContext.HttpContext.GetOverriddenBrowser().IsMobileDevice, but the wrong views are still being rendered. This leads me to believe the problem lies with the DisplayModes.
The action in question is not being cached:
[OutputCache(NoStore = true, Duration = 0, VaryByParam = "*")]
I am using 51Degrees for mobile detection, but I don't think this should affect the overridden mobile detection. Is this a bug in DisplayModes feature for ASP.NET MVC 4 Beta & Developer Preview, or am I doing something else wrong?
Here is my DisplayModes setup in Application_Start:
DisplayModeProvider.Instance.Modes.Insert(0, new DefaultDisplayMode("iPhone")
{
ContextCondition = context =>
context.GetOverriddenBrowser().IsMobileDevice
&& (context.Request.UserAgent.IndexOf("iPhone", StringComparison.OrdinalIgnoreCase) >= 0
|| context.Request.UserAgent.IndexOf("Android", StringComparison.OrdinalIgnoreCase) >= 0
|| !context.Request.Browser.IsMobileDevice)
});
/* Looks complicated, but renders Home.iPhone.cshtml if the overriding browser is
mobile or if the "real" browser is on an iPhone or Android. This falls through
to the next instance Home.Mobile.cshtml for more basic phones like BlackBerry.
*/
DisplayModeProvider.Instance.Modes.Insert(1, new DefaultDisplayMode("Mobile")
{
ContextCondition = context =>
context.GetOverriddenBrowser().IsMobileDevice
});
This is a known issue in MVC 4 (Codeplex: #280: Multiple DisplayModes - Caching error, will show wrong View). This will be fixed in the next version of MVC.
In the meantime you can install a workaround package available here: http://nuget.org/packages/Microsoft.AspNet.Mvc.FixedDisplayModes.
For most applications simply installing this package should resolve the issue.
For some applications that customize the collection of registered view engines, you should make sure that you reference Microsoft.Web.Mvc.FixedRazorViewEngine or Microsoft.Web.Mvc.FixedWebFormViewEngine, instead of the default view engine implementations.
I had a similar issue and it turned out to be a bug when mixing webforms based desktop views with razor based mobile views.
See http://aspnetwebstack.codeplex.com/workitem/276 for more info
Possibly a bug in ASP.NET MVC 4 related to caching of views, see:
http://forums.asp.net/p/1824033/5066368.aspx/1?Re+MVC+4+RC+Mobile+View+Cache+bug+
I can't speak for this particular stack (I'm still in MVC2) but check your output caching setup (either in your controllers or views - and in your web.config in your app and at the machine level). I've seen it work initially for the first few users and then a desktop browser comes in right around the time ASP decides to cache, then everyone gets the same view. We've avoided output caching as a result, hoping this would get addressed later.
If you want all mobile devices to use the same mobile layout you can use
DisplayModeProvider.Instance.Modes.Insert(1, new DefaultDisplayMode("Mobile")
{
ContextCondition = context =>
context.GetOverriddenBrowser().IsMobileDevice
});
And of course you need to make a view in the shared layout folder named _Layout.Mobile.cshtml
If you want to have a separate layout for each type of device or browser you need to do this;
DisplayModeProvider.Instance.Modes.Insert(0, new DefaultDisplayMode("Android")
{
ContextCondition = (context => context.GetOverriddenUserAgent().IndexOf
("Android", StringComparison.OrdinalIgnoreCase) >= 0)
});
DisplayModeProvider.Instance.Modes.Insert(0, new DefaultDisplayMode("iPhone")
{
ContextCondition = (context => context.GetOverriddenUserAgent().IndexOf
("iPhone", StringComparison.OrdinalIgnoreCase) >= 0)
});
DisplayModeProvider.Instance.Modes.Insert(0, new DefaultDisplayMode("Mobile")
{
ContextCondition = (context => context.GetOverriddenUserAgent().IndexOf
("IEMobile", StringComparison.OrdinalIgnoreCase) >= 0)
});
And of course you need to make a view in the shared layout folder for each named
_Layout.Android.cshtml
_Layout.iPhone.cshtml
_Layout.Mobile.cshtml
Can you not just do this?
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
// Code removed for clarity.
// Cache never expires. You must restart application pool
// when you add/delete a view. A non-expiring cache can lead to
// heavy server memory load.
ViewEngines.Engines.OfType<RazorViewEngine>().First().ViewLocationCache =
new DefaultViewLocationCache(Cache.NoSlidingExpiration);
// Add or Replace RazorViewEngine with WebFormViewEngine
// if you are using the Web Forms View Engine.
}
So guys here is the answer to all of your worries..... :)
To avoid the problem, you can instruct ASP.NET to vary the cache entry according to whether the visitor is using a mobile device. Add a VaryByCustom parameter to your page’s OutputCache declaration as follows:
<%# OutputCache VaryByParam="*" Duration="60" VaryByCustom="isMobileDevice" %>
Next, define isMobileDevice as a custom cache parameter by adding the following method override to your Global.asax.cs file:
public override string GetVaryByCustomString(HttpContext context, string custom)
{
if (string.Equals(custom, "isMobileDevice", StringComparison.OrdinalIgnoreCase))
return context.Request.Browser.IsMobileDevice.ToString();
return base.GetVaryByCustomString(context, custom);
}
This will ensure that mobile visitors to the page don’t receive output previously put into the cache by a desktop visitor.
please see this white paper published by microsoft. :)
http://www.asp.net/whitepapers/add-mobile-pages-to-your-aspnet-web-forms-mvc-application
Thanks and Keep coding.....
I have a controller as follows that returns a byte[]
public class ImageController : Controller
{
public ActionResult Show(int id)
{
var proxy = new ServiceProxy();
var imgData = proxy.GetCheckImage(id);
return File(imgData, "image/tiff");
}
}
My view is as follows:
<img alt ="" src='#Url.Action("show", "image", new { id = 36 })'/>
I have hard coded the image id for debug purposes.
On the browser in chrome/ie I get a x where the image needs to be displayed. But if I go directly to the controller url http://localhost/website/image/show/id=36, the image gets downloaded fine to the local machine.I have tried creating a separate ActionResult in the same controller which is used to display other data without any luck. This is a Win7/IIS7 local dev. environment.
Tiff is not supported by most browsers. The solution is to convert the Tiff to a Png file.
This post has the solution to your problem:
Render image to the screen from MVC controller
Is Tiff supported by Chrome/IE? I don't think it is...