chart.GetHtmlImageMap not working as expected - c#

Gents,
I need help to create an imagemap to my web app's charts. After reading many resources, here and on the web, I end up with the following problem:
The Problem:
On my view I' am calling #Html.Action(RenderMyMap), that renders an <map> tag, with an atribute coords=0,0,0,0.
What Am I doing:
My project is organized this way:
-Core.cs: Class responsable to create the System.Web.UI.DataVisualization.Charting.Chart objects;
-ClientController: With an existing Chart object, I'm saving it with Chat.SaveImage(memoryStream,format), storing it in a session variable as a byte array, and returning a ContentResult object, like in return Content(chart.GetHtmlImageMap());
-ClientController: After that, I'am using the session variable to actually render the chart, like in return File(chartByteArray,"image/png");
-Index.cshtml: Finally in my view I'am calling method on the ClientController to render the map and the chart.
My setup:
.NET 4.0, MVC3, SQL Server 2008.
Bellw goes the code of one of my methods, on the ClientController, to generate and return n ImageMap.
public ActionResult ChartMapGetClientsByType()
{
System.Web.UI.DataVisualization.Charting.Chart chart = null;
if (Session["GraficoClientePorTipo"] != null)
{
chart = (System.Web.UI.DataVisualization.Charting.Chart)Session["GraficoClientePorTipo"];
Session.Remove("GraficoClientePorTipo");
}
else
{
chart = MeuMRP.Core.Chart.CreateChartImageMap("GraficoClientePorTipo", SeriesChartType.Pie);
chart.IsMapEnabled = true;
System.IO.MemoryStream ms = new System.IO.MemoryStream();
chart.SaveImage(ms, ChartImageFormat.Png);
Session["GraficoClientePorTipo"] = ms.ToArray();
}
return Content(chart.GetHtmlImageMap("GraficoClientePorTipo"));
}
What Am I doing wrong?

You should set the Url property to the Chart.Series object. For example:
chart.Series[0].Url = "#";

This error resulted for me on a complex chart with a set of BoxPlots.
For a simple Points chart, there was no error, the <map> was fine with coordinates and attributes.
In order for the map area coordinates to be generated, you need to add a ToolTip or a URL or MapAreaAttributes: e.g.
series.Points[idx].ToolTip = tooltip;
or
series.Points[idx].MapAreaAttributes = "onmouseover=\"DisplayTooltip('" + tooltip +"');\" onmouseout=\"DisplayTooltip('');\"";
But I found the ToolTip to be useless because it added nothing to the <area> in the <map> except for the coordinates (what am I doing wrong?). I did not test assigning a Url, assuming that works as asserted in another answer.
Adding the mouseover and mouseout MapAreaAttributes works just fine resulting in this:
<area onmouseover="DisplayTooltip('99');" onmouseout="DisplayTooltip('');" shape="rect" coords="98,44,103,49" alt="">
FYI You can find more details on setting up Web.UI Charting in MVC, at various URI, including
http://www.codeproject.com/Articles/297677/Using-ASP-Net-Charting-with-Image-Map-in-MVC
http://geekswithblogs.net/DougLampe/archive/2011/01/23/charts-in-asp.net-mvc-2-with-drill-down.aspx.

Related

Display Each Element From Array Using HTML Helper in C#

This is probably a very simple problem, but I am extremely new to C# / MVC and I have been handed a broken project to fix. So it's time to sink or swim!
I have an array of strings that is being passed from a function to the front end.
The array looks something like
reports = Directory.GetFiles(#"~\Reports\");
On the front end, I would like it to display each report, but I am not sure how to do that.
This project is using a MVC, and I believe the view is called "Razor View"? I know that it's using an HTML helper.
In essence, I need something like
#HTML.DisplayTextFor(Model.report [And then print every report in the array]);
I hope that makes sense.
If you want to display the file name array you can simply use a foreach:
#foreach(var report in Model.Reports){ #report }
Note that you should add the Reports property to your view model:
public class SampleViewModel
{
public string [] Reports { get; set; }
}
You could use ViewData or TempData but I find that using the view model is the better way.
You can then populate it:
[HttpGet]
public ActionResult Index()
{
var model = new SampleViewModel(){ Reports = Directory.GetFiles(#"~\Reports\")};
return View(model);
}
And use it in the view as you see fit.
Here is a simple online example: https://dotnetfiddle.net/5WmX5M
If you'd like to add a null check at the view level you can:
#if(Model.Reports != null)
{
foreach(var report in Model.Reports){ #report <br> }
}
else
{
<span> No files found </span>
}
https://dotnetfiddle.net/melMLW
This is never a bad idea, although in this case GetFiles will return an empty list if no files can be found, and I assume a possible IOException is being handled.

Cannot delete file from server folder

I'm working on a simple portfolio project. I would like to show images on a webpage that logged in users can edit. My problem is in the [HttpPost] Edit, more specifically this part:
if (ModelState.IsValid)
{
//updating current info
inDb = ModelFactory<ArtSCEn>.GetModel(db, artSCEn.ArtSCEnID);
inDb.LastModified = DateTime.Now;
inDb.TechUsed = artSCEn.TechUsed;
inDb.DateOfCreation = artSCEn.DateOfCreation;
inDb.Description = artSCEn.Description;
inDb.ArtSC.LastModified = DateTime.Now;
//validating img
if (Validator.ValidateImage(img))
{
inDb.ImageString = Image.JsonSerialzeImage(img);
}
else
{
//return to the UI becuase we NEED a valid pic
return View(artSCEn);
}
db.Entry(inDb).State = System.Data.Entity.EntityState.Modified;
db.SaveChanges();
//[PROBLEMATIC PART STARTS HERE]
//updating the pic on the server
//getting the string info
string userArtImgFolder = Server.MapPath($"~/Content/Images/Artistic/{inDb.ArtSC.PersonID}");
string imgNameOnServer = Path.Combine(
userArtImgFolder,
$"{inDb.ArtSC.PersonID}_{inDb.ArtSC.ArtSCID}_{inDb.ArtSCEnID}{Path.GetExtension(img.FileName)}");
//deleting previous pic
System.IO.File.Delete(imgNameOnServer);
//creating a new pic
Image.ResizePropotionatelyAndSave(img, Path.Combine(
userArtImgFolder,
$"{inDb.ArtSC.PersonID}_{inDb.ArtSC.ArtSCID}_{inDb.ArtSCEnID}{Path.GetExtension(img.FileName)}"));
return RedirectToAction("Edit", "Art", new { id = inDb.ArtSCID });
}
When I get back the new picture and I want to delete the previous, System.IO.File.Delete() always triggers an exception that it cannot access the resource, because someone else is holding onto it. Any idea what that might be?
Maybe it's something simple, I'm new to ASP, but just can't figure it out.
UPDATE
Following on the suggestions in the comments section, I checked the processes with a tool called Process Monitor and it seems that indeed IIS is locking the resource:
This one appears 2 more times in the logs, by the way.
Judging by the fact that the operation is CreateFileMapping, I guess it has to do with either Server.MapPath() or Path.Combine(), however, the Server is an IDisposable (being derived from Controller), so can that be the one I should deal with?
Also, the resource I'm trying to delete is an image used on the website, which might be a problem, but that section of the website is not shown during this process.
I found the solution building on the comment of #Diablo.
The IIS was indeed holding on to the resource, but Server.MapPath() or any of that code had nothing to do with it: it was the Edit view my page returning the data to. With the help of this SO answer, it turns out I was careless with a BitMap that I used without a using statement in the view to get some image stats. I updated the helper function with the following code:
public static float GetImageWidthFromPath(string imgAbsolutPath, int offset)
{
float width = 0;
using (Bitmap b = new Bitmap(imgAbsolutPath))
{
width = b.Width - offset;
}
return width;
}
Now IIS does not hold on to the resource and I can delete the file.

Adding an image within C# code (MVC4)

I'm trying to load an image within a webgrid within some if/else logic. Essentially if the API returns true show one image if false show the other image however the code I'm using right now -
columns: grid.Columns(
grid.Column("PowerState", format: (vm) =>
{
if (vm.PowerState == "Stopped") //Choose font colour depending on the status of the VM
{
return new HtmlString("<img src ="~/Content/offstate.png">");
}
else
{
return new HtmlString("<img src =~/Content/onstate.png>");
}
}),
isn't working as intended as the URLs returned by each image are https://localhost:44300/~/Content/onstate.png and https://localhost:44300/"~/Content/offstate.png"
respectively when what I want is https://localhost:44300/Content/offstate.png
(tried removing the ~/ out of annoyance but this will return https://localhost:44300/Home/Content/onstate.png just in case somebody has that idea.)
EDIT: The solution is to use the Url.Content function as so
return new HtmlString("<img src = " + Url.Content("~/Content/offstate.png") + ">");
As suggested by Ashok!
You always need relative path to that image irrespective of current page. In such cases we need #Url.Content(...).
Look at why use #Url.Content topic and instead of providing path. Get path from #Url.Content and join to you html.

ArcGis Engine, how to select objects?

I'm trying to create a standalone application, which loads a ArcGis map, selects a few objects in one layer and zooms to them.
Loading and displaying the map does work, using something like this:
AxMapControl _mapControl;
// in constructor:
_mapControl = new AxMapControl();
// in loading
_mapControl.LoadMxFile(#"C:\Users\me\Documents\TestProject.mxd");
This does work nicely and does display the map as full extent (of course the AxMapControl is embedded into a WindowsFormsHost, but this shouldn't be a problem).
But now I need to select one or more objects and zoom to them. I tried to select in one layer for testing, but this does not work at all:
IFeatureSelection features = _mapControl.Map.Layer[0] as IFeatureSelection;
if (features != null)
{
QueryFilter qf = new QueryFilterClass();
qf.WhereClause = "[Name]='FS4711000'";
features.SelectFeatures(qf, esriSelectionResultEnum.esriSelectionResultNew, false);
}
on the SelectFeatures call I get an COM error 80004005 (E_Fail) in ESRI.ArcGIS.Carto, without much more explanation. Probably I'm doing it all wrong.
Maybe someone has a sample how to select objects in a layer?
I think your issue is as simple as the [square brackets] around your field name in the query string.
This works:
IFeatureSelection features = _currentLayer as IFeatureSelection;
if (features != null)
{
QueryFilter qf = new QueryFilter();
qf.WhereClause = "Type='1'";
features.SelectFeatures(qf, esriSelectionResultEnum.esriSelectionResultNew, false);
}
_axMapControl.Refresh();
Whereas this fails with COM-error E_FAIL:
IFeatureSelection features = _currentLayer as IFeatureSelection;
if (features != null)
{
QueryFilter qf = new QueryFilter();
qf.WhereClause = "[Type]='1'";
features.SelectFeatures(qf, esriSelectionResultEnum.esriSelectionResultNew, false);
}
_axMapControl.Refresh();
Also, notice that the map (or at least the IActiveView returned by AxMapControl.ActiveView) needs to be manually refreshed, or the selection is not displayed before the map is redrawn.

How to feed WebBrowser control and manipulate the HTML document?

Good day
I have question about displaying html documents in a windows forms applications. App that I'm working on should display information from the
database in the html format. I will try to describe actions that I have taken (and which failed):
1) I tried to load "virtual" html page that exists only in memory and dynamically change it's parameters (webbMain is a WebBrowser control):
public static string CreateBookHtml()
{
StringBuilder sb = new StringBuilder();
//Declaration
sb.AppendLine(#"<?xml version=""1.0"" encoding=""utf-8""?>");
sb.AppendLine(#"<?xml-stylesheet type=""text/css"" href=""style.css""?>");
sb.AppendLine(#"<!DOCTYPE html PUBLIC ""-//W3C//DTD XHTML 1.1//EN""
""http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"">");
sb.AppendLine(#"<html xmlns=""http://www.w3.org/1999/xhtml"" xml:lang=""en"">");
//Head
sb.AppendLine(#"<head>");
sb.AppendLine(#"<title>Exemplary document</title>");
sb.AppendLine(#"<meta http-equiv=""Content-Type"" content=""application/xhtml+xml;
charset=utf-8""/ >");
sb.AppendLine(#"</head>");
//Body
sb.AppendLine(#"<body>");
sb.AppendLine(#"<p id=""paragraph"">Example.</p>");
sb.AppendLine(#"</body>");
sb.AppendLine(#"</html>");
return sb.ToString();
}
void LoadBrowser()
{
this.webbMain.Navigate("about:blank");
this.webbMain.DocumentText = CreateBookHtml();
HtmlDocument doc = this.webbMain.Document;
}
This failed, because doc.Body is null, and doc.getElementById("paragraph") returns null too. So I cannot change paragraph InnerText property.
Furthermore, this.webbMain.DocumentText is "\0"...
2) I tried to create html file in specified folder, load it to the WebBrowser and then change its parameters. Html is the same as created by
CreateBookHtml() method:
private void LoadBrowser()
{
this.webbMain.Navigate("HTML\\BookPage.html"));
HtmlDocument doc = this.webbMain.Document;
}
This time this.webbMain.DocumentText contains Html data read from the file, but doc.Body returns null again, and I still cannot take element using
getByElementId() method. Of course, when I have text, I would try regex to get specified fields, or maybe do other tricks to achieve a goal, but I wonder - is there simply way to mainipulate html? For me, ideal way would be to create HTML text in memory, load it into the WebBrowser control, and then dynamically change its parameters using IDs. Is it possible? Thanks for the answers in advance, best regards,
Paweł
I've worked some time ago with the WebControl and like you wanted to load a html from memory but have the same problem, body being null. After some investigation, I noticed that the Navigate and NavigateToString methods work asynchronously, so it needs a little time for the control to load the document, the document is not available right after the call to Navigate. So i did something like (wbChat is the WebBrowser control):
wbChat.NavigateToString("<html><body><div>first line</div></body><html>");
DoEvents();
where DoEvents() is implemeted as:
[SecurityPermissionAttribute(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)]
public void DoEvents()
{
DispatcherFrame frame = new DispatcherFrame();
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
new DispatcherOperationCallback(ExitFrame), frame);
Dispatcher.PushFrame(frame);
}
and it worked for me, after the DoEvents call, I could obtain a non-null body:
mshtml.IHTMLDocument2 doc2 = (mshtml.IHTMLDocument2)wbChat.Document;
mshtml.HTMLDivElement div = (mshtml.HTMLDivElement)doc2.createElement("div");
div.innerHTML = "some text";
mshtml.HTMLBodyClass body = (mshtml.HTMLBodyClass)doc2.body;
if (body != null)
{
body.appendChild((mshtml.IHTMLDOMNode)div);
body.scrollTop = body.scrollHeight;
}
else
Console.WriteLine("body is still null");
I don't know if this is the right way of doing this, but it fixed the problem for me, maybe it helps you too.
Later Edit:
public object ExitFrame(object f)
{
((DispatcherFrame)f).Continue = false;
return null;
}
The DoEvents method is necessary on WPF. For System.Windows.Forms one can use Application.DoEvents().
Another way to do the same thing is:
webBrowser1.DocumentText = "<html><body>blabla<hr/>yadayada</body></html>";
this works without any extra initialization

Categories

Resources