Definition von __doPostBack erzwingen

February 15, 2008 at 2:13 PMAndre Loker

Das Problem

Zwei aspx-Seiten, beide rufen den identischen JavaScript Code auf:

   1: function postBackHiddenField(hiddenFieldID) {
   2:     var hiddenField = $get(hiddenFieldID);
   3:     if (hiddenField) {
   4:         hiddenField.value = (new Date()).getTime();
   5:         __doPostBack(hiddenFieldID,'');
   6:     }
   7: }

Auf Seite A funktioniert der JS Code, auf Seite B nicht. Bei letzterem sagt der JavaScript-Debugger:

__doPostBack not defined

(Hoffentlich) verständlicherweise verursachte das bei mir ziemliches Kopfkratzen. __doPostBack sollte ja eigentlich auf einer ASPX Seite nicht fremd sein. Bis mir auf einmal siedendheiß einfiel, dass ASP.NET vielleicht so schlau ist, und nur dann die enstprechenden Script-Dateien inkludiert, wenn tatsächlich ein Control auch ein Postback via JavaScript ausführen kann. Auf Seite A war so ein Control vorhanden, auf Seite B jedoch nicht. Demzufolge wurde für Seite B der Code für __doPostBack nicht in die Seite eingepflegt.

Der Quick-Fix

Um das Problem zu lösen, reicht es schon aus, einfach ein Control einzufügen, dass einen Postback mittels JavaScript auslöst, wie etwa ein LinkButton. Dieser muss auf der Client Seite nicht zwingend sichtbar sein, d.h. man kann ihn clientseitig ausblenden:

   1: <asp:LinkButton runat="server" Style='display: none;' ID="enablesJSPostBack" />

Achtung: es funktioniert nicht, wenn man den LinkButton schon serverseitig ausblendet mit Visible="false"! In dem Fall erzeugt ASP.NET ebenfalls den nötigen Code für __doPostBack nicht.

Technische Details und eine elegantere Lösung

Ich habe ein wenig mit Hilfe von Reflector im Gedärm von ASP.NET gewühlt um nachzusehen, unter welchen Umständen der __doPostBack-Code gerendert wird. Womöglich gibt es eine Methode, mit der man eben dieses erreichen kann, ohne ein Control in der Seite erstellen zu müssen. Entscheidend ist die Methode RegisterPostBackScript der Page-Klasse. Wird sie angerufen erzeugt die Seite den __doPostBack Code. Leider ist die Methode internal. Allerdings wird die Methode unter anderem aufgerufen, wenn man ClientScript.GetPostBackEventReference anruft. Mit dieser Methode kann man den __doPostBack Script-Code erzeugen, der einen Script-seitiges Postback ausführt. Ich habe meiner Page-Klasse folgende Methode hinzugefügt, um (ohne zusätzliche Controls) sicher zu stellen, dass das __doPostBack-Script erzeugt wird:

   1: protected void EnsureDoPostBackDefined(){
   2:     ClientScript.GetPostBackEventReference(this, string.Empty);
   3: }

Die Methode EnsureDoPostBackDefined wird einfach z.B. im Load Ereignis aufgerufen und schon klappt es mit __doPostBack.

Posted in: ASP.NET | Snippets

Tags: ,

K.I.S.S. mit dem Holzhammer

February 12, 2008 at 6:09 PMAndre Loker

Manchmal ist es echt so, dass der leichteste Weg doch noch am besten ist. Das musste ich heute auf einigermaßen schmerzliche Art erfahren.

Das Problem

Ich brauchte in einer Anwendung die Möglichkeit, das System herunter zu fahren. Genauer: nach Ablauf eines Timers sollte das Programm einen kompletten Shutdown des Systems einleiten.

Die gut gemeinte Lösung

Mir fiel recht schnell shutdown.exe ein, ein Dienstprogramm, dass ab Windows 2000 mit an Bord ist. Aber mal ehrlich - ich kann ja nicht einfach so ein Programm aufrufen, das dann die Arbeit erledigt. Wofür gibt es die WinAPI? Also, frisch gegoogelt, und das Stichwort war ExitWindowsEx. So soll es sein! P/Invoke an die Macht:

   1: [DllImport( "user32.dll" )]
   2: private static extern bool ExitWindowsEx( uint mode, uint reason );

Eigentlich ja ganz schön, wenn da nicht bei de, für mich interessanten Flag EWX_POWEROFF die lapidare Bemerkung stünde:

The calling process must have the SE_SHUTDOWN_NAME privilege. For more information, see the following Remarks section.

Offenbar konnte ich diese Flags nicht verwenden, ohne das entsprechende Privileg zu setzen. Das allerdings erforderte wiederum eine beachtliche Menge Code, nicht zu letzt wegen der verschiedenen Interop-Konstrukte. Meine Lösung sah schließlich so aus:

   1: /// <summary>
   2: /// Defines the shutdown mode
   3: /// </summary>
   4: public enum ShutDownMode {
   5:     /// <summary>
   6:     /// Log off current user.
   7:     /// </summary>
   8:     LogOff = 0,
   9:     /// <summary>
  10:     /// Shut down computer without turning power off (some OS's still power off though)
  11:     /// </summary>
  12:     ShutDown = 0x1,
  13:     /// <summary>
  14:     /// Shut down computer and turn power off if supported.
  15:     /// </summary>
  16:     ShutDownAndPowerOff = 0x8,
  17:     /// <summary>
  18:     /// Reboot system.
  19:     /// </summary>
  20:     Reboot = 0x2,
  21:     /// <summary>
  22:     /// Reboot system and restart all applications that used RegisterApplicationRestart 
  23:     /// to register for re-start.s
  24:     /// </summary>
  25:     RebootAndRestartApps = 0x40
  26: }
  27:  
  28: /// <summary>
  29: /// 
  30: /// </summary>
  31: public enum ForceType {
  32:     /// <summary>
  33:     /// Applications get the chance to notify the user of the upcoming shutdown.
  34:     /// This way the user may save changes.
  35:     /// </summary>
  36:     None = 0,
  37:     /// <summary>
  38:     /// Applications get the chance to let the user save changes. After a time-out
  39:     /// the applications are still forced to shutdown.
  40:     /// </summary>
  41:     ForceIfHung = 0x10,
  42:     /// <summary>
  43:     /// The user does not get a chance to save changes.
  44:     /// </summary>
  45:     Force = 0x4
  46: }
  47:  
  48: /// <summary>
  49: /// Utility class to exit windows or the current interactive logon session.
  50: /// </summary>
  51: public static class ShutdownUtil {
  52:     #region Error messages
  53:     private const string ErrorLookupPrivilegeValue = "Konnte die Privilegdaten nicht anfordern.";
  54:     private const string ErrorOpenProcessToken = "Konnte token nicht öffnen";
  55:     private const string ErrorAdjustTokenPrivileges = "Du hast nicht die Rechte, um den Computer herunterzufahren.";
  56:     #endregion
  57:  
  58:     /// <summary>
  59:     /// Exits windows or logs off the current user
  60:     /// </summary>
  61:     /// <param name="mode">The shutdown mode.</param>
  62:     /// <param name="forceType">Defines whether and how a shutdown is forced.</param>
  63:     /// <returns><c>true</c> if shutdown was initiated; otherwise, <c>false</c></returns>
  64:     public static bool Shutdown( ShutDownMode mode, ForceType forceType ) {
  65:         IntPtr privileges = SetPrivileges();
  66:  
  67:         try {
  68:             uint flags = (uint)mode | (uint)forceType;
  69:             // Reason for shutdown (for simplicity)
  70:             // "Other (Planned)"
  71:             // SHTDN_REASON_MAJOR_OTHER | 
  72:             // SHTDN_REASON_MINOR_OTHER | 
  73:             // SHTDN_REASON_FLAG_PLANNED:
  74:             const uint PlannedShutdown = 0x80000000;
  75:  
  76:             if ( !ExitWindowsEx( flags, PlannedShutdown ) ) {
  77:                 Console.WriteLine( Marshal.GetLastWin32Error() );
  78:                 return false;
  79:             }
  80:             return true;
  81:         } finally {
  82:             CloseHandle( privileges );
  83:         }
  84:     }
  85:  
  86:     private static IntPtr SetPrivileges() {
  87:         TokenPrivileges priv = new TokenPrivileges();
  88:         priv.PrivilegesCount = 1;
  89:         priv.Privileges = new LuidAndAttributes[priv.PrivilegesCount];
  90:         // Get
  91:         if ( !LookupPrivilegeValue( "", "SeShutdownPrivilege", out priv.Privileges[ 0 ] ) ) {
  92:             throw new Exception( ErrorLookupPrivilegeValue );
  93:         }
  94:         priv.Privileges[ 0 ].Attributes = SE_PRIVILEGE_ENABLED;
  95:  
  96:         TokenPrivileges rec = new TokenPrivileges();
  97:  
  98:         IntPtr token;
  99:         IntPtr procHandle = Process.GetCurrentProcess().Handle;
 100:         if ( !OpenProcessToken( procHandle, TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, out token ) ) {
 101:             throw new Exception( ErrorOpenProcessToken );
 102:         }
 103:         try {
 104:             int recSize;
 105:             if ( !AdjustTokenPrivileges( token, false, ref priv,
 106:                       Marshal.SizeOf( rec ), ref rec, out recSize ) ||
 107:                  Marshal.GetLastWin32Error() == 1300 /*ERROR_NOT_ALL_ASSIGNED*/ ) {
 108:                 throw new Exception( ErrorAdjustTokenPrivileges );
 109:             }
 110:             return token;
 111:         } catch {
 112:             CloseHandle( token );
 113:             throw;
 114:         }
 115:     }
 116:  
 117:     #region Interop
 118:     [DllImport( "user32.dll" )]
 119:     private static extern bool ExitWindowsEx( uint mode, uint reason );
 120:     
 121:     // enable privilege on token 
 122:     private const uint SE_PRIVILEGE_ENABLED = 2;
 123:     // used by OpenProcessToken
 124:     private const uint TOKEN_QUERY = 0x0008;
 125:     // used by OpenProcessToken
 126:     internal const uint TOKEN_ADJUST_PRIVILEGES = 0x0020;
 127:  
 128:  
 129:     [DllImport( "Advapi32.dll" )]
 130:     private static extern bool LookupPrivilegeValue( string system,
 131:                                                      string privilege,
 132:                                                      out LuidAndAttributes result );
 133:  
 134:     [DllImport( "advapi32.dll" )]
 135:     [return : MarshalAs( UnmanagedType.Bool )]
 136:     private static extern bool OpenProcessToken( IntPtr ProcessHandle,
 137:                                                  UInt32 DesiredAccess,
 138:                                                  out IntPtr TokenHandle );
 139:  
 140:     [DllImport( "Advapi32.dll" )]
 141:     private static extern bool AdjustTokenPrivileges( IntPtr handle,
 142:                                                       bool disableAll,
 143:                                                       ref TokenPrivileges newState,
 144:                                                       int bufferLength,
 145:                                                       ref TokenPrivileges realState,
 146:                                                       out int foo );
 147:  
 148:     [DllImport( "Kernel32.dll" )]
 149:     private static extern bool CloseHandle( IntPtr handle );
 150:  
 151:     #region Nested type: LuidAndAttributes
 152:     [StructLayout( LayoutKind.Sequential )]
 153:     public struct LuidAndAttributes {
 154:         public long LUID;
 155:         public uint Attributes;
 156:     }
 157:     #endregion
 158:  
 159:     #region Nested type: TokenPrivileges
 160:     [StructLayout( LayoutKind.Sequential )]
 161:     private struct TokenPrivileges {
 162:         public uint PrivilegesCount;
 163:         [MarshalAs( UnmanagedType.ByValArray, SizeConst = 1 )] 
 164:         public LuidAndAttributes[] Privileges;
 165:     }
 166:     #endregion
 167:  
 168:     #endregion
 169: }

Spektakulär viel Code, um ein System herunter zu fahren.  Und das Beste: es funktioniert nicht mal! Zumindest unter meinem eingeschränkten Vista Account schien es so, als ob der Account die entsprechenden Privilegien nicht erhalten konnte. Ich bin zugegebenerweise beileibe kein Token- und Privlegien-Fachmann, aber trotzdem war das irgendwie ernüchternd. Fazit: viel Code, nichts dahinter.

Die offensichtliche Lösung

Schande über mein Haupt. Da hab ich es so gut gemeint mit meiner "Profi-Lösung" und dann funktioniert sie nicht. Mir blieb also nicht viel übrig, als auf die schnöde Variante mittels shutdown.exe auszuweichen. Hier also der vollständige Code der Lösung:

   1: /// <summary>
   2: /// Defines the shutdown mode
   3: /// </summary>
   4: public enum ShutDownMode {
   5:     /// <summary>
   6:     /// Log off current user.
   7:     /// </summary>
   8:     LogOff = 0,
   9:     /// <summary>
  10:     /// Shut down computer without turning power off (some OS's still power off though)
  11:     /// </summary>
  12:     ShutDown = 1,
  13:     /// <summary>
  14:     /// Reboot system.
  15:     /// </summary>
  16:     Reboot = 2,
  17: }
  18:  
  19: /// <summary>
  20: /// Utility class to exit windows or the current interactive logon session.
  21: /// </summary>
  22: public static class ShutdownUtil {
  23:     /// <summary>
  24:     /// Exits windows or logs off the current user
  25:     /// </summary>
  26:     /// <param name="mode">The shutdown mode.</param>
  27:     /// <returns>The exit code of the shutdown application.
  28:     /// </returns>
  29:     public static int Shutdown( ShutDownMode mode ) {
  30:         
  31:         string switches;
  32:         switch ( mode ) {
  33:             case ShutDownMode.LogOff:
  34:                 switches = "-l -f";
  35:                 break;
  36:             case ShutDownMode.ShutDown:
  37:                 switches = "-s -t 10 -f";
  38:                 break;
  39:             case ShutDownMode.Reboot:
  40:                 switches = "-r -t 10 -f";
  41:                 break;
  42:             default:
  43:                 throw new ArgumentOutOfRangeException( "mode" );
  44:         }
  45:         ProcessStartInfo info = new ProcessStartInfo( "shutdown", switches );
  46:         info.CreateNoWindow = true;
  47:         info.UseShellExecute = false;
  48:         
  49:         Process p = Process.Start( info );
  50:         p.WaitForExit();
  51:         return p.ExitCode;
  52:     }
  53: }

Deutlich weniger Code, wie man sieht. Und das Schöne - oder Gemeine, je nach Blickwinkel - ist, dass diese Lösung auch mit einem eingeschränkten Account funktioniert.

Was lernen wir daraus?

K.I.S.S. - Keep it simple, stupid! Die offensichtlichsten und leichtesten Lösungen sind oft doch die richtigen. 

Update 06/09/2008

Eine Alternatieve zum Herunterfahren des Computers hat mir heute ein Kollege zugetragen. Mittels WMI lässt sich der Shutdown elegant und ohne das Starten eines zusätzlichen Prozesses einleiten:

   1: var wmi = new System.Management.ManagementClass("Win32_OperatingSystem");
   2: wmi.Scope.Options.EnablePrivileges = true;
   3: foreach (System.Management.ManagementObject o in wmi.GetInstances()) {
   4:   o.InvokeMethod("Shutdown", null);
   5: }

Vielen Dank, Hennie!

Posted in: Snippets

Tags: , , ,