How to read a HTML page from a remote site using VBA/.NET into a Htmlocument0 Reacties

 

There are a lot of ways to read and parse HTML, the better tricks, don’t use IE itself, since this will deliver automation errors and waste memory.

I’m for 99% of my time into .NET programming, but still, one of my hobbies use an Access 2013 database and thus, a VBA codebase, yummy! And to get powerfeatures, I compiled a tlb to have interfaces like IPersistStreamInit, IStream etc. (it’s called odl compiling and requires  MkTypLib.EXE, not midl.exe!)

Now here is a neat way to fetch/get a plain HTML text and load it into a HTMLDocument without any dependency on IE automation. You’re a smart non-lazy programmer (right?) so you get the idea for C# as well since you need IPersistStreamInit there as well. It’s COM interop, dude!

Public Function HttpGet(ByRef url As String) As mshtml.HTMLDocument
    Dim xmlHttp As MSXML2.ServerXMLHTTP60
    Set xmlHttp = CreateObject("MSXML2.ServerXMLHTTP")
    xmlHttp.Open "GET", url, False
    xmlHttp.send
   'set return value
    Set HttpGet = New HTMLDocument
    Dim stream As adodb.stream
    Set stream = CreateObject("ADODB.Stream")
    Dim istrea As IPersistStreamInit
   
   'get interface IPersistStreamInit from HTMLDocument
    Set istrea = HttpGet
   
   'write the muke using a binary array (bytes)
    stream.Type = adTypeBinary
    stream.Open
    stream.write xmlHttp.responseBody
   'reset stream
    stream.position = 0
    'load the muke into the HTMLDocument
    istrea.Load stream

    Dim s As Single
    s = Timer

   'fake body onload ready
    Do Until Timer - s > 10 Or HttpGet.ReadyState = "complete"
        DoEvents        
    Loop

End Function

SiteMaps made easy0 Reacties

 

If you’re a site admin or asp.net developer for an internet site, you certainly need to look into sitemaps, if you want to perform SEO.

It’s not necessary to  simply crawl your own site and then to give every page a priority, but consider this for a forum or other pages which are irregularly or often updated. If you don’t want to have crawlers do unneeded roundtrips, implement a sitemap.

‘robots.txt’ should contain a reference to your map eg. Sitemap: http://www.myfantasticsite.com/sitemap.xml

Ideas of this class, written using C#, can be found anywhere on the net. However, as some might know me, I like it to be finished and neat and a self-supporting class ready for usage (e.g. it must not be written to a string to add or remove wished attributes that the serializer could not handle).

The following things are solved. 
Since  ‘changefreq’ and ‘lastmod’ and ‘priority’ are optional values, you don’t want the XmlSerializer to create empty tags!
This is done by adding a DefaultValue attribute. It will cause XmlSerializer to check the current value against the default value. If they are equal, it is considered to be an empty non existing tag. Remember, that the defaultvalues need to be out of the range of possible values! Therefore, EnumChangeFreq contains an extra member ‘notused’
Remember, the Xml.Serialization name space, offers the tools to get it done without converting your loading your XML in to some XmlDocument class.

You can use the class as follows.
UrlSet retVal = new UrlSet();

retVal.AddUrl(new Url() { LastModifiedDateTime = DateTime.Now.ToUniversalTime(), Loc = “http://www.myfantasticsite.com/blah.aspx”) });

Retrieve the XML sitemap string.
string xml = null;
using (var io = (MemoryStream)retVal.ToStream())
{
                xml =  new UTF8Encoding().GetString(io.ToArray());
}

or, to write it directly to an output stream

using (var io = (MemoryStream)retVal.ToStream())
{

//todo: Deal with Response.Cache, etag and last modified to avoid unnecessary round trips.
Response.ContentType = “text/xml”;
Response.CharSet = “utf-8”;
Response.BinaryWrite(retVal.ToArray());

}

using System;
using System.Xml;
using System.Xml.Serialization;
using System.ComponentModel;
using System.IO;

namespace adccure
{

    public enum EnumChangeFreq
    {
        notset,
        always,
        hourly,
        daily,
        weekly,
        monthly,
        yearly,
        never
    }

[

[XmlRoot(ElementName = "urlset", Namespace = SCHEMA_SITEMAP)]
public sealed class UrlSet
{
    [XmlNamespaceDeclarations]
    public XmlSerializerNamespaces xmlns;
    private const string XSI_NAMESPACE = "http://www.w3.org/2001/XMLSchema-instance";
    private const string SCHEMA_SITEMAP = "http://www.sitemaps.org/schemas/sitemap/0.9";

    private Url[] _url;

    public UrlSet()
    {
        _url = new Url[0];
        xmlns = new XmlSerializerNamespaces();
        xmlns.Add("xsi", XSI_NAMESPACE);
        SchemaLocation = SCHEMA_SITEMAP + " " + "http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd";

    }
    [XmlAttribute(AttributeName = "schemaLocation", Namespace = XSI_NAMESPACE)]
    public string SchemaLocation;

    public void AddUrl(Url url)
    {
        int l = _url.Length + 1;
        Array.Resize(ref _url, l);
        _url[l - 1] = url;
    }

    [XmlElement(ElementName = "url")]
    public Url[] url
    {
        get { return _url; }
        set { _url = value; } //bogus
    }
    ///


    /// serializes the UrlSet to a sitemap.xsd conform string ready for saving to disk.
    ///

    /// a Stream object
    public Stream ToStream()
    {
        XmlSerializer xmlser = new XmlSerializer(GetType());
        var io = new MemoryStream();
        xmlser.Serialize(new StreamWriter(io, Encoding.UTF8), this);
        io.Position = 0;
        return io;
    }
}

    public sealed class Url
    {
        private string _loc;
        private DateTime _lastmod;
        private float _priority;
        private EnumChangeFreq _changefreq;

        public Url()
        {
            //setting defaults
            _changefreq = EnumChangeFreq.notset;
            _priority = 0.0F;
            _lastmod = DateTime.MinValue;
        }

        [XmlElement(ElementName = "loc")]
        public string Loc
        {
            get
            {
                return _loc;
            }

            set
            {
                if (string.IsNullOrEmpty(value))
                {
                    throw new ArgumentNullException();
                }
                if (value.Length < 12 || value.Length > 2048)
                {
                    throw new ArgumentException("loc must be between 12 and 2048 in length");
                }
                _loc = value;
            }
        }
        [XmlElement(ElementName = "lastmod"), DefaultValue(typeof(DateTime), "1-1-0001")]
        public DateTime LastModifiedDateTime
        {
            get
            {
                return _lastmod;
            }

            set
            {
                _lastmod = new DateTime(value.Year, value.Month, value.Day, value.Hour, value.Minute, value.Second, value.Kind);

            }
        }
        [XmlElement(ElementName = "changefreq")]
        [DefaultValue(EnumChangeFreq.notset)]
        public EnumChangeFreq ChangeFreq
        {
            get
            {
                return _changefreq;
            }

            set
            {
                _changefreq = value;
            }
        }
        [XmlElement(ElementName = "priority")]
        [DefaultValue(0.0F)]
        public float Priority
        {
            get
            {
                return _priority;
            }

            set
            {
                if (value < 0 || value > 1.0)
                {
                    throw new ArgumentException("Must be between 0 and 1");
                }
                _priority = value;
            }
        }
    }
}

Tags van Technorati: ,,

Howto: Create a custom numeric pager for the ASP.NET Gridview Control0 Reacties

Figure 1: Our custom pager in action!

I never have liked the concept of storing all the data in whatever form (DataTable/Lists of records/etc.) to the ASP.NET gridview control and having it automatically manage paging for me. This could however be improved using Visual Studio 2008 wizards. However, this requires writing stored procedures.

I’ve got a concept for you, which works without a lot of extra work. The concept is:

  1. Inherit from asp:GridView and override the PageCount and the PageIndex properties
  2. Create in instance of my CustomPager class at DataBinding time.

The result is as shown in figure 1: It adheres to PageButtonCount, to any styles that you have applied to the GridView and it features a ‘jump to’ page input box.
The ‘native’ event handling in your ASPX, still can be maintained by this code since it emulates the PageIndexChanging event.

Other solutions, implement an AJAX updatepanel per row. This really minimizes unnecessary refreshing of grid data.
However, I don’t mind if say, 30 rows are being pulled from a DB and bound to a GridView. If we do so, we also get the best of two worlds, in one world, we get all data and have a fresh update of the real table rows, and in the other world, we have just the active row being fetched and we could end up having outdated data on our screen because of (for instance) co-writers/typers who updated rows at the database which are not reflected at our screen in grid (which is displayed using a gridview).

So, in other words, I like this pager control since it is a balanced solution! It has been tested on 10,000 records. WHen I would page to for instance, page 900, it really is a matter of fractions of a second to get a response. (For paging solutions  on SQL server data tables which contain say millions of rows, we would need a more sophisticated approach).

Here is the control source (it’s called gridhelper.cs)! (In theory, it should work for .NET 2.0 up to 3.5)

// if you use this code, please leave the original author
//author: Egbert Nierop
using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace adccure.tools
{
    public sealed class GridView2 : GridView
    {
        public GridView2()
            : base()
        {
        }
        private int _pageCount;

        public override int PageIndex
        {
            get
            {
                object o = ViewState["pgi"];
                return o == null ? 0 : (int)o;
            }
            set
            {
                ViewState["pgi"] = value;
            }
        }

        public override int PageCount
        {
            get
            {
                return _pageCount;
            }

        }
        public void PageCountSet(int pageCount)
        {
            _pageCount = pageCount;
        }
    }

    public sealed class CustomPager : ITemplate
    {
        readonly Table tbl;
        readonly GridView2 _grid;

        //readonly int _totalPages;
        public CustomPager(GridView2 grid, int totalPages)
        {
            tbl = new Table();
            grid.PageCountSet(totalPages);
            tbl.Width = Unit.Percentage(100);
            _grid = grid;
        }
        void ITemplate.InstantiateIn(Control container)
        {
            container.Controls.Add(tbl);

            int pageSize = _grid.PageSize;
            int pageIndex = _grid.PageIndex;
            int pageButtonCount = _grid.PagerSettings.PageButtonCount;
            int pageCount = _grid.PageCount;
            ClientScriptManager cs = _grid.Page.ClientScript;
            _grid.PagerSettings.Visible = true;
            var trow = new TableRow();
            var trowpagePosition = new TableRow();
            tbl.Rows.Add(trow);
            tbl.Rows.Add(trowpagePosition);
            TextBox tb = new TextBox();
            tb.ID = "txtJumpPage";
            tb.MaxLength = 4;
            tb.Width = Unit.Pixel(40);
            tb.Text = (pageIndex + 1).ToString();
            //avoid bubble up by return false
            tb.Attributes["onkeydown"] = string.Format("if (event.keyCode==13) {__doPostBack('{0}', 'Page$' + this.value ); return false;}", _grid.UniqueID);

            LiteralControl lit = new LiteralControl(string.Format(" of {0}", (pageCount + 1).ToString()));
            TableCell posCaption = new TableCell();
            trowpagePosition.Cells.Add(posCaption);
            posCaption.Controls.Add(tb);
            posCaption.Controls.Add(lit);
            int cellspan = 0;
            if (pageIndex > 0)
            {
                var cellText = new TableCell();
                trow.Cells.Add(cellText);
                cellspan++;
                cellText.Controls.Add(new HyperLink()
                {
                    NavigateUrl = cs.GetPostBackClientHyperlink(_grid,
                    string.Format("Page${0}", pageIndex - pageButtonCount >= 0 ? (pageIndex - pageButtonCount) + 1 : 1), false),
                    Text = "<"
                });
            }
            for (int x = pageIndex; x < pageIndex + pageButtonCount && x <= pageCount; x++)
            {
                var cellText = new TableCell();
                cellspan++;
                trow.Cells.Add(cellText);
                cellText.Controls.Add(new HyperLink()
                {
                    NavigateUrl = cs.GetPostBackClientHyperlink(_grid,
                        string.Format("Page${0}", x + 1), false),
                    Text = (x + 1).ToString(),
                });

            }
            if (pageIndex + pageButtonCount < pageCount)
            {
                var cellText = new TableCell();
                cellspan++;
                trow.Cells.Add(cellText);

                cellText.Controls.Add(new HyperLink()
                {
                    NavigateUrl = cs.GetPostBackClientHyperlink(_grid,
                    string.Format("Page${0}", (pageIndex + pageButtonCount) + 1), false),
                    Text = ">"
                });
            }
            tbl.Visible = true;
            posCaption.HorizontalAlign = HorizontalAlign.Center;
            posCaption.ColumnSpan = cellspan;
        }
    }
}

Now, I don’t publish the code to read from sample data (such as northwind), it would be a yadda, yadda and you know the drill. (In my code, it is just a silly HttpReferrer table having all the columns which allow you research this specific statistical interest of your web site)
But in my datalayer, I have things like shown below. It is a great solution for those tables, for say, less than 100,000 records. SQL server is able to deal with these types of queries pretty well and we still avoid pumping around lots of redundant data on the network and gridview control.

public IList getHttpReferrers(int pPage, int pPageSize, HttpReferrerSortOrder sortOrder,
                SortDirection sortDirection,
                out int totalRecords)
        {
            totalRecords = dcd.HttpReferrers.Count();
            IQueryable retVal = null;
            if (sortDirection ==  SortDirection.Ascending)
            {
                switch (sortOrder)
                {
                    case HttpReferrerSortOrder.Referer:
                        retVal = dcd.HttpReferrers.OrderBy(t => t.Referrer);
                        break;
                    case HttpReferrerSortOrder.IP:
                        retVal = dcd.HttpReferrers.OrderBy(t => t.IP_Address);
                        break;
                    case HttpReferrerSortOrder.Page:
                        retVal = dcd.HttpReferrers.OrderBy(t => t.page);
                        break;
                    default:
                        retVal = dcd.HttpReferrers.OrderBy(t => t.ts);
                        break;
                }
            }
            else
            {
                switch (sortOrder)
                {
case HttpReferrerSortOrder.Referer:
                        retVal = dcd.HttpReferrers.OrderByDescending(t => t.Referrer);
                        break;
                    case HttpReferrerSortOrder.IP:
                        retVal = dcd.HttpReferrers.OrderByDescending(t => t.IP_Address);
                        break;
                    case HttpReferrerSortOrder.Page:
                        retVal = dcd.HttpReferrers.OrderByDescending(t => t.page);
                        break;
                    default:
                        retVal = dcd.HttpReferrers.OrderByDescending(t => t.ts);
                        break;
                }
            }
            return retVal.Skip(pPage * pPageSize).Take(pPageSize).ToList();
        }

So, our Grid, can sort and it can page.

How do we deal with the paging event at the code behind the aspx?
It’s so simple!

void gridHttpReferrers_PageIndexChanging(object sender, GridViewPageEventArgs e) 

gridHttpReferrers.PageIndex = e.NewPageIndex; 
gridHttpReferrers.DataBind(); 
}

void gridHttpReferrers_DataBinding(object sender, EventArgs e)

{   int totalRecords; 
   int pageSize = gridHttpReferrers.PageSize; 
   int pageIndex = gridHttpReferrers.PageIndex; 
  gridHttpReferrers.DataSource = datadal.getHttpReferrers(pageIndex, pageSize, SortOrder, SortDir, out totalRecords); 
   gridHttpReferrers.PagerTemplate = new CustomPager(gridHttpReferrers, totalRecords); 
   gridHttpReferrers.PageCountSet (totalRecords / pageSize);
}

What did I do to get the new grid behavior inside the ASPX?
Just rename the tag from asp:GridView to ctrl:GridView2 and create the reference (or in web.config)

<%@ Register TagPrefix="ctrl" Namespace="adccure.tools" %>

<ctrl:GridView2 runat="server" emptydatatext="No data available."  ID="gridHttpReferrers" AllowPaging="true" AllowSorting="True"  Width ="100%" AutoGenerateColumns="false" PageSize="20" DataKeyNames="ID"><PagerSettings Position="Top" PageButtonCount="20" />
<
Columns> ETC.

So, I hope this code was quite enlightning for you and you can play with it and have fun.

Howto: Properly use the AccessCheck API for the current user0 Reacties

I've seen people pulling the hair out for not getting this API workign for them.

The API, even if impersonating the current user, returns error 1309. "An attempt has been made to operate on an impersonation token by a thread that is not currently impersonating a client."

The clue is that this API, (and this is not clearly documented on the MSDN) needs a duplicated handle.

Anyway, spare your hair, have fun with the code. B.t.w. You can hire me for smart code and research on components etc..

http://www.adccure.nl for contact.

    [StructLayout(LayoutKind.Sequential)]

    internal struct GENERIC_MAPPING

    {

        internal uint GenericRead;

        internal uint GenericWrite;

        internal uint GenericExecute;

        internal uint GenericAll;

    }

    [DllImport("advapi32.dll", SetLastError = false)]

    static extern void MapGenericMask([In, MarshalAs(UnmanagedType.U4)] ref TokenAccessLevels AccessMask,

        [In] ref GENERIC_MAPPING map);

   

    [DllImport("advapi32.dll", SetLastError = true)]

    [return: MarshalAs(UnmanagedType.Bool)]

    public static extern bool DuplicateToken(IntPtr ExistingTokenHandle,

            [MarshalAs(UnmanagedType.U4)] TokenImpersonationLevel level,

            out int DuplicateTokenHandle);

    [DllImport("advapi32.dll", SetLastError = true)]       

     [return: MarshalAs(UnmanagedType.Bool)] static extern bool AccessCheck(

      [MarshalAs(UnmanagedType.LPArray)]

        byte[] pSecurityDescriptor, 

      IntPtr ClientToken,

      [MarshalAs(UnmanagedType.U4)]

        TokenAccessLevels accessmask,

      [In] ref GENERIC_MAPPING GenericMapping,

      IntPtr PrivilegeSet,

      ref int PrivilegeSetLength,

      out uint GrantedAccess,

      [MarshalAs(UnmanagedType.Bool)]

      out bool AccessStatus);

    [DllImport("kernel32")]

    static extern void CloseHandle(IntPtr ptr);

  internal static bool hasReadAccess(string path)

    {

        // Obtain the authenticated user's Identity

       

        WindowsIdentity winId = WindowsIdentity.GetCurrent(TokenAccessLevels.Duplicate | TokenAccessLevels.Query);

       

        WindowsImpersonationContext ctx = null;

        int statError = 0;

        IntPtr dupToken = IntPtr.Zero;

        try

        {

            // Start impersonating

            //ctx = winId.Impersonate(); works but AccessCheck does not like this

           

            int outPtr;

            //AccessCheck needs a duplicated token!

            DuplicateToken(winId.Token, TokenImpersonationLevel.Impersonation, out outPtr);

           

            dupToken = new IntPtr(outPtr);

            ctx = WindowsIdentity.Impersonate(dupToken);                

            Folder.GENERIC_MAPPING map = new Folder.GENERIC_MAPPING();

            map.GenericRead = 0x80000000;

            map.GenericWrite = 0x40000000;

            map.GenericExecute = 0x20000000;

            map.GenericAll = 0x10000000;

            TokenAccessLevels required = TokenAccessLevels.Query | TokenAccessLevels.Read | TokenAccessLevels.AssignPrimary | (TokenAccessLevels)0x00100000; // add synchronization

            MapGenericMask(ref required, ref map);

           

           

            uint status = 0;

            bool accesStatus = false;

            // dummy area the size should be 20 we don't do anything with it

            int sizeps = 20;

            IntPtr ps = Marshal.AllocCoTaskMem(sizeps);

           

            //AccessControlSections.Owner | AccessControlSections.Group MUST be included,

            //otherwise the descriptor would be seen with ERROR 1338

            var ACE = Directory.GetAccessControl(path,

                AccessControlSections.Access | AccessControlSections.Owner |

                    AccessControlSections.Group);

           

            bool success = AccessCheck(ACE.GetSecurityDescriptorBinaryForm(), dupToken, required, ref map,

                    ps, ref sizeps, out status, out accesStatus);

            Marshal.FreeCoTaskMem(ps);

            if (!success)

            {

                statError = Marshal.GetLastWin32Error();

            }

            else

            {

                return accesStatus;

            }

        }

        // Prevent exceptions from propagating

        catch (Exception ex)

        {

            Trace.Write(ex.Message);

        }

        finally

        {

            // Revert impersonation

           

            if (ctx != null)

                ctx.Undo();

            CloseHandle(dupToken);

        }

        if (statError != 0)

        {

            throw new Win32Exception(statError);

        }

       

        return false;

    }

This code is just a cut and paste. You can make it pretty.

Howto: [asp.net] Use Request.LogonUserIdentity to fetch the current user's Active Directory DirectoryEntry?0 Reacties

If you have a website using ASP pages or asp.net pages, and you want to integrate user management with active directory, you’ll have a lot of extra technology that you need to make yourself known with. 

 

Add this article to my Live favorites

 

What you mostly would do, is disable anonymous web access, and have users login, using credentials stored at Active Directory.

 

But unfortunately, IIS, Internet Information Server, on Windows 2003 and 2000, does map only the current logged on user through an NT4 domain format, which looks like ‘DOMAIN\johnd’ retrieved from Request.ServerVariables(“LOGON_USER”) or Request.LogonUserIdentity.Name, will equal exactly that name in NT 4 format. You can also logon to an IIS website using a user principal  (in AD, this the attribute userPrincipalName like 'johnd@domain') but we need to be sure that the identity -always- can be handled, no matter what syntax is given by the user at logon.

 

So from there you need to translate the NT4 name to a distinguished Name (dn is like  “cn=itsme,cn=users,dc=nwtraders,dc=msft”)  that is suitable for Active Directory.

 

Now comes an often made ‘trick’ which is obviously slow (see for a listing, completely below)! 

One could do a Directory Search on sAMAccountName=’johnd’ and get the active, logged on user and its active directory record. <- don't do this!

 

A very fast method would be as follows.
note: this is assuming that you are using the DotNet framework version 2.0 and c#. The very same effective code could be written for Vb.Net.

 

 

protected void Page_Load(object sender, EventArgs e)

{

// identical to Request.LogonUserIdentity but using a static method instead of a Page property
//WindowsIdentity wi = System.Security.Principal.WindowsIdentity.GetCurrent();

wi = System.Security.Principal.WindowsIdentity.GetCurrent();

        WindowsIdentity wi = Request.LogonUserIdentity;

        DirectoryEntry dir = new DirectoryEntry("LDAP:// + SidToHex(wi.User) + ">");

        

        dir = new DirectoryEntry("LDAP://" + (string)dir.Properties["distinguishedName"][0], null, null,

            AuthenticationTypes.Secure |

            AuthenticationTypes.ReadonlyServer);

 

 

//Now you got ‘dir’ at your disposal and you can read the current users profile information (for instance)!

 

}

 

To get Request.LogonUserIdentity initialized after a browser logon to your aspx pages, with the correct user-logon-info,

configure web.Config as follows so that it contains at least the configuration seen below.

 

web.Config

xml version="1.0" encoding="utf-8"?>

<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">

    <system.web>

            <compilation defaultLanguage="c#" debug="true">

            <assemblies>

            <add assembly="System.DirectoryServices, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>

                  <add assembly="System.Security, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>

            assemblies>

 

        <authentication mode="Windows" />

        <identity impersonate="true"/>

    system.web>

configuration>

 

    ///

    ///  needed for Windows 2000 compatibility

    /// Windows 2003 can bind using a S-1-xx-x-xx-xxx- format

    ///

    ///

    ///

    private static string SidToHex(SecurityIdentifier sid)

    {

        int binLength = sid.BinaryLength;

        byte[] bt = new byte[binLength];

        sid.GetBinaryForm(bt, 0);

        System.Text.StringBuilder retval = new System.Text.StringBuilder(binLength * 2, binLength * 2);

        for (int cx = 0; cx < binLength; cx++)       

            retval.Append(bt[cx].ToString("X2"));

        return retval.ToString();

    }

Bad performing often used code, not to be used!

string ldapPath = "LDAP://" + userDomain;
DirectoryEntry rootEntry = new DirectoryEntry(ldapPath);
using (DirectorySearcher ds = new DirectorySearcher(rootEntry, "(samAccountName=" + userName + ")"))
{
            SearchResult result = ds.FindOne();
            if (result != null)
            {
                        ResultPropertyValueCollection resultValues = result.Properties["displayName"];
                        if (resultValues.Count > 0)
                        {
                                    Label1.Text = (string) resultValues[0];
                        }
                        else
                        {
                                    Label1.Text = "No display name"; 
                        }
            }
}

Howto use Paypal Instant Payment Notification (IPN) with character encoding0 Reacties

The C# sample at the IPN website, is a little bit outdated, since paypal currently supports international  character sets (great!) so if a chinese customer would enter his name in Chinese for instance :), it still would be kept ok. So to not corrupt the characters, we have to stick the same format as paypal sends us. This would be simple if the page would be a web service form, it is not, so we have to wrap the POST request and do the encoding all yourselves.

using System;

using System.Collections;

using System.Collections.Specialized;

using System.IO;

using System.Text;

using System.Net;

using System.ComponentModel;

using System.Data;

using System.Web;

 

using CSession;

namespace pp

{

      ///

      /// Summary description for ipn.

      ///

      public partial class ipn : System.Web.UI.Page

      {

            private bool isTesting;

            // this page runs as LCID 1033 english

            // and is tested on codepage 65001. you must –enable- the codepage

            // on your paypal preferences!

            // note that the shopping basket, is based on ISP Session from

            // http://www.nieropwebconsult.nl/asp_session_manager.htm

            // it integrates with classic asp as well as .NET

            protected void Page_Load(object sender, System.EventArgs e)

            {

                 

                  NameValueCollection rq = Request.Form;

                  //Response.BufferOutput = false;

                  string test_ipn = rq["test_ipn"];

                  isTesting = test_ipn != null && test_ipn == "1"? true: false;

                  // Create the request back

// instead of pp_url you could read https://www.paypal.com/cgi-bin/webscr

                  HttpWebRequest req = (HttpWebRequest) WebRequest.Create(

      System.Configuration.ConfigurationSettings.AppSettings["pp_url"]);

           

                  string version = rq["notify_version"]; // ' 2.1 currently

                  // Set values for the request back

                  req.Method = "POST";

                  string notifyCmd = "cmd=_notify-validate";

                  req.ContentType = "application/x-www-form-urlencoded";     

            // the charset will be utf-8 if set as preference on paypal!

            Encoding enc = System.Text.Encoding.GetEncoding(rq["charset"]);

           

            StreamWriter tw = new StreamWriter(new MemoryStream());

            int keyCount = rq.Count;

            // here goes our first command

            tw.Write(notifyCmd);

            //tricky part. If you do Request.Form.ToString() the & and the = is encoded as well!

            for (int xx = 0; xx < keyCount; xx++)

            {  

                tw.Write('&');

                tw.Write(HttpUtility.UrlEncodeToBytes(rq.GetKey(xx), enc));

                tw.Write('=');

                tw.Write(HttpUtility.UrlEncodeToBytes(rq.Get(xx), enc));

            }

           

            int contLen = (int)tw.BaseStream.Position;

            req.ContentLength = contLen;

            // Write the request back IPN strings in binary form

            // do not use a TextWriter since that would cause

            // encoding on encoding which obviously will fail

            byte[] temp = new byte[contLen];

            tw.BaseStream.Position = 0;

            tw.BaseStream.Read(temp, 0, contLen);

            Stream stOut = req.GetRequestStream();

            stOut.Write(temp, 0, contLen);

            stOut.Flush();           

 

            // Do the request to PayPal and get the response

            StreamReader stIn = new StreamReader(req.GetResponse().GetResponseStream(), enc);

                  string strResponse = stIn.ReadToEnd();

                  stIn.Close();

                  string Item_number, Payment_status, Txn_id, sessionId;

                  Item_number = rq["item_number"];

                  sessionId = rq["invoice"];

                  Txn_id = rq["txn_id"];

                 

                  db dbl = new db();

                  dbl.sqlConn.Open();

                 

                  DataSet1.tLicenseRow lic =  dbl.dataSet11.tLicense.NewtLicenseRow();

                  lic.CtryISO2Code = rq["address_country_code"];

                  lic.email = rq["payer_email"];

                  //TODO: lic should be fullfilled

                  lic.sessionID = new Guid(sessionId).ToByteArray();

                  lic.transactionID = Guid.NewGuid();

            lic.salesBlob = rq.ToString().Replace("&", "\r\n");

                  lic.saleDate = DateTime.Now;

                  lic.total_value = decimal.Parse(rq[