How to display data uri scheme into a C# WebBrowser Controller - c#

How can I show an image base64 encoded using WebBrowser control in C#?
I used the following code:
<img src="data:image/gif;base64,/9j/4AAQSkZJRgABAgAAZABkAA7AAR
R894ADkFkb2JlAGTAAAAAAfbAIQABAMDAwMDBAMDBAYEAwQGBwUEBAUHCAYGBw
...
uhWkvoJfQO2z/rf4VpL6CX0Dts/63+FaS+gl9A7bP+tthWkvoJfQODCde4qfcg
RiNWK3UyUeX9CXpHU43diOK915X5fG/reux5hUAUBftZ" />
but no image is displayed. One solution would be to save images locally and using absolute path, but this is not desirable.
Any idea?

I tried doing this for a project and IE (which the WebBrowser control will eventually use) became the limiting factor - it can only hold 32Kb-sized images. I wound up having to create an HTTP handler (.ashx) that returned the image based on a database key.
edit: example - note the database handling routines are proprietary and you'd have to put in your own. The rest of the handler will show how to rescale images (if desired) and send back as a response to the browser:
public class GenerateImage : IHttpHandler
{
/// <summary>
/// Shortcut to the database controller. Instantiated immediately
/// since the ProcessRequest method uses it.
/// </summary>
private static readonly IDataModelDatabaseController controller =
DataModelDatabaseControllerFactory.Controller;
/// <summary>
/// Enables processing of HTTP Web requests by a custom HttpHandler
/// that implements the <see cref="T:System.Web.IHttpHandler"/>
/// interface.
/// </summary>
/// <param name="context">An <see cref="T:System.Web.HttpContext"/>
/// object that provides references to the intrinsic server objects
/// (for example, Request, Response, Session, and Server) used to
/// service HTTP requests.</param>
public void ProcessRequest(HttpContext context)
{
if (controller == null)
{
return;
}
IDataModelDescriptor desc = controller.GetDataModelDescriptor(
new Guid(context.Request.QueryString["dataModel"]));
IDataModelField imageField =
desc.Fields[context.Request.QueryString["imageField"]];
IDatabaseSelectQuery query = controller.CreateQuery();
string[] keys = context.Request.QueryString["key"].Split(',');
string showThumb = context.Request.QueryString["showThumbnail"];
bool showThumbnail = showThumb != null;
query.AssignBaseTable(desc);
query.AddColumn(imageField, false);
for (int i = 0; i < desc.KeyFields.Count; i++)
{
query.AddCompareValue(
desc.KeyFields[i],
keys[i],
DatabaseOperator.Equal);
}
context.Response.CacheControl = "no-cache";
context.Response.ContentType = "image/jpeg";
context.Response.Expires = -1;
byte[] originalImage = (byte[])controller.ExecuteScalar(query);
if (showThumbnail)
{
int scalePixels;
if (!int.TryParse(showThumb, out scalePixels))
{
scalePixels = 100;
}
using (Stream stream = new MemoryStream(originalImage))
using (Image img = Image.FromStream(stream))
{
double multiplier;
if ((img.Width <= scalePixels)
&& (img.Height <= scalePixels))
{
context.Response.BinaryWrite(originalImage);
return;
}
else if (img.Height < img.Width)
{
multiplier = (double)img.Width / (double)scalePixels;
}
else
{
multiplier = (double)img.Height / (double)scalePixels;
}
using (Bitmap finalImg = new Bitmap(
img,
(int)(img.Width / multiplier),
(int)(img.Height / multiplier)))
using (Graphics g = Graphics.FromImage(finalImg))
{
g.InterpolationMode =
InterpolationMode.HighQualityBicubic;
finalImg.Save(
context.Response.OutputStream,
ImageFormat.Jpeg);
}
}
}
else
{
context.Response.BinaryWrite(originalImage);
}
}
/// <summary>
/// Gets a value indicating whether another request can use the
/// <see cref="T:System.Web.IHttpHandler"/> instance.
/// </summary>
/// <value></value>
/// <returns>true if the <see cref="T:System.Web.IHttpHandler"/>
/// instance is reusable; otherwise, false.
/// </returns>
public bool IsReusable
{
get
{
return false;
}
}
}

What is data uri string length, according to data Protocol in IE8 Data URIs cannot be larger than 32,768 characters.
Edit: The resource data must be properly encoded; otherwise, an error occurs and the resource is not loaded. The "#" and "%" characters must be encoded, as well as control characters, non-US ASCII characters, and multibyte characters.

Related

Why are the contents of my pages getting mixed up when reading PDF?

I am using iText7 to read the text from a pdf file. This works fine for the first page. After that the contents of the pages are somehow getting mixed up. So at page 3 of the document I have lines that contain content of page 1 and 3. The text of page 2 shows the exact same lines as page 1 (but in "reallity" they are completely different).
Page 1, real: ~36 lines, result 36 lines -> GREAT
Page 2, real: >50 lines, result 36 lines (==Page 1)
Page 3, real: ~16 lines, result 47 lines (adds and mixes with lines of page 1)
https://www.dropbox.com/s/63gy5cg1othy6ci/Dividenden_Microsoft.pdf?dl=0
For reading the document I use the following code:
using System;
using System.Collections.Generic;
using System.Linq;
namespace StockMarket
{
class PdfReader
{
/// <summary>
/// Reads PDF file by a given path.
/// </summary>
/// <param name="path">The path to the file</param>
/// <param name="pageCount">The number of pages to read (0=all, 1 by default) </param>
/// <returns></returns>
public static DocumentTree PdfToText(string path, int pageCount=1 )
{
var pages = new DocumentTree();
using (iText.Kernel.Pdf.PdfReader reader = new iText.Kernel.Pdf.PdfReader(path))
{
using (iText.Kernel.Pdf.PdfDocument pdfDocument = new iText.Kernel.Pdf.PdfDocument(reader))
{
var strategy = new iText.Kernel.Pdf.Canvas.Parser.Listener.LocationTextExtractionStrategy();
// set up pages to read
int pagesToRead = 1;
if (pageCount > 0)
{
pagesToRead = pageCount;
}
if (pagesToRead > pdfDocument.GetNumberOfPages() || pageCount==0)
{
pagesToRead = pdfDocument.GetNumberOfPages();
}
// for each page to read...
for (int i = 1; i <= pagesToRead; ++i)
{
// get the page and save it
var page = pdfDocument.GetPage(i);
var txt = iText.Kernel.Pdf.Canvas.Parser.PdfTextExtractor.GetTextFromPage(page, strategy);
pages.Add(txt);
}
pdfDocument.Close();
reader.Close();
}
}
return pages;
}
}
/// <summary>
/// A class representing parts of a PDF document.
/// </summary>
class DocumentTree
{
/// <summary>
/// Constructor
/// </summary>
public DocumentTree()
{
Pages = new List<DocumentPage>();
}
private List<DocumentPage> _pages;
/// <summary>
/// The pages of the document
/// </summary>
public List<DocumentPage> Pages
{
get { return _pages; }
set { _pages = value; }
}
/// <summary>
/// Adds a <see cref="DocumentPage"/> to the document.
/// </summary>
/// <param name="page">The text of the <see cref="DocumentPage"/>.</param>
public void Add(string page)
{
Pages.Add(new DocumentPage(page));
}
}
/// <summary>
/// A class representing a single page of a document
/// </summary>
class DocumentPage
{
/// <summary>
/// Constructor
/// </summary>
/// <param name="pageContent">The pages content as text</param>
public DocumentPage(string pageContent)
{
// set the content to the input
CompletePage = pageContent;
// split the content by lines
var splitter = new string[] { "\n" };
foreach (var line in CompletePage.Split(splitter, StringSplitOptions.None))
{
// add lines to the page if the content is not empty
if (!string.IsNullOrWhiteSpace(line))
{
_lines.Add(new Line(line));
}
}
}
private List<Line> _lines = new List<Line>();
/// <summary>
/// The lines of text of the <see cref="DocumentPage"/>
/// </summary>
public List<Line> Lines
{
get
{
return _lines;
}
}
/// <summary>
/// The text of the complete <see cref="DocumentPage"/>.
/// </summary>
private string CompletePage;
}
/// <summary>
/// A class representing a single line of text
/// </summary>
class Line
{
/// <summary>
/// Constructor
/// </summary>
public Line(string lineContent)
{
CompleteLine = lineContent;
}
/// <summary>
/// The words of the <see cref="Line"/>.
/// </summary>
public List<string> Words
{
get
{
return CompleteLine.Split(" ".ToArray()).Where((word)=> { return !string.IsNullOrWhiteSpace(word); }).ToList();
}
}
/// <summary>
/// The complete text of the <see cref="Line"/>.
/// </summary>
private string CompleteLine;
public override string ToString()
{
return CompleteLine;
}
}
}
The page tree is a simple tree with the pages, consisting of lines (read page split by "\n") and lines consisting of words (line split by " ") but the txt in the loop already contains the messed up content (so my tree is not causing the issues).
Thanks for your help.
Some parsing event listeners, in particular most text extraction strategies, are not meant to be reused on multiple pages. Instead you should create a new instance for each page.
As a rule of thumb each such listener that collects information while a page is parsed, and afterwards allows you to access that data (like text extraction strategies allow you to access the collected page text), most likely must be instantiated separately for each page if you don't want the data from all pages to accumulate.
Thus, in your code move the strategy instantiation
var strategy = new iText.Kernel.Pdf.Canvas.Parser.Listener.LocationTextExtractionStrategy();
into the for loop:
// for each page to read...
for (int i = 1; i <= pagesToRead; ++i)
{
var strategy = new iText.Kernel.Pdf.Canvas.Parser.Listener.LocationTextExtractionStrategy();
// get the page and save it
var page = pdfDocument.GetPage(i);
var txt = iText.Kernel.Pdf.Canvas.Parser.PdfTextExtractor.GetTextFromPage(page, strategy);
pages.Add(txt);
}
Alternatively you can shorten the loop to
// for each page to read...
for (int i = 1; i <= pagesToRead; ++i)
{
// get the page and save it
var page = pdfDocument.GetPage(i);
var txt = iText.Kernel.Pdf.Canvas.Parser.PdfTextExtractor.GetTextFromPage(page);
pages.Add(txt);
}
This PdfTextExtractor.GetTextFromPage overload creates a new LocationTextExtractionStrategy instance each time internally.

How to use Fonts in PDFsharp in my Website?

I am just using the Arial font on an Azure WebApplication web site but when I get to this line:
MainFont = new XFont("Arial", FontSize);
it throws an exception reading: Font data could not retrieved.
I would have thought Arial would have been installed on the server ... and I also tried changing it to Sans-Serif to match the default font of the Microsoft generated web-site ... but it still fails.
I have also tried adding Arial.ttf to the project, but that hasn't worked.
Thanks for the pointers #PDFSharp Team. Here is my implementation for PdfSharp 1.5 beta3b:
Add the fonts you want to your project - in my example below I put Arial in MyProject\fonts\arial\arial.ttf etc. Set each font file as an embedded resource (properties -> build action).
Apply the font resolver only once using the static call like this:
MyFontResolver.Apply(); // Ensures it's only applied once
Here's the font resolver class:
class MyFontResolver : IFontResolver
{
public FontResolverInfo ResolveTypeface(string familyName, bool isBold, bool isItalic)
{
// Ignore case of font names.
var name = familyName.ToLower().TrimEnd('#');
// Deal with the fonts we know.
switch (name)
{
case "arial":
if (isBold)
{
if (isItalic)
return new FontResolverInfo("Arial#bi");
return new FontResolverInfo("Arial#b");
}
if (isItalic)
return new FontResolverInfo("Arial#i");
return new FontResolverInfo("Arial#");
}
// We pass all other font requests to the default handler.
// When running on a web server without sufficient permission, you can return a default font at this stage.
return PlatformFontResolver.ResolveTypeface(familyName, isBold, isItalic);
}
/// <summary>
/// Return the font data for the fonts.
/// </summary>
public byte[] GetFont(string faceName)
{
switch (faceName)
{
case "Arial#":
return FontHelper.Arial;
case "Arial#b":
return FontHelper.ArialBold;
case "Arial#i":
return FontHelper.ArialItalic;
case "Arial#bi":
return FontHelper.ArialBoldItalic;
}
return null;
}
internal static MyFontResolver OurGlobalFontResolver = null;
/// <summary>
/// Ensure the font resolver is only applied once (or an exception is thrown)
/// </summary>
internal static void Apply()
{
if (OurGlobalFontResolver == null || GlobalFontSettings.FontResolver == null)
{
if (OurGlobalFontResolver == null)
OurGlobalFontResolver = new MyFontResolver();
GlobalFontSettings.FontResolver = OurGlobalFontResolver;
}
}
}
/// <summary>
/// Helper class that reads font data from embedded resources.
/// </summary>
public static class FontHelper
{
public static byte[] Arial
{
get { return LoadFontData("MyProject.fonts.arial.arial.ttf"); }
}
public static byte[] ArialBold
{
get { return LoadFontData("MyProject.fonts.arial.arialbd.ttf"); }
}
public static byte[] ArialItalic
{
get { return LoadFontData("MyProject.fonts.arial.ariali.ttf"); }
}
public static byte[] ArialBoldItalic
{
get { return LoadFontData("MyProject.fonts.arial.arialbi.ttf"); }
}
/// <summary>
/// Returns the specified font from an embedded resource.
/// </summary>
static byte[] LoadFontData(string name)
{
var assembly = Assembly.GetExecutingAssembly();
// Test code to find the names of embedded fonts
//var ourResources = assembly.GetManifestResourceNames();
using (Stream stream = assembly.GetManifestResourceStream(name))
{
if (stream == null)
throw new ArgumentException("No resource with name " + name);
int count = (int)stream.Length;
byte[] data = new byte[count];
stream.Read(data, 0, count);
return data;
}
}
}
This is a single, complete and working class based on these two almost identical posts: this blog and this forum.
Use the latest version of PDFsharp (currently 1.50 beta 3) and implement IFontResolver.
See also:
https://stackoverflow.com/a/32489271/162529
https://stackoverflow.com/a/29059207/162529
The font may be installed on the server, but PDFsharp cannot read it.

How to create nested fields using C# Interop.Word (IF option with MailMerge Fields)

I am trying to set MailMerge fields and let Word fill them for me, which isn't a Problem at all... What i am looking to do and can't figure out is the following, I want to set 2 MailMergeFields in 1 place and let Word sort it out for me.
In this case have a mergefield for PO_Box and Adress, if there is a PO_Box # use it, otherwise use the Standard Adress.
Example of the MailMerge what it would look like in Word:
{ IF { MERGEFIELD PO_Box } > "1" "{ MERGEFIELD PO_Box }" "{ MERGEFIELD Adress }" \* MERGEFORMAT }
Is there a way to make this happen thru some Word Interop Funktion?
Edit:
static void Main(string[] args)
{
object fileName = #"C:\test.docx";
string dataSource = #"C:\Test.csv";
Word.Selection wrdSelection;
Word.MailMerge wrdMailMerge;
Word.MailMergeFields wrdMergeFields;
// Start Word Application
Word.Application wrdApp = new Word.Application();
//Load a document
Word.Document wrdDoc = wrdApp.Documents.Add(ref fileName, Visible: true);
wrdSelection = wrdApp.Selection;
wrdMailMerge = wrdDoc.MailMerge;
// Open Data Source from .csv file
wrdDoc.MailMerge.OpenDataSource(dataSource);
//Create MergeFields
wrdSelection.ParagraphFormat.Alignment = Word.WdParagraphAlignment.wdAlignParagraphLeft;
wrdSelection.ParagraphFormat.LineSpacingRule = Word.WdLineSpacing.wdLineSpaceSingle;
wrdSelection.ParagraphFormat.SpaceAfter = 0.0F;
wrdMergeFields = wrdMailMerge.Fields;
wrdMergeFields.Add(wrdSelection.Range, "Title");
wrdSelection.TypeText(" ");
wrdMergeFields.Add(wrdSelection.Range, "FirstName");
wrdSelection.TypeText(" ");
wrdMergeFields.Add(wrdSelection.Range, "LastName");
wrdSelection.TypeParagraph();
// Here I want to combine this Field with a PO_Box and let Word
// do the trick
wrdMergeFields.Add(wrdSelection.Range, "Address");
wrdSelection.TypeParagraph();
wrdMergeFields.Add(wrdSelection.Range, "City");
wrdSelection.TypeText(", ");
wrdMergeFields.Add(wrdSelection.Range, "State");
wrdSelection.TypeText(" ");
wrdMergeFields.Add(wrdSelection.Range, "Zip");
wrdSelection.ParagraphFormat.LineSpacingRule = Word.WdLineSpacing.wdLineSpaceDouble;
insertLines(wrdApp, 2);
//Right justify the line and insert a date field with current date.
wrdSelection.ParagraphFormat.Alignment = Word.WdParagraphAlignment.wdAlignParagraphRight;
object objDate = "dd.MM.yyyy";
wrdSelection.InsertDateTime(ref objDate);
//Preview the final merge
wrdDoc.MailMerge.Destination = Word.WdMailMergeDestination.wdSendToNewDocument;
wrdDoc.MailMerge.Execute();
//Close Template
object saveOption = Word.WdSaveOptions.wdDoNotSaveChanges;
wrdDoc.Close(ref saveOption);
//Shows the Application after the process to the User
wrdApp.Visible = true;
}
public static void insertLines(Word.Application wrdApp, int LineNum)
{
int iCount;
// Insert "LineNum" blank lines.
for (iCount = 1; iCount <= LineNum; iCount++)
{
wrdApp.Selection.TypeParagraph();
}
}
So this basicly what I have, now i need the Adress MergeField to behave as i described above, since I will receive a .csv data from another programm that i can't modify I would like to place this field in Word that it will sort out if there is a PO Box or Adress.
So, what you really want is to create the nested field codes. There are two basic approaches for this:
Record a macro while doing it as a user as the basis. This relies on the Selection object, which can be tricky; the approach is not scalable (only works for that specific combination). This is described on StackOverflow, so I won't repeat it here: Setting up a nested field in Word using VBA
Insert the field as a string, using "placeholders" to indicate
where the field codes are, then convert the placeholders to field
codes. This is scalable: it can be used for any combination of
fields. There is an excellent algorithm in C# posted on GitHub, by Florian Wolters in response to a discussion in which I participated on MSDN. I copy it below for convenience.
https://gist.github.com/FlorianWolters/6257233
//------------------------------------------------------------------------------
// <copyright file="FieldCreator.cs" company="Florian Wolters">
// Copyright (c) Florian Wolters. All rights reserved.
// </copyright>
// <author>Florian Wolters <wolters.fl#gmail.com></author>
//------------------------------------------------------------------------------
namespace FlorianWolters.Office.Word.Fields
{
using System;
using System.Collections;
using System.Runtime.InteropServices;
using Word = Microsoft.Office.Interop.Word;
/// <summary>
/// The class <see cref="FieldCreator"/> simplifies the creation of <see cref="Word.Field"/>s.
/// </summary>
public class FieldCreator
{
/// <summary>
/// Adds one or more new <see cref="Word.Field"/> to the specified <see cref="Word.Range"/>.
/// <para>
/// This method allows to insert nested fields at the specified range.
/// </para>
/// <example>
/// <c>InsertField(Application.Selection.Range, {{= {{PAGE}} - 1}};</c>
/// will produce
/// { = { PAGE } - 1 }
/// </example>
/// </summary>
/// <param name="range">The <see cref="Word.Range"/> where to add the <see cref="Word.Field"/>.</param>
/// <param name="theString">The string to convert to one or more <see cref="Word.Field"/> objects.</param>
/// <param name="fieldOpen">The special code to mark the start of a <see cref="Word.Field"/>.</param>
/// <param name="fieldClose">The special code to mark the end of a <see cref="Word.Field"/>.</param>
/// <returns>The newly created <see cref="Word.Field"/></returns>
/// <remarks>
/// A solution for VBA has been taken from this
/// article and adopted for C# by the author.
/// </remarks>
public Word.Field InsertField(
Word.Range range,
string theString = "{{}}",
string fieldOpen = "{{",
string fieldClose = "}}")
{
if (null == range)
{
throw new ArgumentNullException("range");
}
if (string.IsNullOrEmpty(fieldOpen))
{
throw new ArgumentException("fieldOpen");
}
if (string.IsNullOrEmpty(fieldClose))
{
throw new ArgumentException("fieldClose");
}
if (!theString.Contains(fieldOpen) || !theString.Contains(fieldClose))
{
throw new ArgumentException("theString");
}
// Special case. If we do not check this, the algorithm breaks.
if (theString == fieldOpen + fieldClose)
{
return this.InsertEmpty(range);
}
// TODO Implement additional error handling.
// TODO Possible to remove the dependency to state capture?
using (new StateCapture(range.Application.ActiveDocument))
{
Word.Field result = null;
Stack fieldStack = new Stack();
range.Text = theString;
fieldStack.Push(range);
Word.Range searchRange = range.Duplicate;
Word.Range nextOpen = null;
Word.Range nextClose = null;
Word.Range fieldRange = null;
while (searchRange.Start != searchRange.End)
{
nextOpen = this.FindNextOpen(searchRange.Duplicate, fieldOpen);
nextClose = this.FindNextClose(searchRange.Duplicate, fieldClose);
if (null == nextClose)
{
break;
}
// See which marker comes first.
if (nextOpen.Start < nextClose.Start)
{
nextOpen.Text = string.Empty;
searchRange.Start = nextOpen.End;
// Field open, so push a new range to the stack.
fieldStack.Push(nextOpen.Duplicate);
}
else
{
nextClose.Text = string.Empty;
// Move start of main search region onwards past the end marker.
searchRange.Start = nextClose.End;
// Field close, so pop the last range from the stack and insert the field.
fieldRange = (Word.Range)fieldStack.Pop();
fieldRange.End = nextClose.End;
result = this.InsertEmpty(fieldRange);
}
}
// Move the current selection after all inserted fields.
// TODO Improvement possible, e.g. by using another range object?
int newPos = fieldRange.End + fieldRange.Fields.Count + 1;
fieldRange.SetRange(newPos, newPos);
fieldRange.Select();
// Update the result of the outer field object.
result.Update();
return result;
}
}
/// <summary>
/// Adds a new empty <see cref="Word.Field"/> to the specified <see cref="Word.Range"/>.
/// </summary>
/// <param name="range">The <see cref="Word.Range"/> where to add the <see cref="Word.Field"/>.</param>
/// <param name="preserveFormatting">
/// Whether to apply the formatting of the previous <see cref="Word.Field"/> result to the new result.
/// </param>
/// <returns>The newly created <see cref="Word.Field"/>.</returns>
public Word.Field InsertEmpty(Word.Range range, bool preserveFormatting = false)
{
Word.Field result = this.AddFieldToRange(range, Word.WdFieldType.wdFieldEmpty, preserveFormatting);
// Show the field codes of an empty field, because otherwise we can't be sure that it is visible.
result.ShowCodes = true;
return result;
}
/// <summary>
/// Creates a <see cref="Word.Field"/> and adds it to the specified <see cref="Word.Range"/>
/// </summary>
/// <remarks>
/// The <see cref="Word.Field"/> is added to the <see cref="Word.Fields"/> collection of the specified <see
/// cref="Word.Range"/>.
/// </remarks>
/// <param name="range">The <see cref="Word.Range"/> where to add the <see cref="Word.Field"/>.</param>
/// <param name="type">The type of <see cref="Word.Field"/> to create.</param>
/// <param name="preserveFormatting">
/// Whether to apply the formatting of the previous field result to the new result.
/// </param>
/// <param name="text">Additional text needed for the <see cref="Word.Field"/>.</param>
/// <returns>The newly created <see cref="Word.Field"/>.</returns>
private Word.Field AddFieldToRange(
Word.Range range,
Word.WdFieldType type,
bool preserveFormatting = false,
string text = null)
{
return range.Fields.Add(
range,
type,
(null == text) ? Type.Missing : text,
preserveFormatting);
}
private Word.Range FindNextOpen(Word.Range range, string text)
{
Word.Find find = this.CreateFind(range, text);
Word.Range result = range.Duplicate;
if (!find.Found)
{
// Make sure that the next closing field will be found first.
result.Collapse(Word.WdCollapseDirection.wdCollapseEnd);
}
return result;
}
private Word.Range FindNextClose(Word.Range range, string text)
{
return this.CreateFind(range, text).Found ? range.Duplicate : null;
}
private Word.Find CreateFind(Word.Range range, string text)
{
Word.Find result = range.Find;
result.Execute(FindText: text, Forward: true, Wrap: Word.WdFindWrap.wdFindStop);
return result;
}
}
}

Windows Phone 7 Mango saving the state of a CookieContainer

update1: After more research I'm not sure this is possible, I created a UserVoice entry on fixing it.
I'm trying to save CookieContainer on app exit or when Tombstoning happens but I've run into some problems.
I've tried to save CookieContainer in the AppSettings but when loaded, the cookies are gone.
Researching this internally, DataContractSerializer cannot serialize cookies.
This seems to be a behavior that Windows Phone inherited from Silverlight's DataContractSerializer.
After doing more research it seemed like the work around was to grab the cookies from the container and save them another way. That worked fine until I hit another snag. I'm unable to GetCookies with a Uri of .mydomain.com. I belive it's because of this bug. I can see the cookie, .mydomain.com in the domaintable but GetCookies doesn't work on that particular cookie.
The bug is posted again here.
There is also a problem with getting cookies out of a container too
when the domain begins with a .:
CookieContainer container = new CookieContainer();
container.Add(new Cookie("x", "1", "/", ".blah.com"));
CookieCollection cv = container.GetCookies(new Uri("http://blah.com"));
cv = container.GetCookies(new Uri("http://w.blah.com"));
I found a work around for that using reflection to iterate the domaintable and remove the '.' prefix.
private void BugFix_CookieDomain(CookieContainer cookieContainer)
{
System.Type _ContainerType = typeof(CookieContainer);
var = _ContainerType.InvokeMember("m_domainTable",
System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.GetField |
System.Reflection.BindingFlags.Instance,
null,
cookieContainer,
new object[] { });
ArrayList keys = new ArrayList(table.Keys);
foreach (string keyObj in keys)
{
string key = (keyObj as string);
if (key[0] == '.')
{
string newKey = key.Remove(0, 1);
table[newKey] = table[keyObj];
}
}
}
Only, when InvokeMember is called a MethodAccessException is thrown in SL. This doesn't really solve my problem as one of the cookies I need to preserve is HttpOnly, which is one of the reasons for the CookieContainer.
If the server sends HTTPOnly cookies, you should create a
System.Net.CookieContainer on the request to hold the cookies,
although you will not see or be able to access the cookies that are
stored in the container.
So, any ideas? Am I missing something simple? Is there another way to save the state of the CookieContainer or do I need to save off the users info including password and re-authentic them every time the app starts and when coming back from tombstoning?
I have written a CookieSerializer that specifically address this issue. The serializer is pasted below. For a working project and scenario, please visit the project's CodePlex site.
public static class CookieSerializer
{
/// <summary>
/// Serializes the cookie collection to the stream.
/// </summary>
/// <param name="cookies">You can obtain the collection through your <see cref="CookieAwareWebClient">WebClient</see>'s <code>CookieContainer.GetCookies(Uri)</code>-method.</param>
/// <param name="address">The <see cref="Uri">Uri</see> that produced the cookies</param>
/// <param name="stream">The stream to which to serialize</param>
public static void Serialize(CookieCollection cookies, Uri address, Stream stream)
{
using (var writer = new StreamWriter(stream))
{
for (var enumerator = cookies.GetEnumerator(); enumerator.MoveNext();)
{
var cookie = enumerator.Current as Cookie;
if (cookie == null) continue;
writer.WriteLine(address.AbsoluteUri);
writer.WriteLine(cookie.Comment);
writer.WriteLine(cookie.CommentUri == null ? null : cookie.CommentUri.AbsoluteUri);
writer.WriteLine(cookie.Discard);
writer.WriteLine(cookie.Domain);
writer.WriteLine(cookie.Expired);
writer.WriteLine(cookie.Expires);
writer.WriteLine(cookie.HttpOnly);
writer.WriteLine(cookie.Name);
writer.WriteLine(cookie.Path);
writer.WriteLine(cookie.Port);
writer.WriteLine(cookie.Secure);
writer.WriteLine(cookie.Value);
writer.WriteLine(cookie.Version);
}
}
}
/// <summary>
/// Deserializes <see cref="Cookie">Cookie</see>s from the <see cref="Stream">Stream</see>,
/// filling the <see cref="CookieContainer">CookieContainer</see>.
/// </summary>
/// <param name="stream">Stream to read</param>
/// <param name="container">Container to fill</param>
public static void Deserialize(Stream stream, CookieContainer container)
{
using (var reader = new StreamReader(stream))
{
while (!reader.EndOfStream)
{
var uri = Read(reader, absoluteUri => new Uri(absoluteUri, UriKind.Absolute));
var cookie = new Cookie();
cookie.Comment = Read(reader, comment => comment);
cookie.CommentUri = Read(reader, absoluteUri => new Uri(absoluteUri, UriKind.Absolute));
cookie.Discard = Read(reader, bool.Parse);
cookie.Domain = Read(reader, domain => domain);
cookie.Expired = Read(reader, bool.Parse);
cookie.Expires = Read(reader, DateTime.Parse);
cookie.HttpOnly = Read(reader, bool.Parse);
cookie.Name = Read(reader, name => name);
cookie.Path = Read(reader, path => path);
cookie.Port = Read(reader, port => port);
cookie.Secure = Read(reader, bool.Parse);
cookie.Value = Read(reader, value => value);
cookie.Version = Read(reader, int.Parse);
container.Add(uri, cookie);
}
}
}
/// <summary>
/// Reads a value (line) from the serialized file, translating the string value into a specific type
/// </summary>
/// <typeparam name="T">Target type</typeparam>
/// <param name="reader">Input stream</param>
/// <param name="translator">Translation function - translate the read value into
/// <typeparamref name="T"/> if the read value is not <code>null</code>.
/// <remarks>If the target type is <see cref="Uri">Uri</see> , the value is considered <code>null</code> if it's an empty string.</remarks> </param>
/// <param name="defaultValue">The default value to return if the read value is <code>null</code>.
/// <remarks>The translation function will not be called for null values.</remarks></param>
/// <returns></returns>
private static T Read<T>(TextReader reader, Func<string, T> translator, T defaultValue = default(T))
{
var value = reader.ReadLine();
if (value == null)
return defaultValue;
if (typeof(T) == typeof(Uri) && String.IsNullOrEmpty(value))
return defaultValue;
return translator(value);
}
}
You cannot access private members outside of your assembly in WP7, even with Reflection. It's a security measure put in place to ensure you cannot call internal system APIs.
It looks like you may be out of luck.

What's the most efficient way to marshal C++ structs to C#?

I am about to begin reading tons of binary files, each with 1000 or more records. New files are added constantly so I'm writing a Windows service to monitor the directories and process new files as they are received. The files were created with a c++ program. I've recreated the struct definitions in c# and can read the data fine, but I'm concerned that the way I'm doing it will eventually kill my application.
using (BinaryReader br = new BinaryReader(File.Open("myfile.bin", FileMode.Open)))
{
long pos = 0L;
long length = br.BaseStream.Length;
CPP_STRUCT_DEF record;
byte[] buffer = new byte[Marshal.SizeOf(typeof(CPP_STRUCT_DEF))];
GCHandle pin;
while (pos < length)
{
buffer = br.ReadBytes(buffer.Length);
pin = GCHandle.Alloc(buffer, GCHandleType.Pinned);
record = (CPP_STRUCT_DEF)Marshal.PtrToStructure(pin.AddrOfPinnedObject(), typeof(CPP_STRUCT_DEF));
pin.Free();
pos += buffer.Length;
/* Do stuff with my record */
}
}
I don't think I need to use GCHandle because I'm not actually communicating with the C++ app, everything is being done from managed code, but I don't know of an alternative method.
Using Marshal.PtrToStructure is rather slow. I found the following article on CodeProject which is comparing (and benchmarking) different ways of reading binary data very helpful:
Fast Binary File Reading with C#
For your particular application, only one thing will give you the definitive answer: Profile it.
That being said here are the lessons I've learned while working with large PInvoke solutions. The most effective way to marshal data is to marshal fields which are blittable. Meaning the CLR can simple do what amounts to a memcpy to move data between native and managed code. In simple terms, get all of the non-inline arrays and strings out of your structures. If they are present in the native structure, represent them with an IntPtr and marshal the values on demand into managed code.
I haven't ever profiled the difference between using Marshal.PtrToStructure vs. having a native API dereference the value. This is probably something you should invest in should PtrToStructure be revealed as a bottleneck via profiling.
For large hierarchies marshal on demand vs. pulling an entire structure into managed code at a single time. I've run into this issue the most when dealing with large tree structures. Marshalling an individual node is very fast if it's blittable and performance wise it works out to only marshal what you need at that moment.
In addition to JaredPar's comprehensive answer, you don't need to use GCHandle, you can use unsafe code instead.
fixed(byte *pBuffer = buffer) {
record = *((CPP_STRUCT_DEF *)pBuffer);
}
The whole purpose of the GCHandle/fixed statement is to pin/fix the particular memory segment, making the memory immovable from GC's point of view. If the memory was movable, any relocation would render your pointers invalid.
Not sure which way is faster though.
This may be outside the bounds of your question, but I would be inclined to write a little assembly in Managed C++ that did an fread() or something similarly fast to read in the structs. Once you've got them read in, you can use C# to do everything else you need with them.
here's a small class i made a while back while playing with structured files. it was the fastest method i could figure out at the time shy of going unsafe (which was what i was trying to replace and maintain comparable performance.)
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
namespace PersonalUse.IO {
public sealed class RecordReader<T> : IDisposable, IEnumerable<T> where T : new() {
const int DEFAULT_STREAM_BUFFER_SIZE = 2 << 16; // default stream buffer (64k)
const int DEFAULT_RECORD_BUFFER_SIZE = 100; // default record buffer (100 records)
readonly long _fileSize; // size of the underlying file
readonly int _recordSize; // size of the record structure
byte[] _buffer; // the buffer itself, [record buffer size] * _recordSize
FileStream _fs;
T[] _structBuffer;
GCHandle _h; // handle/pinned pointer to _structBuffer
int _recordsInBuffer; // how many records are in the buffer
int _bufferIndex; // the index of the current record in the buffer
long _recordPosition; // position of the record in the file
/// <overloads>Initializes a new instance of the <see cref="RecordReader{T}"/> class.</overloads>
/// <summary>
/// Initializes a new instance of the <see cref="RecordReader{T}"/> class.
/// </summary>
/// <param name="filename">filename to be read</param>
public RecordReader(string filename) : this(filename, DEFAULT_STREAM_BUFFER_SIZE, DEFAULT_RECORD_BUFFER_SIZE) { }
/// <summary>
/// Initializes a new instance of the <see cref="RecordReader{T}"/> class.
/// </summary>
/// <param name="filename">filename to be read</param>
/// <param name="streamBufferSize">buffer size for the underlying <see cref="FileStream"/>, in bytes.</param>
public RecordReader(string filename, int streamBufferSize) : this(filename, streamBufferSize, DEFAULT_RECORD_BUFFER_SIZE) { }
/// <summary>
/// Initializes a new instance of the <see cref="RecordReader{T}"/> class.
/// </summary>
/// <param name="filename">filename to be read</param>
/// <param name="streamBufferSize">buffer size for the underlying <see cref="FileStream"/>, in bytes.</param>
/// <param name="recordBufferSize">size of record buffer, in records.</param>
public RecordReader(string filename, int streamBufferSize, int recordBufferSize) {
_fileSize = new FileInfo(filename).Length;
_recordSize = Marshal.SizeOf(typeof(T));
_buffer = new byte[recordBufferSize * _recordSize];
_fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.None, streamBufferSize, FileOptions.SequentialScan);
_structBuffer = new T[recordBufferSize];
_h = GCHandle.Alloc(_structBuffer, GCHandleType.Pinned);
FillBuffer();
}
// fill the buffer, reset position
void FillBuffer() {
int bytes = _fs.Read(_buffer, 0, _buffer.Length);
Marshal.Copy(_buffer, 0, _h.AddrOfPinnedObject(), _buffer.Length);
_recordsInBuffer = bytes / _recordSize;
_bufferIndex = 0;
}
/// <summary>
/// Read a record
/// </summary>
/// <returns>a record of type T</returns>
public T Read() {
if(_recordsInBuffer == 0)
return new T(); //EOF
if(_bufferIndex < _recordsInBuffer) {
// update positional info
_recordPosition++;
return _structBuffer[_bufferIndex++];
} else {
// refill the buffer
FillBuffer();
return Read();
}
}
/// <summary>
/// Advances the record position without reading.
/// </summary>
public void Next() {
if(_recordsInBuffer == 0)
return; // EOF
else if(_bufferIndex < _recordsInBuffer) {
_bufferIndex++;
_recordPosition++;
} else {
FillBuffer();
Next();
}
}
public long FileSize {
get { return _fileSize; }
}
public long FilePosition {
get { return _recordSize * _recordPosition; }
}
public long RecordSize {
get { return _recordSize; }
}
public long RecordPosition {
get { return _recordPosition; }
}
public bool EOF {
get { return _recordsInBuffer == 0; }
}
public void Close() {
Dispose(true);
}
void Dispose(bool disposing) {
try {
if(disposing && _fs != null) {
_fs.Close();
}
} finally {
if(_fs != null) {
_fs = null;
_buffer = null;
_recordPosition = 0;
_bufferIndex = 0;
_recordsInBuffer = 0;
}
if(_h.IsAllocated) {
_h.Free();
_structBuffer = null;
}
}
}
#region IDisposable Members
public void Dispose() {
Dispose(true);
}
#endregion
#region IEnumerable<T> Members
public IEnumerator<T> GetEnumerator() {
while(_recordsInBuffer != 0) {
yield return Read();
}
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
return GetEnumerator();
}
#endregion
} // end class
} // end namespace
to use:
using(RecordReader<CPP_STRUCT_DEF> reader = new RecordReader<CPP_STRUCT_DEF>(path)) {
foreach(CPP_STRUCT_DEF record in reader) {
// do stuff
}
}
(pretty new here, hope that wasn't too much to post... just pasted in the class, didn't chop out the comments or anything to shorten it.)
It seems this has nothing to do with neither C++ nor marshalling. You know the structure what else do you need.
Obviously you need a simple code which will read group of bytes representing one struct and then using BitConverter to place bytes into corresponding C# fields..

Categories

Resources