Angel Hernández

Assembler Language in .NET and some other ideas

Hi Community,

This post is about something not very recent in .NET and it’s been available for a while, but the first time I read about it I couldn’t blog it because I didn’t have a blog back then Stick out tongue and since I’m currently reading this book once again "The art of assembly language" (I think that I’m reading it for the third time… It just contains too much information) so I thought it’s worthy to mention it. Even when .NET is  managed code, in the end the CPU understands  machine code which can be represented through  assembler language. This happens the same with the rest of other languages (managed and non-managed). Back in  2001, someone had the brilliant idea of doing  ASP.NET with  assembler language.  The article can be found  here and below a sample page is depicted

The few things I learned about assembler language was back in 1995, thanks to my mate Virginia when she was about to earn her degree in Bsc. Systems Engineering and I was a freshman in Uni. I used to spend a lot of time with Microsoft C/C++ 7.0 (MFC 1.0 was bundled in it) and I thought it was cool to place inline code inside  __asm, at the same time I was learning VB 4.0 and in my spare time I played  Heretic and MK II... I remember my mom calling me to go and have lunch Stick out tongue *sigh* What a wonderful time!!!... There’s one thing I’ve been trying to keep from that time and throughout the years and it’s been like a golden rule: "whatever you do, do it with passion and… there’s no a better way to learn something new than by playing with it”  and that’s probably why I never get bored of PCs, technology and related stuff.

Changing of topics, some days ago one of our customers had a new requirement “I’d like to detect when an active session is about to expire and store the existing cart in CRM", this is something very easy and straightforward to do in ASP.NET, where I can put some code in the Global.asax and intercept the Session_End event but what I’m talking about is a SharePoint based solution with form based authentication enabled and as we all MUST know by now, SharePoint supports this authentication scheme since  version 2007, because it has always relied on  NTLM and Kerberos.  Anyways I had a couple of approaches which I mention below: 

  • Every single SharePoint application is an instance of SPHttpApplication which in turn inherits from HttpApplication, so I said  "Nah... this is a piece of cake", I just need to modify the Global.asax and write some inline code to handle Session_End  but it doesn’t work Sad so I thought about creating a new project and explicit inherit from SPHttpApplication but it doesn’t work either…  So I gave it a try to another alternative…

  • Write an HttpModule which contains a singleton and at the same time it keeps a data structure updated and in sync by a  critical section at the moment of a session timing out, code is shown below
  • using System;
    using System.ComponentModel;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Web;
    using Microsoft.SharePoint;
    using Microsoft.SharePoint.ApplicationRuntime;
    using System.IO;
    using Microsoft.SharePoint.Security;
    using System.Runtime;
    using System.Runtime.InteropServices;
    using System.Web.SessionState;
     
     
    namespace Demo {
        public class MyCustomApplication : IHttpModule {
            #region IHttpModule Members
            private SyncedInfo _synced = null;
            private GCHandle? _allocatedMem = null;
     
            private GCHandle? AllocatedMemory {
                get {
                    return _allocatedMem;
                }
                set {
                    _allocatedMem = value;
                }
            }
     
            private SyncedInfo Singleton {
                get {
                    return _synced;
                }
                set {
                    _synced = value;
                }
            }
     
            public void Dispose() {
                if (AllocatedMemory != null) {
                    AllocatedMemory.Value.Free();
                }
            }
     
            public void Init(HttpApplication context) {
                if (Singleton == null) {
                    Singleton = new SyncedInfo(context.Modules["Session"]);
                    AllocatedMemory = GCHandle.Alloc(Singleton);
                }
            }
            #endregion
        }
     
        [Serializable]
        public class SyncedInfo {
            protected SessionStateModule _sessionModule;
            protected HttpContext _currentContext = HttpContext.Current;
     
            public SyncedInfo(IHttpModule context) {
                _sessionModule = (SessionStateModule)context;
                _currentContext.Application["UserData"] = new Dictionary<string, string>();
                _sessionModule.Start += Session_Start;
                _sessionModule.End += Session_End;
            }
     
            public bool Push(string customer, string selectedData) {
                bool retval = false;
                object userData = _currentContext.Application["UserData"];
     
                try {
                    lock (userData) {
                        if (!((Dictionary<string, string>)userData).ContainsKey(customer)) {
                            ((Dictionary<string, string>)userData).Add(customer, selectedData);
                        } else {
                            ((Dictionary<string, string>)userData)[customer] = selectedData;
                        }
                        retval = true;
                    }
                } catch { } // Swallow the exception
                return retval;
            }
     
            public KeyValuePair<string, string>? Pop(string customer) {
                KeyValuePair<string, string>? retval = null;
                object userData = _currentContext.Application["UserData"];
     
                try {
                    lock (userData) {
                        if (((Dictionary<string, string>)userData).ContainsKey(customer)) {
                            retval = new KeyValuePair<string, string>(customer, ((Dictionary<string, string>)userData)[customer]);
                            ((Dictionary<string, string>)userData).Remove(customer);
                        }
                    }
                } catch { retval = null; } // Swallow the exception
                return retval;
            }
     
            private void Session_End(object sender, EventArgs e) {
                // Serialize data here (Pop)
     
                SPSecurity.RunWithElevatedPrivileges(new SPSecurity.CodeToRunElevated(delegate() {
                    WriteActivity(false);
                }));
     
            }
     
            private void Session_Start(object sender, EventArgs e) {
                // Push should be call from the App (UI)
     
                SPSecurity.RunWithElevatedPrivileges(new SPSecurity.CodeToRunElevated(delegate() {
                    WriteActivity(true);
                }));
            }
     
            private void WriteActivity(bool state) {
                string fileName = @"c:\log.txt";
     
                try {
                    using (StreamWriter logWriter = (!File.Exists(fileName) ? File.CreateText(fileName) : File.AppendText(fileName))) {
                        logWriter.WriteLine(string.Format("Session {0} at {1}", new object[] { (state ? "Started" : "Ended"), DateTime.Now }));
                        logWriter.Flush();
                        logWriter.Close();
                    }
                } catch { } // Swallow exception
            }
        }
    }

This approach should work, it does as a matter of fact, when Session_Start is triggered but it doesn’t fire when the session expires and in my humble opinion this is quite annoying because  SharePoint is taking away from me something that ASP.NET provides, this is valid when security relies on Windows and “a valid ticket” is always available, but what about form based authentication?

In conclusion, I can do ASP.NET with assembler language (according to the previously mentioned article) but I can’t intercept any event related to a  session timing out in SharePoint Sad  How cool is that!!!!

Regards,

Angel

Lenguaje Ensamblador en .NET y algunas otras ideas

Hola Comunidad,

El siguiente post es sobre algo no tan nuevo en .NET que leí hace un tiempo atrás, pero para ese entonces no tenía blog Stick out tongue y como actualmente estoy leyendo de nuevo este libro "El arte del lenguaje ensamblador" (creo que por tercera vez, porque contiene demasiada información) pensé que sería interesante mencionarlo. Aunque .NET es código gestionado al final de cuentas lo que entiende el procesador es lenguaje de máquina que puede representarse simbólicamente a traves del lenguaje Ensamblador, lo mismo pasa con los demás lenguajes (gestionados y no gestionados). En el año 2001 ha alguien se le ocurrió la idea de hacer ASP.NET usando lenguaje ensamblador.  El artículo completo lo pueden ver acá y abajo pueden ver un ejemplo de como luce el código de la página

Lo poco que aprendí sobre lenguaje Ensamblador fue por allá en el año 1995, gracias a mi amiga Virginia que ya estaba por graduarse de Ingeniero de Sistemas y yo estaba recién entrando en universidad. Me la pasaba horas haciendo cosas con Microsoft C/C++ 7.0 (que traía consigo MFC 1.0) por lo que me parecía chévere poner código inline dentro del __asm al mismo tiempo estaba aprendiendo VB 4.0 y en los ratos libres jugando Heretic y MK II... Mientras mi mamá me peleaba para que fuera a comer Stick out tongue que buenos tiempos aquellos... de verdad que si... Una cosa que he tratado mantener con los años es eso de "hacer las cosas con pasión y aprender jugando" por eso creo que no me aburro de la computadora.

Cambiando de tema, en días pasados un cliente hizo un requerimiento, "quiero detectar cuando la sesión haya expirado y guardar el carrito de compras en CRM", claro... en ASP.NET puedo escribir algo en el Global.asax e interceptar el evento Session_End y es sencillo, pero esto es una solución basada en SharePoint que hace uso de autenticación basada en formas y como es bien sabido SharePoint viene a soportar este tipo de autenticación  a partir de la versión 2007, porque siempre ha utilizado NTLM y Kerberos para ello. En fin, se me ocurrieron un par de ideas las cuales menciono a continuación:

  • Toda aplicación basada en SharePoint es una instancia de SPHttpApplication que a la vez deriva de HttpApplication, al estar consciente de esto me dije "Nah... sencillo" con modificar el Global.asax y escribir código para el evento Session_End lo consigo... No funciona Sad entonces pensé en crear un proyecto y explicitamente derivar de SPHttpApplication  y tampoco funciona... Por lo que pensé en otra alternativa...

  • Crear un HttpModule que contiene un singleton y este a su vez mantiene una estructura que mediante una región crítica permite su actualización al “expirar alguna sesión”, el código es mostrado a continuación
  • using System;
    using System.ComponentModel;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Web;
    using Microsoft.SharePoint;
    using Microsoft.SharePoint.ApplicationRuntime;
    using System.IO;
    using Microsoft.SharePoint.Security;
    using System.Runtime;
    using System.Runtime.InteropServices;
    using System.Web.SessionState;
     
     
    namespace Demo {
        public class MyCustomApplication : IHttpModule {
            #region IHttpModule Members
            private SyncedInfo _synced = null;
            private GCHandle? _allocatedMem = null;
     
            private GCHandle? AllocatedMemory {
                get {
                    return _allocatedMem;
                }
                set {
                    _allocatedMem = value;
                }
            }
     
            private SyncedInfo Singleton {
                get {
                    return _synced;
                }
                set {
                    _synced = value;
                }
            }
     
            public void Dispose() {
                if (AllocatedMemory != null) {
                    AllocatedMemory.Value.Free();
                }
            }
     
            public void Init(HttpApplication context) {
                if (Singleton == null) {
                    Singleton = new SyncedInfo(context.Modules["Session"]);
                    AllocatedMemory = GCHandle.Alloc(Singleton);
                }
            }
            #endregion
        }
     
        [Serializable]
        public class SyncedInfo {
            protected SessionStateModule _sessionModule;
            protected HttpContext _currentContext = HttpContext.Current;
     
            public SyncedInfo(IHttpModule context) {
                _sessionModule = (SessionStateModule)context;
                _currentContext.Application["UserData"] = new Dictionary<string, string>();
                _sessionModule.Start += Session_Start;
                _sessionModule.End += Session_End;
            }
     
            public bool Push(string customer, string selectedData) {
                bool retval = false;
                object userData = _currentContext.Application["UserData"];
     
                try {
                    lock (userData) {
                        if (!((Dictionary<string, string>)userData).ContainsKey(customer)) {
                            ((Dictionary<string, string>)userData).Add(customer, selectedData);
                        } else {
                            ((Dictionary<string, string>)userData)[customer] = selectedData;
                        }
                        retval = true;
                    }
                } catch { } // Swallow the exception
                return retval;
            }
     
            public KeyValuePair<string, string>? Pop(string customer) {
                KeyValuePair<string, string>? retval = null;
                object userData = _currentContext.Application["UserData"];
     
                try {
                    lock (userData) {
                        if (((Dictionary<string, string>)userData).ContainsKey(customer)) {
                            retval = new KeyValuePair<string, string>(customer, ((Dictionary<string, string>)userData)[customer]);
                            ((Dictionary<string, string>)userData).Remove(customer);
                        }
                    }
                } catch { retval = null; } // Swallow the exception
                return retval;
            }
     
            private void Session_End(object sender, EventArgs e) {
                // Serialize data here (Pop)
     
                SPSecurity.RunWithElevatedPrivileges(new SPSecurity.CodeToRunElevated(delegate() {
                    WriteActivity(false);
                }));
     
            }
     
            private void Session_Start(object sender, EventArgs e) {
                // Push should be call from the App (UI)
     
                SPSecurity.RunWithElevatedPrivileges(new SPSecurity.CodeToRunElevated(delegate() {
                    WriteActivity(true);
                }));
            }
     
            private void WriteActivity(bool state) {
                string fileName = @"c:\log.txt";
     
                try {
                    using (StreamWriter logWriter = (!File.Exists(fileName) ? File.CreateText(fileName) : File.AppendText(fileName))) {
                        logWriter.WriteLine(string.Format("Session {0} at {1}", new object[] { (state ? "Started" : "Ended"), DateTime.Now }));
                        logWriter.Flush();
                        logWriter.Close();
                    }
                } catch { } // Swallow exception
            }
        }
    }

Esta solución debería funcionar, de hecho lo hace cuando Session_Start es disparado pero no cuando la sesión expira lo cual me molesta pues SharePoint me está quitando algo ofrece ASP.NET,  quizás esto no aplica cuando la seguridad está basada en Windows pero puede darse el caso con autenticación basada en formas sí.

Entonces es posible hacer ASP.NET con lenguaje Ensamblador (según el artículo antes mencionado) pero no puedo interceptar el cierre de una sesión en SharePoint Sad  Que les parece!!!!

Saludos,

Angel

Upcoming MSDN Webcast: A lap around Microsoft ASP.NET 4.0 and Microsoft Visual Studio 2010 / Próximo Webcast en MSDN: Una vuelta por Microsoft ASP.NET 4.0 y Microsoft Visual Studio 2010

Hi Community,

I'll be presenting the following MSDN Webcast (Saturday, 28th November 2009 @ 7:00 AM - Australia Time)

I look forward to seeing you there Wink

Regards,

Angel


Hola Comunidad,

Estaré presentando el siguiente MSDN Webcast (Viernes, 27 de Noviembre de 2009 (05:00 p.m. - Hora Colombia) - (05:30 p.m. - Hora Venezuela))

Espero verlos ese día Wink

Saludos,

Angel

Visual Studio Documentary / Documental de Visual Studio

Hi Community,

Grabbed from Channel 9

Regards,

Angel


Hola Comunidad,

Tomado de  Channel 9

Saludos,

Angel


 

Part I

Click here to play this video

Part II

Click here to play this video

Angel on dnrTV / Angel en dnrTV (dotNet Rocks TV)

Hi Community,

I just finished my presentation on dnrTV, thanks to Carl Franklin and Scott Hanselman. This session was about "Using Native Code in .NET" and we covered

  • Hooks      
  • Memory Mapped Files

However, I'm planning to organize some other sessions to cover a bit more about this topic (e.g.: Virtual Memory, Device I/O, Error Reporting and Application Recovery and more). I'll keep you updated on this.

Please, feel free to download the source code attached to this post

Regards,

Angel


Hola Comunidad,

Acabo de terminar mi presentación para  dnrTV, gracias a  Carl Franklin y Scott Hanselman. El tópico de esta sesión fue  "Using Native Code in .NET" y cubrimos

  • Hooks      
  • Memory Mapped Files

Sin embargo, estoy planeando organizar otras sesiones para hablar un poco más de este tema  (por ejemplo, Virtual Memory, Device I/O, Error Reporting and Application Recovery and more). Los mantendré informados al respecto.

Por favor, siéntanse libres de descargar el código adjunto a este post

Saludos,

Angel

WWSAPI (Windows Web Services API) + Interop + WPF… How cool is that!!!

A couple of nights ago I was reading about some of the new features in Windows 7 SDK and I can honestly say “They’re a lot”, as a matter of fact, I mentioned some of them in one of my previous Tech-Ed session this year. Despite of all these new features, there’s one that got my attention from the beginning and it was WWSAPI, because there wasn’t any support for Web Service from native code, except for a couple of existing toolkits including gSOAP that can be implemented  on Windows, Linux and Mac OSX.  WWSAPI was first introduced at  PDC 2008 and it will be formally released along Windows 7, although previous versions of the operating system starting from XP SP2 can get it via Windows Update when becomes available. I first tested it when Windows 7 was still RC but I didn’t post about it because I was expecting for Windows 7 to be released or about to be released.

Having said that, last weekend I created a solution to demo  WWSAPI in conjunction with .NET. The solution structure is depicted below

Solution 

  • NativeTester: Console application (C++) which calls our Dynamic Link Library (DLL)
  • RSSFeedService: Web Service (C#) which retrieves RSS feeds, parse them and returns a well-formed XML
  •  RSSViewer: WPF application (C#) which consumes the Web Service plus invokes our Dynamic Link Library (DLL)
  • Tester: WinForm application (C#) which consumes the Web Service plus invokes our Dynamic Link Library (DLL)
  • WWSAPIDemo: Dynamic Link Library (C++) which implements  WWSAPI and it’s invoked from .NET via Interop

Along with the code you can find MSDN.xml which contains the RSS feeds from MSDN Australia, even when the code references  http://localhost/MSDN.xml, you can change the Url and retrieve any given RSS, in my case I deployed the previously mentioned file to my local IIS.

Many of you might be wondering about, how can I generate a proxy based on the Web Service via C++?  and the answer is quite simple, the new Windows 7 SDK provides us with an utility that does this for us, WSUTIL.exe. After executing it, we have as a result two files (an .H and a .CPP). The strings are treated as  WCHAR* by default which in turn is Unicode and every single string in .NET are Unicode as well, I’ll comment a bit more about this later.  

wsutil

Having the Web Service published already on IIS, we better start working on the Dynamic Link Library which implements WWSAPI. Below we can see its header file

#include "stdafx.h"
 
#define EXPORT extern "C" __declspec(dllexport)
 
#define MAX_SIZE  128000 //128Kb
#define TRIM_SIZE 512
 
EXPORT WCHAR* GetFeeds(WCHAR* szUrl, int cbResults);
 
EXPORT void GetItem(WCHAR* szUrl, WCHAR* szItemName);
And this is the method responsible for connecting against the Web Service and retrieves the RSS feeds 
// Get feeds from a given Url through WWSAPI
EXPORT WCHAR* GetFeeds(WCHAR* szUrl, int cbResults) {
    WCHAR* temp = NULL;
    WS_HEAP* heap = NULL;
    WCHAR retval[MAX_SIZE]; // 128Kb (It should be enough to avoid WS_E_QUOTA_EXCEEDED error)
    WS_ERROR* error = NULL;
    ULONG propertiesCount = 0;
    WS_SERVICE_PROXY* proxy = NULL;
    WS_CHANNEL_PROPERTY channelProps[2]; 
    WS_STRING serviceUrl = WS_STRING_VALUE(L"http://localhost/DemoSvc/RSSFeedService.asmx");
    WS_ENDPOINT_ADDRESS endpoint = { serviceUrl}; 
    WS_ENVELOPE_VERSION soapVersion = WS_ENVELOPE_VERSION_SOAP_1_1; // Our Webservice is WsiProfiles.BasicProfile1_1 compliant
    WS_ADDRESSING_VERSION addressingVersion = WS_ADDRESSING_VERSION_TRANSPORT;
 
    // Set channel's properties
    channelProps[propertiesCount].id = WS_CHANNEL_PROPERTY_ENVELOPE_VERSION;
    channelProps[propertiesCount].value = &soapVersion;
    channelProps[propertiesCount].valueSize = sizeof(soapVersion);
    propertiesCount++;
 
    // Set addressing's properties
    channelProps[propertiesCount].id = WS_CHANNEL_PROPERTY_ADDRESSING_VERSION;
    channelProps[propertiesCount].value = &addressingVersion;
    channelProps[propertiesCount].valueSize = sizeof(addressingVersion);
    propertiesCount++;
 
    // Can we create an WsError and WsHeap objects?
    if (SUCCEEDED(WsCreateError(NULL, NULL, &error)) && SUCCEEDED(WsCreateHeap(MAX_SIZE, TRIM_SIZE, NULL, NULL, &heap, error))) {
        // Can we create a proxy based on the service?
        if  (SUCCEEDED(WsCreateServiceProxy(WS_CHANNEL_TYPE_REQUEST, WS_HTTP_CHANNEL_BINDING, NULL, NULL, NULL, 
            channelProps, propertiesCount, &proxy, error))) {
                // Can we open the proxy object?
                if  (SUCCEEDED(WsOpenServiceProxy(proxy, &endpoint, NULL, error))) {
                    // If we're able to invoke the service then copy the results to another variable because if
                    // we don't we lose the response after freeing the heap
                    if (SUCCEEDED(RSSFeedServiceSoap12_RetrieveFeeds(proxy, szUrl, cbResults, &temp, heap, NULL, NULL, NULL, error))) 
                        wcscpy_s(retval, temp);
                }
        }
    }
 
    // Deallocate and free resources
    if (error != NULL)
        WsFreeError(error);
 
    if (proxy != NULL) {
        WsCloseServiceProxy(proxy, NULL, error);
        WsFreeServiceProxy(proxy);
    }
 
    if (heap != NULL)
        WsFreeHeap(heap);
 
    return retval;
}

Please note the following:

  • We must specify the version of SOAP to use (otherwise, the client is going to complain about it, because it will use 1.2, as evidence check the RSSFeedServiceSoap12 method name) 
  • I return a WCHAR* and it works without issues, even when the right way to do it is returning an HRESULT or an integer, accept a pointer as an argument which at the same time it’s an output parameter. There’s a document about best practices for creating DLLs which can be downloaded from here

Our implementation from the console application is shown below

#include "stdafx.h"
 
typedef WCHAR* (*myCallback) (WCHAR*, int);
 
int _tmain(int argc, _TCHAR* argv[]) {
    HINSTANCE hInstance;
    myCallback ptrToFunc;
    WCHAR* results = NULL;
 
    if ((hInstance = LoadLibrary(L"C:\\Users\\angel.hernandez\\Desktop\\WWSAPI\\WWSAPIDemo\\x64\\Debug\\WWSAPIDemo.dll")) != NULL) {
        if ((ptrToFunc = (myCallback) GetProcAddress(hInstance, "GetFeeds")) != NULL) {
            results = ptrToFunc(L"http://localhost/MSDN.xml", -1);
            wprintf(L"\n%ls\n", results);
            FreeLibrary(hInstance);
            printf("\n\nPress any key to exit...\n");
            _getch();
        }
    }
    return 0;
}

After compiling, linking and executing our NativeTester application, we can see how the RSS feeds are displayed on the console.

NativeTester

GetItem is another exported function in our DLL which displays a found element (if any) after executing an  XPath query and using the starts-with function (as if it was the Like % operator). GetItem retrieves the RSS feeds by previously calling the GetFeeds function.

// Execute XPATH Query based on feeds retrieved through WWSAPI
EXPORT void GetItem(WCHAR* szUrl, WCHAR* szItemName) {
    WCHAR retval[TRIM_SIZE];
    WCHAR* results = NULL;
    BSTR nodeContent = NULL;
    WCHAR xPathQueryBuffer[TRIM_SIZE];
    IXMLDOMDocumentPtr docPtr = NULL;
    IXMLDOMNodePtr selectedNode = NULL;
 
    if ((results = GetFeeds(szUrl, -1)) != NULL &&  wcslen(results) > 0) {
        CoInitialize(NULL);
 
        docPtr.CreateInstance("Msxml2.DOMDocument.6.0");
 
        wsprintf(xPathQueryBuffer, L"/rss/items/item[starts-with(@title,'%ls')]", szItemName);
 
        if (SUCCEEDED(docPtr->loadXML(_bstr_t(results), NULL))) {
            if (SUCCEEDED(docPtr->selectSingleNode(_bstr_t(xPathQueryBuffer), &selectedNode))) {
                nodeContent = SysAllocString(retval);
                selectedNode->get_xml(&nodeContent);
                MessageBox(NULL, nodeContent, L"XPath Query Results", NULL);
                SysFreeString(nodeContent);
            }
        }
    }
}

So far we’ve been able to implement and use WWSAPI from native code, however we haven’t tested our Dll’s  functionality  from .NET, in that case the first thing to do is, “to import” the function we’re interested on by specifying the DllImport attribute.

[DllImport(@"C:\Users\angel.hernandez\Desktop\WWSAPI\WWSAPIDemo\x64\Debug\WWSAPIDemo.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr GetFeeds(IntPtr szUrl, int cbResults);

The GetFeeds function returns a  WCHAR* which in turn is interpreted by .NET as an IntPtr. We also need to use the Marshal class to convert it from  WCHAR* to an Unicode string and to pass a  WCHAR* as parameter to the function

private void btnTestWWSAPI_Click(object sender, EventArgs e) {
    IntPtr szUrl = Marshal.StringToBSTR("http://localhost/MSDN.xml");
    MessageBox.Show(Marshal.PtrToStringUni(GetFeeds(szUrl, -1)));
    Marshal.FreeBSTR(szUrl);
}

and Voila!!! I have my WWSAPI implementation being consumed and used from .NET, well.. from a WinForm application, but what about a WPF application? The answer is… the implementation remains the same except for the  XAML code required plus the databinding  process is quite simple if an  XmlDataProvider is used because the data is extracted and assigned based on an XPath syntax.

<!-- Data Provider-->
<Window.Resources>
    <XmlDataProvider x:Key="xmlFeeds" IsAsynchronous="True" XPath="/rss/items/item"/>
</Window.Resources>
 
<Grid Name="grdFeeds" Margin="0,75,0,12">
    <!-- Binding Source-->
    <ListView Margin="11.432,6" Name="lstFeeds"  
             ItemsSource="{Binding Source={StaticResource xmlFeeds} }" MouseDoubleClick="lstFeeds_MouseDoubleClick">
        <ListView.View>
            <GridView x:Name="grdFeedItems">
                <GridViewColumn Header="Title" x:Name="grcTitle" Width="100" DisplayMemberBinding="{Binding XPath=@title}"/>
                <GridViewColumn Header="Publishing Date" x:Name="grcPublishingDate" Width="100" DisplayMemberBinding="{Binding XPath=@publishingDate}"/>
                <GridViewColumn Header="Url" x:Name="grcUrl" Width="100" DisplayMemberBinding="{Binding XPath=@url}" />
            </GridView>
        </ListView.View>
    </ListView>
</Grid>
 

The Button Click event handler is shown below

private void btnExecuteOperation_Click(object sender, RoutedEventArgs e) {
    XmlDocument feeds = new XmlDocument();
    IntPtr szUrl = IntPtr.Zero, szItemName = IntPtr.Zero;
    XmlDataProvider xmlFeeds = TryFindResource("xmlFeeds") as XmlDataProvider;
 
    try {
        switch (cboOperations.SelectedIndex) {
            case 0:
                using (RSSFeedService proxy = new RSSFeedService())
                    feeds.LoadXml(proxy.RetrieveFeeds("http://localhost/MSDN.xml", -1));
                xmlFeeds.Document = feeds;
                break;
            case 1:
                szUrl = Marshal.StringToBSTR("http://localhost/MSDN.xml");
                feeds.LoadXml(Marshal.PtrToStringUni(GetFeeds(szUrl, -1)));
                Marshal.FreeBSTR(szUrl);
                xmlFeeds.Document = feeds;
                break;
            case 2:
                szUrl = Marshal.StringToBSTR("http://localhost/MSDN.xml");
                szItemName = Marshal.StringToBSTR(!string.IsNullOrEmpty(txtItemTitle.Text) ? txtItemTitle.Text : "Windows");
                GetItem(szUrl, szItemName);
                Marshal.FreeBSTR(szUrl);
                Marshal.FreeBSTR(szItemName);
                break;
        }
    } catch (Exception ex) {
        MessageBox.Show(string.Format("Oops! Something wrong just occurred\n{0}", ex.Message),
            "Exception caught", MessageBoxButton.OK, MessageBoxImage.Information);
    }
}

Attached to this post you can find the code, please feel free to download it, modify it and play with it. I hope you consider this information useful and always remember one thing… The only possible way to learn is by playing with the technology and feel confident about taking risks.

God bless you

Regards,

Angel

WWSAPI (Windows Web Services API) + Interop + WPF… Chévere!!!

Hace un par de noches atrás estaba leyendo sobre las características nuevas del SDK de Windows 7 y puedo decir que  son bastantes, de hecho en una de las sesiones que tuve en Tech-Ed este año mencioné algunas de ellas. Sin embargo, una que llamó mi atención fue WWSAPI pues hasta ahora no existía soporte para código nativo, excepto por un par de Toolkits existentes entre ellos gSOAP que puede utilizarse con Windows, Linux y Mac OSX. WWSAPI fue presentado en el PDC del año pasado (2008) y será liberado formalmente con Windows 7 aunque versiones anteriores del sistema operativo, a partir de XP SP2. Yo lo probé inicialmente cuando Windows 7 estaba en RC sin embargo no había posteado al respecto por esperar que Windows 7 fuese liberado o cerca de serlo.

Una vez dicho esto, durante el fin de semana creé una solución para demostrar WWSAPI en conjunto con .NET. La solución tiene la estructura mostrada a continuación

Solution 

  • NativeTester: Aplicación de Consola (C++) que llama a la biblioteca de enlace dinámico (DLL)
  • RSSFeedService: Servicio Web (C#) que recupera entradas RSS, las parsea y regresa el resultado como XML
  •  RSSViewer: Aplicación basada en WPF (C#) que consume el servicio Web e invoca a la biblioteca de enlace dinámico (DLL)
  • Tester: Aplicación basada en WinForm (C#) que consume el servicio Web e invoca a la biblioteca de enlace dinámico (DLL)
  • WWSAPIDemo: Biblioteca de enlace dinámico (C++) que implementa WWSAPI y es invocada desde .NET a través de Interop

Así mismo con el código pueden encontrar MSDN.xml que son las entradas RSS de la página de MSDN Australia, aunque el código apunta a http://localhost/MSDN.xml, ustedes pueden cambiar dicho Url y apuntar al RSS que gusten, en mi caso guardé el archivo de entradas RSS y lo pusé en my IIS local.

Muchos de ustedes se preguntarán, ¿cómo genero el proxy del servicio Web desde C++? Y la respuesta es muy simple, el nuevo SDK de Windows 7 trae consigo un utilitario que lo hace por nosotros,  WSUTIL.exe. La ejecución nos da como resultado dos archivos (uno .H y otro .CPP). Las cadenas por defecto son tratadas como WCHAR* que es Unicode y todas las cadenas en .NET son interpretadas como Unicode, más adelante comentaré un poco de esto.  

wsutil

Con el servicio Web ya publicado en IIS, entonces nos queda comenzar a trabajar en la biblioteca de enlace dinámico que será cliente e implementará WWSAPI. A continuación el archivo de cabecera de la biblioteca

#include "stdafx.h"
 
#define EXPORT extern "C" __declspec(dllexport)
 
#define MAX_SIZE  128000 //128Kb
#define TRIM_SIZE 512
 
EXPORT WCHAR* GetFeeds(WCHAR* szUrl, int cbResults);
 
EXPORT void GetItem(WCHAR* szUrl, WCHAR* szItemName);
así como el método que se conecta al servicio Web y recupera las entradas de RSS
// Get feeds from a given Url through WWSAPI
EXPORT WCHAR* GetFeeds(WCHAR* szUrl, int cbResults) {
    WCHAR* temp = NULL;
    WS_HEAP* heap = NULL;
    WCHAR retval[MAX_SIZE]; // 128Kb (It should be enough to avoid WS_E_QUOTA_EXCEEDED error)
    WS_ERROR* error = NULL;
    ULONG propertiesCount = 0;
    WS_SERVICE_PROXY* proxy = NULL;
    WS_CHANNEL_PROPERTY channelProps[2]; 
    WS_STRING serviceUrl = WS_STRING_VALUE(L"http://localhost/DemoSvc/RSSFeedService.asmx");
    WS_ENDPOINT_ADDRESS endpoint = { serviceUrl}; 
    WS_ENVELOPE_VERSION soapVersion = WS_ENVELOPE_VERSION_SOAP_1_1; // Our Webservice is WsiProfiles.BasicProfile1_1 compliant
    WS_ADDRESSING_VERSION addressingVersion = WS_ADDRESSING_VERSION_TRANSPORT;
 
    // Set channel's properties
    channelProps[propertiesCount].id = WS_CHANNEL_PROPERTY_ENVELOPE_VERSION;
    channelProps[propertiesCount].value = &soapVersion;
    channelProps[propertiesCount].valueSize = sizeof(soapVersion);
    propertiesCount++;
 
    // Set addressing's properties
    channelProps[propertiesCount].id = WS_CHANNEL_PROPERTY_ADDRESSING_VERSION;
    channelProps[propertiesCount].value = &addressingVersion;
    channelProps[propertiesCount].valueSize = sizeof(addressingVersion);
    propertiesCount++;
 
    // Can we create an WsError and WsHeap objects?
    if (SUCCEEDED(WsCreateError(NULL, NULL, &error)) && SUCCEEDED(WsCreateHeap(MAX_SIZE, TRIM_SIZE, NULL, NULL, &heap, error))) {
        // Can we create a proxy based on the service?
        if  (SUCCEEDED(WsCreateServiceProxy(WS_CHANNEL_TYPE_REQUEST, WS_HTTP_CHANNEL_BINDING, NULL, NULL, NULL, 
            channelProps, propertiesCount, &proxy, error))) {
                // Can we open the proxy object?
                if  (SUCCEEDED(WsOpenServiceProxy(proxy, &endpoint, NULL, error))) {
                    // If we're able to invoke the service then copy the results to another variable because if
                    // we don't we lose the response after freeing the heap
                    if (SUCCEEDED(RSSFeedServiceSoap12_RetrieveFeeds(proxy, szUrl, cbResults, &temp, heap, NULL, NULL, NULL, error))) 
                        wcscpy_s(retval, temp);
                }
        }
    }
 
    // Deallocate and free resources
    if (error != NULL)
        WsFreeError(error);
 
    if (proxy != NULL) {
        WsCloseServiceProxy(proxy, NULL, error);
        WsFreeServiceProxy(proxy);
    }
 
    if (heap != NULL)
        WsFreeHeap(heap);
 
    return retval;
}

Por favor, nótese lo siguiente:

  • Debemos especificar la versión de SOAP (de lo contrario, el cliente se va a quejar al respecto, porque va a utilizar 1.2. Prueba de esto, es el nombre del método RSSFeedServiceSoap12) 
  • Regreso un WCHAR* y funciona sin problemas, aunque la manera correcta debería ser es regresar un HRESULT o un entero, tomar un puntero como parámetro que al mismo tiempo sirve de valor de retorno. Un documento sobre las mejores prácticas de desarrollo de DLLs puede encontrarse aquí

Nuestra implementación desde la aplicación de consola es mostrada a continuación

#include "stdafx.h"
 
typedef WCHAR* (*myCallback) (WCHAR*, int);
 
int _tmain(int argc, _TCHAR* argv[]) {
    HINSTANCE hInstance;
    myCallback ptrToFunc;
    WCHAR* results = NULL;
 
    if ((hInstance = LoadLibrary(L"C:\\Users\\angel.hernandez\\Desktop\\WWSAPI\\WWSAPIDemo\\x64\\Debug\\WWSAPIDemo.dll")) != NULL) {
        if ((ptrToFunc = (myCallback) GetProcAddress(hInstance, "GetFeeds")) != NULL) {
            results = ptrToFunc(L"http://localhost/MSDN.xml", -1);
            wprintf(L"\n%ls\n", results);
            FreeLibrary(hInstance);
            printf("\n\nPress any key to exit...\n");
            _getch();
        }
    }
    return 0;
}

Al compilar y ejecutar nuestra aplicación de prueba NativeTester, podemos ver como se muestran las entradas recuperadas en la consola.

NativeTester

La otra función contenida en la biblioteca de enlace dinámico es GetItem, que muestra el elemento encontrado tras la ejecución de una consulta de XPath y haciendo uso de la función starts-with (como si fuera el operador Like %). La función GetItem recupera las entradas RSS al llamar previamente a la función GetFeeds.

// Execute XPATH Query based on feeds retrieved through WWSAPI
EXPORT void GetItem(WCHAR* szUrl, WCHAR* szItemName) {
    WCHAR retval[TRIM_SIZE];
    WCHAR* results = NULL;
    BSTR nodeContent = NULL;
    WCHAR xPathQueryBuffer[TRIM_SIZE];
    IXMLDOMDocumentPtr docPtr = NULL;
    IXMLDOMNodePtr selectedNode = NULL;
 
    if ((results = GetFeeds(szUrl, -1)) != NULL &&  wcslen(results) > 0) {
        CoInitialize(NULL);
 
        docPtr.CreateInstance("Msxml2.DOMDocument.6.0");
 
        wsprintf(xPathQueryBuffer, L"/rss/items/item[starts-with(@title,'%ls')]", szItemName);
 
        if (SUCCEEDED(docPtr->loadXML(_bstr_t(results), NULL))) {
            if (SUCCEEDED(docPtr->selectSingleNode(_bstr_t(xPathQueryBuffer), &selectedNode))) {
                nodeContent = SysAllocString(retval);
                selectedNode->get_xml(&nodeContent);
                MessageBox(NULL, nodeContent, L"XPath Query Results", NULL);
                SysFreeString(nodeContent);
            }
        }
    }
}

Hasta ahora hemos logrado implementar y utilizar WWSAPI desde código nativo, sin embargo aún no hemos probado la funcionalidad de nuestra biblioteca de enlace dinámico desde .NET, en ese caso lo primero que debemos hacer es importar la función que nos interesa a través de DllImport

[DllImport(@"C:\Users\angel.hernandez\Desktop\WWSAPI\WWSAPIDemo\x64\Debug\WWSAPIDemo.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr GetFeeds(IntPtr szUrl, int cbResults);

La  función GetFeeds retorna un WCHAR* que es traducido a NET como un IntPtr, así mismo hacemos uso  de la clase Marshal para traducir el WCHAR* a una cadena Unicode y para pasar un WCHAR* a la función, como es mostrado a continuación

private void btnTestWWSAPI_Click(object sender, EventArgs e) {
    IntPtr szUrl = Marshal.StringToBSTR("http://localhost/MSDN.xml");
    MessageBox.Show(Marshal.PtrToStringUni(GetFeeds(szUrl, -1)));
    Marshal.FreeBSTR(szUrl);
}

Y Voila!!! Tengo mi implementación de WWSAPI siendo utilizada desde .NET, bueno al menos desde una aplicación Windows Form, pero ¿cómo será con una aplicación WPF? Pues… la respuesta es será igual, a diferencia de que debes escribir el XAML y que hacer binding a los objetos es más sencillo si se utiliza un XmlDataProvider pues los datos se extraen y asignan utilizando una sintaxis basada en XPath.

<!-- Data Provider-->
<Window.Resources>
    <XmlDataProvider x:Key="xmlFeeds" IsAsynchronous="True" XPath="/rss/items/item"/>
</Window.Resources>
 
<Grid Name="grdFeeds" Margin="0,75,0,12">
    <!-- Binding Source-->
    <ListView Margin="11.432,6" Name="lstFeeds"  
             ItemsSource="{Binding Source={StaticResource xmlFeeds} }" MouseDoubleClick="lstFeeds_MouseDoubleClick">
        <ListView.View>
            <GridView x:Name="grdFeedItems">
                <GridViewColumn Header="Title" x:Name="grcTitle" Width="100" DisplayMemberBinding="{Binding XPath=@title}"/>
                <GridViewColumn Header="Publishing Date" x:Name="grcPublishingDate" Width="100" DisplayMemberBinding="{Binding XPath=@publishingDate}"/>
                <GridViewColumn Header="Url" x:Name="grcUrl" Width="100" DisplayMemberBinding="{Binding XPath=@url}" />
            </GridView>
        </ListView.View>
    </ListView>
</Grid>
 

El manejador del evento Click del botón para llamar a nuestra biblioteca de enlace dinámico es mostrado a continuación

private void btnExecuteOperation_Click(object sender, RoutedEventArgs e) {
    XmlDocument feeds = new XmlDocument();
    IntPtr szUrl = IntPtr.Zero, szItemName = IntPtr.Zero;
    XmlDataProvider xmlFeeds = TryFindResource("xmlFeeds") as XmlDataProvider;
 
    try {
        switch (cboOperations.SelectedIndex) {
            case 0:
                using (RSSFeedService proxy = new RSSFeedService())
                    feeds.LoadXml(proxy.RetrieveFeeds("http://localhost/MSDN.xml", -1));
                xmlFeeds.Document = feeds;
                break;
            case 1:
                szUrl = Marshal.StringToBSTR("http://localhost/MSDN.xml");
                feeds.LoadXml(Marshal.PtrToStringUni(GetFeeds(szUrl, -1)));
                Marshal.FreeBSTR(szUrl);
                xmlFeeds.Document = feeds;
                break;
            case 2:
                szUrl = Marshal.StringToBSTR("http://localhost/MSDN.xml");
                szItemName = Marshal.StringToBSTR(!string.IsNullOrEmpty(txtItemTitle.Text) ? txtItemTitle.Text : "Windows");
                GetItem(szUrl, szItemName);
                Marshal.FreeBSTR(szUrl);
                Marshal.FreeBSTR(szItemName);
                break;
        }
    } catch (Exception ex) {
        MessageBox.Show(string.Format("Oops! Something wrong just occurred\n{0}", ex.Message),
            "Exception caught", MessageBoxButton.OK, MessageBoxImage.Information);
    }
}

El post tiene adjunto el código mostrado, pueden descargarlo, modificarlo y jugar con él. Espero que sea de utilidad y recuerden, la única manera de aprender es jugar con la tecnología y no tener miedo para asumir nuevos retos.

Que Dios los bendiga.

Saludos,

Angel

A couple of pictures from Microsoft Tech-Ed 2009 / Un par de fotos de Microsoft Tech-Ed 2009

Hi Community,

Please find attached a couple of pictures from Microsoft TechEd, they were taken and sent to me by our ANZ  MVP Lead (Roseanne Stamell). In Picture 1,  my fellow MVP and mate, Paul Glavich and I. Picture 2, it’s just  me getting ready to start a Mini Theatre session.  . 

Regards,

Angel


Hola Comunidad,

Por favor vean adjuntas un par de fotos desde Microsoft TechEd, las cuales fueron tomadas y enviadas por nuestra líder MVP (Roseanne Stamell). En la foto 1,  un colega MVP y amigo,  Paul Glavich y yo. Foto 2, soy yo preparándome para comenzar una sesión en un mini teatro.

Saludos,

Angel

DSC01693

 

DSC01719

Adding client-side capabilities to our Webparts

A couple of weeks ago I was attending and presenting at Microsoft TechEd on Gold Coast, when I got a phone call from our Sales Manager. The mission: Develop a new webpart for an existing B2B portal which integrates SAP, MQ, CRM and webMethods through SharePoint. The challenge: Write some Javascript code  to do some processing on the server-side based on the user’s input. 

I’ve been doing some SharePoint development for a while and if you know how to do things on ASP.NET then is not that hard to leverage those skills and use them on SharePoint. The requirements were:

  • Allow user input and perform some validation on the browser-side
  • Add AJAX capabilities to deliver a responsive UI to the users
  • Grab information stored on the client-side (browser) from the server-side and do some processing against CRM

So let’s start from the beginning explaining how I got this done

Properties required

        private DataTable SearchResults {
            get {

return (ViewState["block_orders"] != null ?
(DataTable)ViewState["block_orders"] : null);

            }
            set {
                ViewState["block_orders"] = value;
            }
        }
         
        private string GridViewSortCondition {
            get {
                if (ViewState["SortCondition"] == null)
                    ViewState["SortCondition"] = "sapSearch ASC";
 
                return ViewState["SortCondition"].ToString();
            }
            set {
                ViewState["SortCondition"] = value;
            }
        }
 
        private SortDirection GridViewSortDirection {
            get {
                if (ViewState["SortDirection"] == null)
                    ViewState["SortDirection"] = SortDirection.Ascending;
 
                return (SortDirection)ViewState["SortDirection"];
            }
            set {
                ViewState["SortDirection"] = value;
            }
        }
 
        private string SortExpression {
            get {
                return (ViewState["sort_exp"] != null ?
                  ViewState["sort_exp"].ToString() : string.Empty);
            }
            set {
                ViewState["sort_exp"] = value;
            }
        }
 
        private string SelectedCheckBoxes {
            get {
                return (ViewState["selected_check_boxes"] != null ?
                    ViewState["selected_check_boxes"].ToString() : string.Empty);
            }
            set {
                ViewState["selected_check_boxes"] = value;
            }
        }

I need to store the search results so the user can perform  paging and sorting operations on the result set

Now, let’s proceed to the code required to add AJAX functionality, I usually break down the UI code into pieces, I mean, a method to generate the top, middle and bottom parts of a webpart. Since I’m implementing some AJAX I enclose everything inside a <DIV> so the code required looks like this

        private void AddAJAXControls() {
            Controls.Add(new Literal() { Text = "<div>", ID = "mainDiv" });
            Controls.Add(new ScriptManager() { ID = "scriptManager_OrderBlock" });
            Controls.Add(new Literal() { Text = InjectScriptToDisableDoublePostBack() });
            Controls.Add(new UpdatePanel() { ID = "updatePanel_OrderBlock" });
        }

Please note the call to “InjectScriptToDisableDoublePostback” method, it’s responsible for disabling the control which has triggered the postback (e.g.: a submit button) so the user won’t be able to “re-submit” the form more than once, the code is shown below

        private string InjectScriptToDisableDoublePostBack() {
            StringBuilder retval = new StringBuilder();
 
            retval.AppendLine("<script type='text/javascript'>");
            retval.AppendLine("var pbControl = null;");
            retval.AppendLine("var prm = Sys.WebForms.PageRequestManager.getInstance();");
            retval.AppendLine("prm.add_beginRequest(BeginRequestHandler);");
            retval.AppendLine("prm.add_endRequest(EndRequestHandler);");
            retval.AppendLine("function BeginRequestHandler(sender, args) {");
            retval.AppendLine("//the control causing the postback");
            retval.AppendLine("pbControl = args.get_postBackElement();");
            retval.AppendLine("if (pbControl.id.indexOf('btnSearch') > -1 || ");
            retval.AppendLine("pbControl.id.indexOf('btnUpdate') > -1 )");
            retval.AppendLine("pbControl.disabled = true;");
            retval.AppendLine("}");
            retval.AppendLine("function EndRequestHandler(sender, args) {");
            retval.AppendLine("pbControl.disabled = false;");
            retval.AppendLine("pbControl = null;");
            retval.AppendLine("}");
            retval.AppendLine("</script>");
 
            return retval.ToString();
        }

The method responsible for putting all of the pieces together and rendering the webpart is

        protected virtual void CreateUI() {
            UpdatePanel panel = null;
 
            // Add AJAX controls
            AddAJAXControls();
 
            if ((panel = FindControl("updatePanel_OrderBlock") as UpdatePanel) != null) {
                // Top Table
                panel.ContentTemplateContainer.Controls.Add(GetTopTable());
 
                // Bottom Table
                panel.ContentTemplateContainer.Controls.Add(GetBottomTable());
            }
            // Close Div containing AJAX controls
            Controls.Add(new Literal() { Text = "</div>" });
        }

This approach gives me a clean and tidy HTML when rendered plus it’s easy to follow, debug and maintain. Now, I have all of the objects required inside a <DIV> and it’s AJAX enabled… The only missing thing is our  UpdateProgress template (code is attached) which uses an animated GIF bundled in SharePoint.

            Table retval = new Table() {
                ID = "tblTopTable", CellPadding = 1,
                CellSpacing = 0, Width = Unit.Percentage(100)
            };
 
            retval.Rows.AddRange(new TableRow[] {new TableRow(), new TableRow(), 
                                                 new TableRow(), new TableRow(),
                                                 new TableRow(), new TableRow(),
                                                 new TableRow(), new TableRow(),
                                                 new TableRow()});
 
 
            retval.Rows[ 8 ].Cells.AddRange(new TableCell[] { new TableCell() {
                                                 Width=Unit.Percentage(20)}, 
                                                            new TableCell() {
                                                 Width=Unit.Percentage(60)}});
 
            // Update Progress
            retval.Rows[ 8 ].Cells[0].ColumnSpan = 2;
            retval.Rows[ 8 ].Cells[0].HorizontalAlign = HorizontalAlign.Left;
            retval.Rows[ 8 ].Cells[0].Controls.Add(new UpdateProgress() {
                ID = "workProgress",
                ProgressTemplate = new UpdateProgressTemplate()
            });
 

Ok… so far we’ve got AJAX working but what’s special about that? Well.. not much really but now we need to synchronize user’s selection on the client-side and reflect these changes on the server-side and to accomplish that we need some scripts (code is attached) and a couple of hidden fields which are referenced from our JavaScript as well as from our webpart code and for the sake of keeping things tidy we add them to the controls collection right after creating everything else (as depicted below)

        protected override void CreateChildControls() {
            CreateUI();
            Controls.Add(new HiddenField() { ID = "selected_checkboxes", 
                                             EnableViewState = true });
            Controls.Add(new HiddenField() { ID = "checkboxes_cleared",
                                             EnableViewState = true });
            ChildControlsCreated = true;
        }

We also need to add some Javascript to support client-side operations so we do this on the OnPreRender method (AddJavaScriptToWebpart method is attached to the post)

        protected override void OnPreRender(EventArgs e) {
            if (!Page.ClientScript.IsClientScriptBlockRegistered(JSCRIPT_NAME))
                Page.ClientScript.RegisterClientScriptBlock(typeof(string),
                        JSCRIPT_NAME, AddJavaScriptToWebpart());
        }

Up to now we’ve met some of the user’s requirements but what about the GridView? Let’s talk about it then. The GridView is created by the following method

        private GridView GetSearchResultsGrid() {
            GridView retval = new GridView() {
                ID = "grdSearchResults", AutoGenerateColumns = false,
                AllowPaging = true, AllowSorting = true, Width = Unit.Percentage(100),
                PageSize = 100,
                EmptyDataText="<font color='red'><b>No results found</b></font>"
            };
 
            // Add Columns to the GridView
            retval.Columns.Add(new BoundField() { DataField = "sapSearch",
                                                  HeaderText = "SAP Search",
                                                  SortExpression = "sapSearch" });
 
            retval.Columns.Add(new BoundField() { DataField = "accountNo",
                                                  HeaderText = "Account No.", 
                                                  SortExpression = "accountNo" });
 
            retval.Columns.Add(new BoundField() { DataField = "accountName", 
                                                  HeaderText = "Account Name", 
                                                  SortExpression = "accountName" });
    
            retval.Columns.Add(new BoundField() { DataField = "state",
                                                  HeaderText = "State", 
                                                  SortExpression = "state" });
 
            retval.Columns.Add(new TemplateField() {
                ItemTemplate = new CheckBoxTemplate(ListItemType.Item),
                HeaderTemplate = new CheckBoxTemplate(ListItemType.Header)
            });
 
            // Let's align to the centre the Order Block and State Columns
            retval.Columns[3].ItemStyle.HorizontalAlign = HorizontalAlign.Center;
            retval.Columns[4].ItemStyle.HorizontalAlign = HorizontalAlign.Center;
 
            // Subscribe to events
            retval.Sorting += grdSearchResults_Sorting;
            retval.RowCreated += grdSearchResults_RowCreated;
            retval.PageIndexChanging += grdResults_PageIndexChanging;
            retval.RowDataBound += grdSearchResults_RowDataBound;
 
            return retval;
        }

The ticking/unticking of the checkboxes is handled by a Javascript call from CheckBoxTemplate (attached to the post)  as shown below

        private CheckBox GetTemplateContents() {
            CheckBox retval = null;
 
            switch (_type) {
                case ListItemType.Header:
                    retval = new CheckBox() { ID = "chkHeader",
                                              Text = "Account Block",
                                              EnableViewState = true };
                    retval.Attributes["onclick"] = "BLOCKED SCRIPTcheckUncheckHeader(this);";
                    break;
                case ListItemType.Item:
                    retval = new CheckBox() {ID = "chkItem_", EnableViewState = true };
                    retval.Attributes["onclick"] = "BLOCKED SCRIPTcheckUncheckItem(this);";
                    break;
            }
            return retval;

As previously mentioned, our Gridview had to support paging and sorting plus display an indicator for the current sorting criteria so I had to handle the RowCreated event as depicted below

        private void grdSearchResults_RowCreated(object sender, 
            GridViewRowEventArgs e) {
 
            int rowIndex = 0;
            DataView sorted = null;
            string strSortedHeader = string.Empty;
        
            // Header
            if (e.Row.RowType == DataControlRowType.Header) {
                foreach (TableCell tc in e.Row.Cells) {
                    if (tc.Controls.Count > 0 && tc.Controls[0].GetType().ToString() == 
                           "System.Web.UI.WebControls.DataControlLinkButton") {
                        strSortedHeader = ((LinkButton)tc.Controls[0]).Text;
 
                        // Sort indicator  (Webdings font does the job for us)
                        if (((LinkButton)tc.Controls[0]).CommandArgument ==
                            SortExpression) {
                            if (GridViewSortDirection == SortDirection.Descending)
                                ((LinkButton)tc.Controls[0]).Text = 
                                     strSortedHeader.Replace(strSortedHeader,
                                     strSortedHeader + "<font face='Webdings'>5</font>");
                            else
                                ((LinkButton)tc.Controls[0]).Text =
                                    strSortedHeader.Replace(strSortedHeader, 
                                    strSortedHeader + "<font face='Webdings'>6</font>");
                        }
                    }
                }
            } else if (e.Row.RowType == DataControlRowType.DataRow) { // DataRow
                if (e.Row.Cells[e.Row.Cells.Count - 1].Controls.Count > 0 &&
                    SearchResults != null && SearchResults.Rows.Count > 0) {
                    sorted = new DataView(SearchResults);
                    sorted.Sort = GridViewSortCondition;
                    rowIndex = e.Row.DataItemIndex;
                    ((CheckBox)e.Row.Cells[e.Row.Cells.Count - 1].Controls[0]).ID += 
                        sorted[e.Row.DataItemIndex]["accountNo"].ToString();
                    ((CheckBox)e.Row.Cells[e.Row.Cells.Count - 1].Controls[0]).
                        Checked = (bool) sorted[e.Row.DataItemIndex]["isSelected"];
 
                    // Is it checked? (Non user interaction)
                    if (((CheckBox)e.Row.Cells[e.Row.Cells.Count - 1].
                        Controls[0]).Checked)
                        SelectedCheckBoxes += string.Format("{0};",
                            ((CheckBox)e.Row.Cells[e.Row.Cells.Count - 1].
                            Controls[0]).ClientID);
                }
            }
        }

Please note that we make a difference when creating/rendering the rows based on their type, for instance, the header is going to display an arrow to indicate sorting direction, otherwise we grab the values retrieved from the database (CRM) and change the checkboxes’ ID on the fly, by adding the account number to it (this is required to keep track of the user selection).

At this moment you’ll be wondering about, where is this guy grabbing the values set already from the client side and calling CRM?  I’m pretty sure that many of you know the answer… The button click event handler.

        private void btnUpdate_Click(object sender, EventArgs e) {
            int rowIndex = 0;
            int selectedCell = 0;
            DataView sorted = null;
            string[] splitData = null;
            string selectedControl = string.Empty;
            HiddenField clientSideSelection = null;
            StringBuilder userSelection = new StringBuilder();
            GridView grdSearchResults = FindControl("grdSearchResults") as GridView;
            HiddenField checkboxes_cleared = FindControl("checkboxes_cleared") as 
                                              HiddenField;
 
            // Is there any data to continue?
            if ((SearchResults != null && SearchResults.Rows.Count > 0) && 
                (grdSearchResults != null && grdSearchResults.Rows.Count > 0)) {
                
                // Let's create a view to deal with the data as it is displayed
                sorted = new DataView(SearchResults);
                sorted.Sort = GridViewSortCondition;
 
                // Let's sync both server and client checkboxes selection
                ManageDeselectedItemsOnClientSide();
 
                // Should we combine client-side selection with data from the DB?
                if ((clientSideSelection = FindControl("selected_checkboxes") as HiddenField) != null 
                                            &&  !string.IsNullOrEmpty(clientSideSelection.Value)) {
                    splitData = clientSideSelection.Value.Split(';');
 
                    var includeQuery = from checkBoxName in splitData.ToList()
                                     .Where(controlName => !string.IsNullOrEmpty(controlName) && 
                                             SelectedCheckBoxes.IndexOf(controlName) == -1 &&
                                             controlName.IndexOf("_REMOVED") == -1)
                                     select checkBoxName;
 
                    // Is there any item we must include?
                    if (includeQuery.ToList().Count > 0)
                        includeQuery.ToList().ForEach(controlToInclude =>
                                             userSelection.Append(string.Format("{0};", 
                            controlToInclude.Substring(controlToInclude.IndexOf("chkItem")))));
 
                    // Let's combine it with the information coming from the DB
                    userSelection.Append(SelectedCheckBoxes);
                } else if (checkboxes_cleared != null && // Has the user interacted with the UI? 
                    string.IsNullOrEmpty(checkboxes_cleared.Value)) 
                    userSelection.Append(SelectedCheckBoxes);
 
                // Accounts to update
                splitData = userSelection.ToString().Split(';');
 
                // We loop through the control names collection 
                // (by default, we unblock those unselected checkboxes)
                foreach (string checkBoxId in splitData) {
                    rowIndex = 0;
                    foreach (GridViewRow selectedRow in grdSearchResults.Rows) {
                        selectedCell = selectedRow.Cells.Count - 1;
                        // Is it the right one?
                        if (selectedRow.Cells[selectedCell].Controls.Count > 0 && 
                            selectedRow.Cells[selectedCell].Controls[0] is CheckBox) {
                            selectedControl = ((CheckBox)selectedRow.Cells[selectedCell].Controls[0]).ID;
                            if (selectedControl.Equals(checkBoxId)) {
                                // Account to block
                                sorted[rowIndex]["isSelected"] = true;
                                break;
                            } else if (!splitData.Contains(selectedControl)) {
                                // Account to unblock (default behaviour)
                                sorted[rowIndex]["isSelected"] = false;
                            }
                        }
                        rowIndex++;
                    }
                }
 
                // Block/Unblock accounts based on the user's selection
                var accountsToProcess = from currentSelection in sorted.Table
                                        .AsEnumerable()
                            select new {
                              AccountId = currentSelection["accountId"].ToString(),
                              IsSelected = (bool)currentSelection["isSelected"]
                            };
 
                accountsToProcess.ToList().ForEach(account => 
                    OrderBlockManagement.updateAccount(account.AccountId,
                                                       account.IsSelected));
 
                // Let's clear DB selection information
                SelectedCheckBoxes = string.Empty;
 
                // Let's reflect the changes on the UI
                GridBindingHelper();
            }
        }

The final result is shown below

Screenshot

Our webpart integrating different systems as mentioned at the beginning of this post plus providing a responsive UI to the user.

Regards,

Angel

Agregando procesamiento del lado-cliente a nuestros Webparts

Hace un par de semanas atrás estaba atendiendo y presentando en  Microsoft TechEd en Gold Coast, cuando de repente recibí una llamada del Gerente de Mercadeo y Ventas. La misión: Desarrollar un nuevo webpart para un portal B2B existente que integra SAP, MQ, CRM y webMethods a través SharePoint. El reto: Escribir código en Javascript para luego realizar operaciones del lado del servidor en base a la selección realizada por el usuario. 

He estado desarrollando para SharePoint por un rato ya, pero si sabes como hacer las cosas con ASP.NET entonces no es díficil utilizar ese conocimiento y aplicarlos con SharePoint. Los requerimientos eran :

  • Permitir la entrada del usuario y validar del lado del navegador
  • Agregar AJAX para ofrecer una interfaz rápida a los usuarios 
  • Obtener información almacenada del lado del cliente (navegador) desde el lado del servidor para realizar operaciones contra CRM

Así que comencemos por el principio explicando como lo hice

Propiedades requeridas

        private DataTable SearchResults {
            get {

return (ViewState["block_orders"] != null ?
(DataTable)ViewState["block_orders"] : null);

            }
            set {
                ViewState["block_orders"] = value;
            }
        }
         
        private string GridViewSortCondition {
            get {
                if (ViewState["SortCondition"] == null)
                    ViewState["SortCondition"] = "sapSearch ASC";
 
                return ViewState["SortCondition"].ToString();
            }
            set {
                ViewState["SortCondition"] = value;
            }
        }
 
        private SortDirection GridViewSortDirection {
            get {
                if (ViewState["SortDirection"] == null)
                    ViewState["SortDirection"] = SortDirection.Ascending;
 
                return (SortDirection)ViewState["SortDirection"];
            }
            set {
                ViewState["SortDirection"] = value;
            }
        }
 
        private string SortExpression {
            get {
                return (ViewState["sort_exp"] != null ?
                  ViewState["sort_exp"].ToString() : string.Empty);
            }
            set {
                ViewState["sort_exp"] = value;
            }
        }
 
        private string SelectedCheckBoxes {
            get {
                return (ViewState["selected_check_boxes"] != null ?
                    ViewState["selected_check_boxes"].ToString() : string.Empty);
            }
            set {
                ViewState["selected_check_boxes"] = value;
            }
        }

Necesito guardar los resultados de la búsqueda para que el usuario pueda paginar y ordenar estos

Ahora, prosigamos con el código requerido para agregar AJAX, en mi caso siempre separo/distribuyo el código que genera la interfaz de usuario en partes, es decir, tengo un método para generar la parte superior, media e inferior del webpart y como estoy implementando AJAX me gusta encerrar todo dentro de un  <DIV> por lo que el código requerido es como éste

        private void AddAJAXControls() {
            Controls.Add(new Literal() { Text = "<div>", ID = "mainDiv" });
            Controls.Add(new ScriptManager() { ID = "scriptManager_OrderBlock" });
            Controls.Add(new Literal() { Text = InjectScriptToDisableDoublePostBack() });
            Controls.Add(new UpdatePanel() { ID = "updatePanel_OrderBlock" });
        }

Por favor nótese la llamada al método “InjectScriptToDisableDoublePostback”, el cuál es responsable de deshabilitar el control que generó el  postback (por ejemplo, un botón enviar) así evito que el usuario lo presione más de una vez, el código es mostrado abajo

        private string InjectScriptToDisableDoublePostBack() {
            StringBuilder retval = new StringBuilder();
 
            retval.AppendLine("<script type='text/javascript'>");
            retval.AppendLine("var pbControl = null;");
            retval.AppendLine("var prm = Sys.WebForms.PageRequestManager.getInstance();");
            retval.AppendLine("prm.add_beginRequest(BeginRequestHandler);");
            retval.AppendLine("prm.add_endRequest(EndRequestHandler);");
            retval.AppendLine("function BeginRequestHandler(sender, args) {");
            retval.AppendLine("//the control causing the postback");
            retval.AppendLine("pbControl = args.get_postBackElement();");
            retval.AppendLine("if (pbControl.id.indexOf('btnSearch') > -1 || ");
            retval.AppendLine("pbControl.id.indexOf('btnUpdate') > -1 )");
            retval.AppendLine("pbControl.disabled = true;");
            retval.AppendLine("}");
            retval.AppendLine("function EndRequestHandler(sender, args) {");
            retval.AppendLine("pbControl.disabled = false;");
            retval.AppendLine("pbControl = null;");
            retval.AppendLine("}");
            retval.AppendLine("</script>");
 
            return retval.ToString();
        }

El método responsable de poner todas las piezas juntas y dibujar el webpart es

        protected virtual void CreateUI() {
            UpdatePanel panel = null;
 
            // Add AJAX controls
            AddAJAXControls();
 
            if ((panel = FindControl("updatePanel_OrderBlock") as UpdatePanel) != null) {
                // Top Table
                panel.ContentTemplateContainer.Controls.Add(GetTopTable());
 
                // Bottom Table
                panel.ContentTemplateContainer.Controls.Add(GetBottomTable());
            }
            // Close Div containing AJAX controls
            Controls.Add(new Literal() { Text = "</div>" });
        }

Este método me genera un HTML limpio y ordenado cuando el webpart es dibujado además es fácil de seguir, depurar y mantener. Ahora, todos los objetos requeridos están dentro del <DIV>  y ya agregué AJAX… La única cosa que me falta es nuestra plantilla de progreso (código adjunto) que utiliza un GIF animado que viene con  SharePoint.

            Table retval = new Table() {
                ID = "tblTopTable", CellPadding = 1,
                CellSpacing = 0, Width = Unit.Percentage(100)
            };
 
            retval.Rows.AddRange(new TableRow[] {new TableRow(), new TableRow(), 
                                                 new TableRow(), new TableRow(),
                                                 new TableRow(), new TableRow(),
                                                 new TableRow(), new TableRow(),
                                                 new TableRow()});
 
 
            retval.Rows[ 8 ].Cells.AddRange(new TableCell[] { new TableCell() {
                                                 Width=Unit.Percentage(20)}, 
                                                            new TableCell() {
                                                 Width=Unit.Percentage(60)}});
 
            // Update Progress
            retval.Rows[ 8 ].Cells[0].ColumnSpan = 2;
            retval.Rows[ 8 ].Cells[0].HorizontalAlign = HorizontalAlign.Left;
            retval.Rows[ 8 ].Cells[0].Controls.Add(new UpdateProgress() {
                ID = "workProgress",
                ProgressTemplate = new UpdateProgressTemplate()
            });
 

Bueno… hasta ahora tenemos el AJAX trabajando con nuestro webpart pero, ¿qué es lo especial de eso? Bien... no mucho en realidad pero ahora necesitamos sincronizar la selección realizada por el usuario del lado del cliente y reflejar estos cambios del lado del servidor, así que para lograr eso necesitamos escribir unos scripts (código adjunto) y un par de campos ocultos que son referenciados desde nuestro código en JavaScript y del webpart también, por lo que en aras de mantener las cosas ordenadas y simples, agregamos estos a la colección de controles del webpart una vez que ya hemos creado todo lo demás  (como se muestra abajo)

        protected override void CreateChildControls() {
            CreateUI();
            Controls.Add(new HiddenField() { ID = "selected_checkboxes", 
                                             EnableViewState = true });
            Controls.Add(new HiddenField() { ID = "checkboxes_cleared",
                                             EnableViewState = true });
            ChildControlsCreated = true;
        }

También necesitamos agregar JavaScript para soportar operaciones del lado del cliente por lo que sobrescribimos el método  OnPreRender  (El método AddJavaScriptToWebpart está adjunto a este post)

        protected override void OnPreRender(EventArgs e) {
            if (!Page.ClientScript.IsClientScriptBlockRegistered(JSCRIPT_NAME))
                Page.ClientScript.RegisterClientScriptBlock(typeof(string),
                        JSCRIPT_NAME, AddJavaScriptToWebpart());
        }

Hasta ahora hemos satisfecho algunos de los requerimientos solicitados por el usuario, pero ¿qué pasó con el  GridView? Hablemos del GridView entonces. El GridView es creado por el siguiente método

        private GridView GetSearchResultsGrid() {
            GridView retval = new GridView() {
                ID = "grdSearchResults", AutoGenerateColumns = false,
                AllowPaging = true, AllowSorting = true, Width = Unit.Percentage(100),
                PageSize = 100,
                EmptyDataText="<font color='red'><b>No results found</b></font>"
            };
 
            // Add Columns to the GridView
            retval.Columns.Add(new BoundField() { DataField = "sapSearch",
                                                  HeaderText = "SAP Search",
                                                  SortExpression = "sapSearch" });
 
            retval.Columns.Add(new BoundField() { DataField = "accountNo",
                                                  HeaderText = "Account No.", 
                                                  SortExpression = "accountNo" });
 
            retval.Columns.Add(new BoundField() { DataField = "accountName", 
                                                  HeaderText = "Account Name", 
                                                  SortExpression = "accountName" });
    
            retval.Columns.Add(new BoundField() { DataField = "state",
                                                  HeaderText = "State", 
                                                  SortExpression = "state" });
 
            retval.Columns.Add(new TemplateField() {
                ItemTemplate = new CheckBoxTemplate(ListItemType.Item),
                HeaderTemplate = new CheckBoxTemplate(ListItemType.Header)
            });
 
            // Let's align to the centre the Order Block and State Columns
            retval.Columns[3].ItemStyle.HorizontalAlign = HorizontalAlign.Center;
            retval.Columns[4].ItemStyle.HorizontalAlign = HorizontalAlign.Center;
 
            // Subscribe to events
            retval.Sorting += grdSearchResults_Sorting;
            retval.RowCreated += grdSearchResults_RowCreated;
            retval.PageIndexChanging += grdResults_PageIndexChanging;
            retval.RowDataBound += grdSearchResults_RowDataBound;
 
            return retval;
        }

La selección/de-selección de las casillas de verificación es manejado por una llamada a una función en JavaScript, realizada desde  CheckBoxTemplate (adjunto al post) como es mostrado abajo

        private CheckBox GetTemplateContents() {
            CheckBox retval = null;
 
            switch (_type) {
                case ListItemType.Header:
                    retval = new CheckBox() { ID = "chkHeader",
                                              Text = "Account Block",
                                              EnableViewState = true };
                    retval.Attributes["onclick"] = "BLOCKED SCRIPTcheckUncheckHeader(this);";
                    break;
                case ListItemType.Item:
                    retval = new CheckBox() {ID = "chkItem_", EnableViewState = true };
                    retval.Attributes["onclick"] = "BLOCKED SCRIPTcheckUncheckItem(this);";
                    break;
            }
            return retval;

Como mencionamos previamente, nuestro GridVied tenía que soportar paginación y ordenamiento además de mostrar un indicador para el criterio de ordenamiento actual así que tuve que manejar el evento RowCreated event como se muestra a continuación

        private void grdSearchResults_RowCreated(object sender, 
            GridViewRowEventArgs e) {
 
            int rowIndex = 0;
            DataView sorted = null;
            string strSortedHeader = string.Empty;
        
            // Header
            if (e.Row.RowType == DataControlRowType.Header) {
                foreach (TableCell tc in e.Row.Cells) {
                    if (tc.Controls.Count > 0 && tc.Controls[0].GetType().ToString() == 
                           "System.Web.UI.WebControls.DataControlLinkButton") {
                        strSortedHeader = ((LinkButton)tc.Controls[0]).Text;
 
                        // Sort indicator  (Webdings font does the job for us)
                        if (((LinkButton)tc.Controls[0]).CommandArgument ==
                            SortExpression) {
                            if (GridViewSortDirection == SortDirection.Descending)
                                ((LinkButton)tc.Controls[0]).Text = 
                                     strSortedHeader.Replace(strSortedHeader,
                                     strSortedHeader + "<font face='Webdings'>5</font>");
                            else
                                ((LinkButton)tc.Controls[0]).Text =
                                    strSortedHeader.Replace(strSortedHeader, 
                                    strSortedHeader + "<font face='Webdings'>6</font>");
                        }
                    }
                }
            } else if (e.Row.RowType == DataControlRowType.DataRow) { // DataRow
                if (e.Row.Cells[e.Row.Cells.Count - 1].Controls.Count > 0 &&
                    SearchResults != null && SearchResults.Rows.Count > 0) {
                    sorted = new DataView(SearchResults);
                    sorted.Sort = GridViewSortCondition;
                    rowIndex = e.Row.DataItemIndex;
                    ((CheckBox)e.Row.Cells[e.Row.Cells.Count - 1].Controls[0]).ID += 
                        sorted[e.Row.DataItemIndex]["accountNo"].ToString();
                    ((CheckBox)e.Row.Cells[e.Row.Cells.Count - 1].Controls[0]).
                        Checked = (bool) sorted[e.Row.DataItemIndex]["isSelected"];
 
                    // Is it checked? (Non user interaction)
                    if (((CheckBox)e.Row.Cells[e.Row.Cells.Count - 1].
                        Controls[0]).Checked)
                        SelectedCheckBoxes += string.Format("{0};",
                            ((CheckBox)e.Row.Cells[e.Row.Cells.Count - 1].
                            Controls[0]).ClientID);
                }
            }
        }

Por favor, nótese la diferencia cuando se está creando/dibujando las filas basadas en su tipo, es decir, el encabezado va a mostrar una flecha que indica la dirección de ordenamiento, de lo contrario, tomamos los valores recuperados de la base de datos (CRM) y cambiamos el ID de las casillas de verificación en tiempo de ejecución, al agregar el número de cuenta a éste (esto es requerido para hacer seguimiento de la selección del usuario)

En este momento se estarán preguntando, ¿en donde se toman los valores almacenados del lado del cliente y se llama a CRM? Estoy seguro que muchos saben la respuesta… El manejador del evento click del botón.

        private void btnUpdate_Click(object sender, EventArgs e) {
            int rowIndex = 0;
            int selectedCell = 0;
            DataView sorted = null;
            string[] splitData = null;
            string selectedControl = string.Empty;
            HiddenField clientSideSelection = null;
            StringBuilder userSelection = new StringBuilder();
            GridView grdSearchResults = FindControl("grdSearchResults") as GridView;
            HiddenField checkboxes_cleared = FindControl("checkboxes_cleared") as 
                                              HiddenField;
 
            // Is there any data to continue?
            if ((SearchResults != null && SearchResults.Rows.Count > 0) && 
                (grdSearchResults != null && grdSearchResults.Rows.Count > 0)) {
                
                // Let's create a view to deal with the data as it is displayed
                sorted = new DataView(SearchResults);
                sorted.Sort = GridViewSortCondition;
 
                // Let's sync both server and client checkboxes selection
                ManageDeselectedItemsOnClientSide();
 
                // Should we combine client-side selection with data from the DB?
                if ((clientSideSelection = FindControl("selected_checkboxes") as HiddenField) != null 
                                            &&  !string.IsNullOrEmpty(clientSideSelection.Value)) {
                    splitData = clientSideSelection.Value.Split(';');
 
                    var includeQuery = from checkBoxName in splitData.ToList()
                                     .Where(controlName => !string.IsNullOrEmpty(controlName) && 
                                             SelectedCheckBoxes.IndexOf(controlName) == -1 &&
                                             controlName.IndexOf("_REMOVED") == -1)
                                     select checkBoxName;
 
                    // Is there any item we must include?
                    if (includeQuery.ToList().Count > 0)
                        includeQuery.ToList().ForEach(controlToInclude =>
                                             userSelection.Append(string.Format("{0};", 
                            controlToInclude.Substring(controlToInclude.IndexOf("chkItem")))));
 
                    // Let's combine it with the information coming from the DB
                    userSelection.Append(SelectedCheckBoxes);
                } else if (checkboxes_cleared != null && // Has the user interacted with the UI? 
                    string.IsNullOrEmpty(checkboxes_cleared.Value)) 
                    userSelection.Append(SelectedCheckBoxes);
 
                // Accounts to update
                splitData = userSelection.ToString().Split(';');
 
                // We loop through the control names collection 
                // (by default, we unblock those unselected checkboxes)
                foreach (string checkBoxId in splitData) {
                    rowIndex = 0;
                    foreach (GridViewRow selectedRow in grdSearchResults.Rows) {
                        selectedCell = selectedRow.Cells.Count - 1;
                        // Is it the right one?
                        if (selectedRow.Cells[selectedCell].Controls.Count > 0 && 
                            selectedRow.Cells[selectedCell].Controls[0] is CheckBox) {
                            selectedControl = ((CheckBox)selectedRow.Cells[selectedCell].Controls[0]).ID;
                            if (selectedControl.Equals(checkBoxId)) {
                                // Account to block
                                sorted[rowIndex]["isSelected"] = true;
                                break;
                            } else if (!splitData.Contains(selectedControl)) {
                                // Account to unblock (default behaviour)
                                sorted[rowIndex]["isSelected"] = false;
                            }
                        }
                        rowIndex++;
                    }
                }
 
                // Block/Unblock accounts based on the user's selection
                var accountsToProcess = from currentSelection in sorted.Table
                                        .AsEnumerable()
                            select new {
                              AccountId = currentSelection["accountId"].ToString(),
                              IsSelected = (bool)currentSelection["isSelected"]
                            };
 
                accountsToProcess.ToList().ForEach(account => 
                    OrderBlockManagement.updateAccount(account.AccountId,
                                                       account.IsSelected));
 
                // Let's clear DB selection information
                SelectedCheckBoxes = string.Empty;
 
                // Let's reflect the changes on the UI
                GridBindingHelper();
            }
        }

El resultado final es mostrado abajo

Screenshot

Nuestro webpart integrando diferentes sistemas como mencioné al principio del post además de proveer una interfaz rápida al usuario.

Saludos,

Angel

Slide deck and demos for Break Out Session at Tech-Ed / Láminas y demos utilizados para mi sesión en Tech-Ed

Hi Community,

Please feel free to download the slide deck and demos for my break out session at Tech-Ed

Regards,

Angel

 

 


 

Hola Comunidad,

Por favor siéntanse libres de  descargar las láminas y demos de mi sesión en Tech-Ed

Saludos,

Angel

Tech Talk on "Windows API Code Pack for .NET" / Charla Técnica sobre "Windows API Code Pack for .NET"

Hi Community,

Please feel free to watch my Tech Talk interview with Catherine Eibner

Regards,

Angel

 


Hola Comunidad,

Por favor siéntanse libres de ver mi charla técnica con Catherine Eibner 

Saludos,

Angel

Click here to play this video

Slide deck and demo for "Blue Theater" Session and Tech Talk / Láminas y demo utilizados para mi sesión en "Blue Theater" y Tech Talk

Hi Community,

Please feel free to download the attached Slide Deck and demo  used for today's "Blue Theater" Session and Yesterday's Tech Talk with Catherine Eibner for TechEd Online.

Regards,

Angel

 


Hola Comunidad,

Por favor siéntanse libres de descargar la presentación y demo utlizados en la sesión  en "Blue Theater" de hoy y la charla técnica de ayer con Catherine Eibner para TechEd Online.

Saludos,

Angel

Upcoming Speaking Engagements (Próximas ponencias y charlas)

These are my upcoming speaking engagements:

Regards,

Angel


Estas son mis próximas ponencias y/o charlas:

Saludos,

Angel

Get the SharePoint version programmatically

This post is about a question that many developers ask themselves, ¿how can I get the SharePoint version programmatically?

There are at least  three possible ways to do this (that I'm aware of):

In my case, I'm always using  WMI and Windows provides us with the right tool (WBEMTEST.EXE) to try our  WQL queries

Regards,

Angel

Determinando el número de versión de SharePoint programáticamente

Este post trata de una pregunta que muchos desarrolladores se hacen, ¿cómo determino la versión de SharePoint que tengo instalada por medio de código?

A continuación enumero las tres posibles maneras (que yo conozco para hacerlo):

En mi caso particular, siempre hago uso de WMI y Windows nos ofrece WBEMTEST.EXE para probar nuestras consultas WQL

Saludos,

Angel

Memory Mapped Files

Writing code nowadays can be considered as a "cheerful experience", we have a complete set of tools and development environments that make us more productive, helping us in the delivery of high-quality software. 15 years ago it was a hard task to find IDEs with Intellisense support, we used to store our application settings in .INI files and we didn't even have any virtualization environment in order to perform tests before sending out our solution to the customers, that's why it's a new adventure each time we got engaged on a new development project and coding hasn't ever been as enjoyable as it is today. A few years ago I read a book written by Jeffrey Richter called "Advanced Windows" and I learned heaps from this book, as a matter of fact, last year I bought his latest book called "Windows via C/C++" which is a classic and a must have in every Windows developer toolbox, same thing occurs with Charles Petzold's book, however I learned a lot about Windows internal mechanisms by reading Richter's, for instance, how can processes exchange information between them? What's the difference between a Mutex and a Semaphore for threads synchronization? just to mention a few. Windows is a product that has evolved and grown throughout the years, despite of new APIs and functionality that have been included into it we will always need access to the PC's memory, for example we can mention Inter-Process Communication (IPC) and virtual memory access, this post is about it pretty much, how can I get access to the virtual memory so I can address and use a given amount of bytes? I think you know already what I'm talking about. Memory Mapped Files can be defined as "a segment of virtual memory which has been assigned a direct byte-for-byte correlation with some portion of a file or file-like resource. This resource is typically a file that is physically present on-disk, but can also be a device, shared memory object or other resource that the operating system can reference through a file descriptor". They have three main purposes which are:

  • Load and execute .exe files and DLLs
  • Access files on-disk very quickly without using buffers. Windows handles this for us
  • Allow many processes to share information in memory

The following diagram depicts some implementations for memory mapped files

 clip_image002

Once the file is mapped in memory, a view based on that file needs to be created, I mean, a region or space within the file which allows a process to read and write onto it, when creating the view we specify bytes to be mapped, file offset and desired access, this process is shown in the image below

 image

One of the new features bundled in .NET Framework 4.0 besides DLR (Dynamic Language Runtime), parallel processing and support for memory mapped files. The code that accompanies this post demonstrates how to do it using the new MemoryMappedFile class plus my own implementation with .NET 3.5, C# and a little of Interop.

Further reading about this topic can be found at  Virtual Memory and Paging

Regards,

Angel

Format: swf
Duration: 22 min

Archivos Mapeados en Memoria

Hoy en día es ameno “echar código”, pues tenemos herramientas de desarrollo que nos ayudan a ser productivos y aseguramos la calidad del software que hacemos. Hace 15 años atrás era difícil encontrar entornos de desarrollo con Intellisense, guardábamos nuestra configuración en archivos .INI y no disponíamos de virtualización para probar nuestra aplicación antes de enviarla al cliente, por eso en la actualidad cada día es una aventura y programar es más divertido. Hace unos años atrás leí un libro escrito por Jeffrey Richter titulado “Programación avanzada para Windows 95/NT”, aprendí mucho de ese libro  de hecho el año pasado me compré la última edición del mismo titulado “Windows via C/C++”, es un clásico como el libro de Charles Petzold, sin embargo con el libro de Richter aprendí cosas sobre el funcionamiento interno del Sistema Operativo, por ejemplo, ¿cómo los procesos pueden intercambiar información entre sí?, ¿cuál es la diferencia entre un Mutex y un semáforo para sincronizar hilos? y muchas otras cosas más. Windows es un producto que ha madurado y crecido con el paso de los años, a pesar de nuevas APIs y funcionalidad siempre tendremos que tener acceso a la memoria del PC, un ejemplo de esto es la comunicación entre procesos (IPC) y el acceso a la memoria virtual, el siguiente post trata de eso, ¿cómo utilizar la memoria virtual para así direccionar y/o utilizar una cantidad de memoria que yo desee? Me imagino, ya saben a lo que me refiero y es Archivos mapeados en memoria (Memory Mapped Files) que lo podríamos definir como “un segmento de la memoria virtual la cual se le ha asignado una correlación directa byte a byte con alguna porción de un archivo ó algún otro recurso, el cuál típicamente es un archivo en disco pero también puede ser un dispositivo, un objeto compartido de memoria u otro recurso que el sistema operativo pueda referenciar a través de un descriptor de archivo”. Los archivos mapeados en memoria tienen tres propósitos los cuales son:

  • Cargar y ejecutar archivos .exe y bibliotecas de enlace dinámico (DLL)
  • Accesar archivos en disco rápidamente sin necesidad de usar búferes, Windows se encarga de esto por nosotros
  • Permitir a varios procesos compartir la misma información en memoria

El siguiente diagrama muestra alguna de las implementaciones para los archivos mapeados en memoria

 clip_image002

Una vez que el archivo se encuentra mapeado en memoria, es necesario crear una vista basada en el mismo, es decir, una región ó espacio dentro del archivo a la cuál un proceso puede leer y escribir, al momento de crear la vista se especifica desde donde se va a comenzar a manipular el archivo así como el número de bytes a utilizar, este proceso lo podemos ver en la imagen mostrada a continuación

 image

Una de las características nuevas que trae consigo el .NET Framework 4.0 además de DLR (Dynamic Language Runtime), procesamiento paralelo y muchas otras más, es el soporte para archivos mapeados en memoria, el código de este posting demuestra como hacerlo con la nueva clase MemoryMappedFile así como mi propia implementación con .NET 3.5, C# y un poco de Interop.

Si deseas leer un poco más sobre este tema puedes revisar estos links en Wikipedia: Memoria Virtual y Paginación de Memoria

Saludos,

Angel

Format: swf
Duration: 38 Min

Extending the Exception class, getting Win32 error messages plus extension methods

Structured Exception Handling (SEH), it’s one of the top used features by developers, it’s not required any more to be dealing with On Error labels or any other less elegant mechanism to handle error conditions, however .NET Framework is an existing layer between our application and the operating system, even when the Exception class describes an error condition it doesn’t provide the error code and message associated to the operating system, that’s why it’s frustrating sometimes to interpret some “less-descriptive” exceptions and hence recovering any Windows error information can be of great help. Throughout the years Windows API has evolved and grown in size and error messages as well, many of these messages can be found in the SDK and WDK, so I had this idea about extending the Exception class by implementing an extension method. Extension methods enable us to “add” methods to existing types without creating a new derived type, recompiling, or otherwise modifying the original type. They are a special kind of static method, but they are called as if they were instance methods on the extended type. The most common extension methods are  the LINQ standard query operators that add query functionality to the existing IEnumerable and IEnumerable<T>. 

To retrieve the description for a given error code we use the FormatMessage function, as it’s shown below 

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true,
           CallingConvention = CallingConvention.Winapi)]
public static extern int FormatMessage(int dwFlags, int lpSource, int dwMessageId,
                     int dwLanguageId, out StringBuilder lpBuffer,
                       int nSize, IntPtr va_list);

The extension method used is the following

    public static class ExceptionExtension {
    /// <summary>
    /// Gets the win32 error description.
    /// </summary>
    /// <param name="ex">The ex.</param>
    /// <returns></returns>
    public static Win32Error GetWin32ErrorDescription(this Exception ex) {
        Win32Error retval = Win32Error.Empty;
        StringBuilder pMsgBuf = new StringBuilder();
 
        int lastError = FormatErrorHelper.GetLastError();
 
        FormatErrorHelper.FormatMessage(
               FormatErrorHelper.FORMAT_MESSAGE_ALLOCATE_BUFFER |
               FormatErrorHelper.FORMAT_MESSAGE_FROM_SYSTEM,
               FormatErrorHelper.NULL,   lastError, 
               FormatErrorHelper.MakeLangID(FormatErrorHelper.LANG_NEUTRAL,
               FormatErrorHelper.SUBLANG_DEFAULT), out pMsgBuf, 0, IntPtr.Zero);
 
         retval.ErrorCode = lastError;
         retval.Description = pMsgBuf != null ? pMsgBuf.ToString() : string.Empty;
 
         return retval;
      }
}

So if we catch any exception, besides obtaining the exception object itself we get the error and message code from the operating system

Win32Error osError = Win32Error.Empty;
 
try {
// Some funky exception here...
} catch(Exception ex) {
  osError = ex.GetWin32ErrorDescription();
  Console.WriteLine(string.Format("Code: {0} - Description: {1}", 
            new object[] {osError.ErrorCode, osError.Description}));  
}

If we implement everything we’ve previously mentioned, see the difference between the exception message and the message provided by the operating system

image_thumb[2]

Hope this helps,

Regards,

Angel

Extendiendo la clase Exception, recuperando mensajes de error de Win32 además de los métodos de extensión

El manejo estructurado de excepciones, es una de las características que como desarrolladores utilizamos más a menudo, ya no es necesario estar implementando etiquetas On Error u otro mecanismo poco elegante para manejar condiciones de error, sin embargo el .NET Framework es una capa entre el sistema operativo y nuestra aplicación, aunque la clase Exception describe una condición de error, carece del código de error y descripción asociado al sistema operativo, en muchas ocasiones el interpretar algunas excepciones puede llegar en cierto punto ser algo frustrante porque no es muy descriptivo, por lo que no sería mala idea recuperar la información de error emitida por Windows. Con el paso de los años, el API de Windows ha crecido y así mismo la cantidad de mensajes de error subyacentes, en el SDK de la plataforma se consigue información sobre estos mensajes de error y lo mismo sucede con el WDK, por lo que se me ocurrió la idea de extender la clase Exception a través de un método de extensión. Los métodos de extensión, nos permiten “agregar “métodos a tipos de datos existentes sin la necesidad de crear un tipo derivado, recompilar ó modificar el tipo original. Estos métodos son un tipo especial de método estático que son llamados como si fueran métodos de instancia en el tipo extendido. Su uso más común es con LINQ para así conseguir funcionalidad adicional de los tipos IEnumerable e IEnumerable<T>.

Para recuperar la descripción de un código de error utilizamos la función FormatMessage, como mostramos a continuación

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true,
           CallingConvention = CallingConvention.Winapi)]
public static extern int FormatMessage(int dwFlags, int lpSource, int dwMessageId,
                     int dwLanguageId, out StringBuilder lpBuffer,
                       int nSize, IntPtr va_list);

El método de extensión utilizado con la clase Exception es el siguiente

    public static class ExceptionExtension {
    /// <summary>
    /// Gets the win32 error description.
    /// </summary>
    /// <param name="ex">The ex.</param>
    /// <returns></returns>
    public static Win32Error GetWin32ErrorDescription(this Exception ex) {
        Win32Error retval = Win32Error.Empty;
        StringBuilder pMsgBuf = new StringBuilder();
 
        int lastError = FormatErrorHelper.GetLastError();
 
        FormatErrorHelper.FormatMessage(
               FormatErrorHelper.FORMAT_MESSAGE_ALLOCATE_BUFFER |
               FormatErrorHelper.FORMAT_MESSAGE_FROM_SYSTEM,
               FormatErrorHelper.NULL,   lastError, 
               FormatErrorHelper.MakeLangID(FormatErrorHelper.LANG_NEUTRAL,
               FormatErrorHelper.SUBLANG_DEFAULT), out pMsgBuf, 0, IntPtr.Zero);
 
         retval.ErrorCode = lastError;
         retval.Description = pMsgBuf != null ? pMsgBuf.ToString() : string.Empty;
 
         return retval;
      }
}

Por lo que al atrapar alguna excepción, además de obtener el objeto Exception como tal también obtengo el código y mensaje de error del sistema operativo

Win32Error osError = Win32Error.Empty;
 
try {
// Some funky exception here...
} catch(Exception ex) {
  osError = ex.GetWin32ErrorDescription();
  Console.WriteLine(string.Format("Code: {0} - Description: {1}", 
            new object[] {osError.ErrorCode, osError.Description}));  
}

Si ponemos en práctica todo lo mencionado anteriormente, vean lo diferente que es el mensaje de proporcionado por el .NET Framework al del sistema operativo

image

Espero sea de utilidad,

Saludos,

Angel

More Posts Next page »