Von Windows 7 aus via ODBC auf eine benannte SQL-Server-2000-Instanz zugreifen

Heute ist Legacy-Tag!

Issue

Für ein Projekt müssen wir von Microsoft Office Access 2010 auf einen SQL Server 2000 via ODBC zugreifen.

Die Herausforderung ist, dass der SQL Server 2000 als benannte Instanz („Named instance“) auf dem Server installiert ist (Windows 2008 R2). Also so was wie „MEINSERVER\SQL2000“, anstatt nur „MEINSERVER“.

Lokal auf dem Server kann ich wunderbar auf den SQL Server 2000 zugreifen, alleine via Netzwerk von einem PC mit Windows 7 ging es nicht. Es kam immer wieder die Meldung, dass keine Verbindung hergestellt werden konnte.

Lösung

Die Lösung war dann im Microsoft-Knowledge-Base-Artikel „How to connect to SQL Server by using an earlier version of SQL Server“ zu finden:

Vom Prinzip via cliconfg.exe-Programm auf dem Client einen Alias zu IP-Adresse und Port des SQL Server 2000 machen und dann über den Alias zugreifen.

Zitate aus der Lösung:

Server:

Determine the TCP/IP port number of the instance of SQL Server. To do this, use one of the following methods, depending on which version of SQL Server that you are running.

  1. On the server that is running SQL Server 2000, start the Server Network Utility. To do this, click Start, click All Programs, click Microsoft SQL Server, and then click Server Network Utility.
  2. Click the General tab, and then select the instance that you want from the Instances list.
  3. Click TCP/IP, and then click Properties. The TCP/IP port number for this instance is shown. Note this number so that you can use it later.

Client:

Configure the server alias on the client computer. To do this, use one of the following methods, depending on your version of SQL Server.

  1. Start the Client Network Utility. To do this, click Start, click Run, type cliconfg.exe, and then press Enter.
  2. On the General tab, verify that TCP/IP appears in the list under Enabled protocols by order.
  3. Click the Alias tab, and then click Add.
  4. Under Network libraries, select TCP/IP.
  5. In the Server name text box, type the IP address of the server that is running SQL Server 2000.
    Note The IP address that you type here is the one that is configured to use the TCP/IP port number.
  6. Click to clear the Dynamically determine port check box, and then type the port number of the instance of SQL Server 2000 in the Port number text box.
  7. Type a name in the Server alias text box, and then click OK.

Dann ging die ODBC-Verbindung („Verbindungstest“ war erfolgreich).

Texte in JavaScript-Dateien in ASP.NET übersetzen

Der Artikel „Localize text in JavaScript files in ASP.NET“ hat eine hervorragende Lösung um Texte in JavaScript-Dateien (und auch in CSS-Dateien) zu übersetzen.

Das klappt sowohl mit ASP.NET-Website-Projekten, als auch mit ASP.NET-Web-Applikations-Projekten (also dem kompilierten).

Bis auf einen Fall, der mir heute passiert ist. Da kam immer eine Exception, dass er die Ressourcen für die aktuelle Culture nicht findet.

Die Lösung sah dann so aus, dass ich eine Funktion geändert habe.

Alt:

public static string TranslateScript(string text)
{
	if (string.IsNullOrEmpty(text))
	{
		return text;
	}
	else
	{
		var matches = Regex.Matches(text);
        var manager = new ResourceManager(typeof(Resources.Resources));

		foreach (Match match in matches)
		{
			var value = match.Groups[1].Value;

            var obj = manager.GetObject(value);
            if (obj != null)
			{
				text = text.Replace(match.Value, CleanText(obj.ToString()));
			}
		}

		return text;
	}
}

Neu:

public static string TranslateScript(string text)
{
	if (string.IsNullOrEmpty(text))
	{
		return text;
	}
	else
	{
		var matches = Regex.Matches(text);

		foreach (Match match in matches)
		{
			var value = match.Groups[1].Value;

			var obj = HttpContext.GetGlobalResourceObject(@"Resources", value);
            if (obj != null)
			{
				text = text.Replace(match.Value, CleanText(obj.ToString()));
			}
		}

		return text;
	}
}

D.h. ich habe den Zugriff über eine Klasse in einen Zugriff über „GetGlobalResourceObject“ umgewandelt, der erst zur Laufzeit versucht das aufzulösen.

Bandbreite und Paketumlaufzeit

Das hier ist sicher jetzt nicht-wissenschaftlich und ggf. auch in Teilen falsch/ungenau. Ich möchte das trotzdem mal für mich festhalten:

Eine Internetanbindung (z.B. zu einem Server) hat zwei Schlüssel-Parameter, die sie als „schnell“ erscheinen lassen:

Anhand der Bandbreite ist quasi definiert, wie viele Daten in welcher Zeiteinheit übertragen werden können. Die Paketumlaufzeit beschreibt, wie lange es dauert, bis die Daten zum Server übertragen werden und eine Antwort wieder beim ursprünglichen Absender ist.

Aus meiner Sicht sind beide Werte wichtig um die Qualität einer Internetverbindung bewerten zu können; eine zwar hohe Bandbreite gepaart jedoch mit einer lange Paketumlaufzeit („Lag„) ergibt wiederum ein unbefriedigendes Ergebnis, wenn ich z.B. via Remote Desktop auf einen Rechner zugreife, also viele kleine Pakete hin und hergehen.

Bubble Mouse Move events from child controls up the hierarchy in Windows Forms

Windows Forms (or „WinForms“ for short) does not know the concept of event bubbling (also called „event propagation“ sometimes). To solve this in terms of command routing, I’ve written some small classes earlier.

To bubble up events from child controls to parent controls (or the form itself), the idea is to hook into the child control creation and hook up for those specific events and manually forward them.

Based on this idea and with the help of a forum answer on MSDN, I’ve written a small class that you can attach to a control and get all child control MouseMove events. The class looks like:

public sealed class MouseEventBubbler
{
    private readonly Control _attachTo;

    public MouseEventBubbler(Control attachTo)
    {
        _attachTo = attachTo;

        _attachTo.MouseMove += _attachTo_MouseMove;

        _attachTo.ControlAdded += _attachTo_ControlAdded;
        _attachTo.ControlRemoved += _attachTo_ControlRemoved;

        foreach (Control control in _attachTo.Controls)
        {
            AttachToControl(control);
        }
    }

    public void _attachTo_MouseMove(object sender, MouseEventArgs e)
    {
        OnMouseMove(e);
    }

    public event MouseEventHandler MouseMove;

    private void _attachTo_ControlAdded(object sender, ControlEventArgs e)
    {
        AttachToControl(e.Control);
    }

    private void _attachTo_ControlRemoved(object sender, ControlEventArgs e)
    {
        DetachFromControl(e.Control);
    }

    private void AttachToControl(Control c)
    {
        c.MouseMove += Child_MouseMove;
        c.ControlAdded += Child_ControlAdded;
        c.ControlRemoved += Child_ControlRemoved;
        AttachToChildren(c);
    }

    private void AttachToChildren(Control parent)
    {
        foreach (Control child in parent.Controls)
        {
            AttachToControl(child);
        }
    }

    private void DetachFromControl(Control c)
    {
        DetachFromChildren(c);
        c.MouseMove -= Child_MouseMove;
        c.ControlAdded -= Child_ControlAdded;
        c.ControlRemoved -= Child_ControlRemoved;
    }

    private void DetachFromChildren(Control parent)
    {
        foreach (Control child in parent.Controls)
        {
            DetachFromControl(child);
        }
    }

    private void Child_ControlAdded(object sender, ControlEventArgs e)
    {
        AttachToControl(e.Control);
    }

    private void Child_ControlRemoved(object sender, ControlEventArgs e)
    {
        DetachFromControl(e.Control);
    }

    private void Child_MouseMove(object sender, MouseEventArgs e)
    {
        var pt = e.Location;
        var child = (Control)sender;
        do
        {
            pt.Offset(child.Left, child.Top);
            child = child.Parent;
        }
        while (child != _attachTo);

        var newArgs = new MouseEventArgs(e.Button, e.Clicks, pt.X, pt.Y, e.Delta);
        OnMouseMove(newArgs);
    }

    private void OnMouseMove(MouseEventArgs newArgs)
    {
        var h = MouseMove;
        if (h != null)
        {
            h(this, newArgs);
        }
    }
}

I’ve also saved it as a PasteBin.

The class can be adjusted to match other events than the MouseMove event, if required.

Dateigrößenangaben in C# menschenlesbar formatieren

Um aus Byte-Angaben (z.B. von Dateigrößen) menschenlesbare Werte (z.B. „12.5GB“) zu machen, sowie den anderen Weg, habe ich mir ein bisschen Code geschrieben bzw. von Stack Overflow zusammen gesucht:

public static class SizeTranslationHelper
{
    /// <summary>
    /// Converts bytes to human-readable, e.g. "12.5GB".
    /// </summary>
    public static string MakeLazy(ulong byteCount)
    {
        string[] suf = { @"B", @"KB", @"MB", @"GB", @"TB", @"PB", @"EB" }; //Longs run out around EB
        if (byteCount == 0)
            return "0" + suf[0];
        var bytes = (ulong)Math.Abs((decimal)byteCount);
        var place = Convert.ToInt32(Math.Floor(Math.Log(bytes, 1024ul)));
        var num = Math.Round(bytes / Math.Pow(1024ul, place), 1);
        return (Math.Sign((decimal)byteCount) * num).ToString(CultureInfo.InvariantCulture) + suf[place];
    }

    /// <summary>
    /// Converts human-readable, e.g. "12.5GB", to bytes.
    /// </summary>
    public static ulong TranslateLazySize(string sizeLazy)
    {
        sizeLazy = sizeLazy.ToLowerInvariant();

        ulong result = 0;
        if (tryParse(ref result, sizeLazy, @"b", 1ul))
        {
            return result;
        }
        else if (tryParse(ref result, sizeLazy, @"kb", 1024ul))
        {
            return result;
        }
        else if (tryParse(ref result, sizeLazy, @"mb", 1024ul * 1024ul))
        {
            return result;
        }
        else if (tryParse(ref result, sizeLazy, @"gb", 1024ul * 1024ul * 1024ul))
        {
            return result;
        }
        else if (tryParse(ref result, sizeLazy, @"tb", 1024ul * 1024ul * 1024ul * 1024ul))
        {
            return result;
        }
        else if (tryParse(ref result, sizeLazy, @"pb", 1024ul * 1024ul * 1024ul * 1024ul * 1024ul))
        {
            return result;
        }
        else if (tryParse(ref result, sizeLazy, @"eb", 1024ul * 1024ul * 1024ul * 1024ul * 1024ul * 1024ul))
        {
            return result;
        }
        else
        {
            decimal r;
            if (decimal.TryParse(sizeLazy, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out r))
            {
                return (ulong)r;
            }
            else
            {
                throw new Exception(string.Format(@"Cannot parse '{0}' to number.", sizeLazy));
            }
        }
    }

    private static bool tryParse(ref ulong result, string sizeLazy, string suffix, ulong factor)
    {
        decimal r;
        if (
            sizeLazy.EndsWith(suffix) &&
            decimal.TryParse(sizeLazy.Substring(0, sizeLazy.Length - suffix.Length), NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out r))
        {
            result = (ulong)(r * factor);
            return true;
        }
        else
        {
            return false;
        }
    }
}

Hier noch der Pastebin-Eintrag dazu.

Fehlermeldung „System.MethodAccessException“ bei .NET-4-Programmen beheben

Habe vorhin ein .NET-2-Programm (Windows Forms) nach .NET 4.5 geändert (also in der Projektkonfiguration in Visual Studio .NET 2012 das Ziel-Framework geändert.

Bei diesem Aufruf hat es dann gekracht:

AppDomain.CurrentDomain.UnhandledException += currentDomain_UnhandledException;

Dort kam dann eine Fehlermeldung:

System.MethodAccessException was unhandled HResult=-2146233072 Message=Fehler beim Versuch der SecurityTransparent-Methode „ZetaHelpdesk.Main.Code.AppHost.Host.Main()“, auf die sicherheitskritische Methode „System.AppDomain.add_UnhandledException(System.UnhandledExceptionEventHandler)“ zuzugreifen.

Die Assembly „zeta-helpdesk, Version=2.1.0.1, Culture=neutral, PublicKeyToken=1dbe5f735b90e083“ ist mit „AllowPartiallyTrustedCallersAttribute“ markiert und verwendet das Sicherheitstransparenzmodell der Stufe 2. Bei Festlegung der Transparenz auf Stufe 2 werden alle Methoden in AllowPartiallyTrustedCallers-Assemblys standardmäßig sicherheitstransparent, was die Ursache der Ausnahme sein kann.

Source=zeta-helpdesk

StackTrace: bei ZetaHelpdesk.Main.Code.AppHost.Host.Main() in c:\P\Zeta Helpdesk\Source\Core\Main\Code\AppHost\Host.cs:Zeile 100. bei System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args) bei System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args) bei Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly() bei System.Threading.ThreadHelper.ThreadStart_Context(Object state) bei System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) bei System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) bei System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) bei System.Threading.ThreadHelper.ThreadStart() InnerException:

Wer die Fehlermeldung aufmerksam liest, findet auch schon die Ursache. Da steht nämlich, dass ein Assembly-Attribut hier unpassend ist. Und tatsächlich habe ich folgendes in der Datei „AssemblyInfo.cs“ stehen:

[assembly: AllowPartiallyTrustedCallers]

Ich hatte seinerzeit aus diesem Grund das Attribut eingefügt.

Als ich das Attribut dann entfernt habe, lief alles wie gewünscht. Neu kompilieren natürlich auch noch 😉

Fehlermeldung „CS0103: Der Name ‚ViewBag‘ ist im aktuellen Kontext nicht vorhanden“ bei ASP.NET MVC 4 lösen

Heute habe ich bei einem neu deployten Projekt mit ASP.NET 4.5 und MVC 4 folgende Fehlermeldung erhalten:

CS0103: Der Name ‚ViewBag‘ ist im aktuellen Kontext nicht vorhanden
CS0103: The name ‚ViewBag‘ does not exist in the current context

Eine ähnliche Meldung lautet:

CS0103: Der Name ‚model‘ ist im aktuellen Kontext nicht vorhanden
CS0103: The name ‚model‘ does not exist in the current context

Lokal lief alles, auf dem Produktivserver kam die Fehlermeldung. Ich habe lange gesucht, bis ich die funktionierende Lösung gefunden habe.

Lösung

Die Lösung war im Endeffekt, dass es zwei Web.Config-Dateien gibt:

Diese im Views-Ordner hat bei mir gefehlt. Sobald ich diese hinzugefügt und entsprechend gefüllt hatte, hat alles funktioniert.

Alle Sichten in einer Microsoft-SQL-Server-Datenbank aktualisieren

Um alle Views in einem Rutsch zu aktualisieren, hilft folgender Code:

DECLARE @view AS VARCHAR(255);

DECLARE views_cursor CURSOR FOR 
    SELECT TABLE_SCHEMA + '.' +TABLE_NAME FROM INFORMATION_SCHEMA.TABLES 
    WHERE TABLE_TYPE = 'VIEW' 
    AND OBJECTPROPERTY(OBJECT_ID(TABLE_NAME), 'IsMsShipped') = 0 
    ORDER BY TABLE_SCHEMA,TABLE_NAME 

OPEN views_cursor 

FETCH NEXT FROM views_cursor 
INTO @view 

WHILE (@@FETCH_STATUS <> -1) 
BEGIN
    BEGIN TRY
        EXEC sp_refreshview @view;
        PRINT @view;
    END TRY
    BEGIN CATCH
        PRINT 'Error during refreshing view "' + @view + '".';
    END CATCH;

    FETCH NEXT FROM views_cursor 
    INTO @view 
END 

CLOSE views_cursor; 
DEALLOCATE views_cursor;

Funktioniert wunderbar.

In der Praxis z. B. hilfreich, wenn in den zugrunde liegenden Tabellen Spalten hinzugekommen sind oder entfernt wurden.

CryptographicException bei Verwendung von Oracle.ManagedDataAccess lösen

Gestern hatte ein Kollege beim Aufsetzen einer Website ASP.NET MVC 4 auf einem IIS unter Windows Server 2008 R2 eine Fehlermeldung:

[CryptographicException: Unbekannter Fehler -1073741766.]
   System.Security.Cryptography.ProtectedData.Protect(Byte[] userData, Byte[] optionalEntropy, DataProtectionScope scope) +504
   Oracle.ManagedDataAccess.Client.ConnectionString.Secure() +493
   OracleInternal.ConnectionPool.PoolManager`3.Initialize(ConnectionString cs) +1760
   OracleInternal.ConnectionPool.OraclePoolManager.Initialize(ConnectionString cs) +21
   OracleInternal.ConnectionPool.OracleConnectionDispenser`3.GetPM(ConnectionString cs, PM conPM, ConnectionString pmCS, Byte[] securedPassword, Byte[] securedProxyPassword, Boolean& bAuthenticated, Boolean& newPM) +296
   OracleInternal.ConnectionPool.OracleConnectionDispenser`3.Get(ConnectionString cs, PM conPM, ConnectionString pmCS, Byte[] securedPassword, Byte[] securedProxyPassword) +1576
   Oracle.ManagedDataAccess.Client.OracleConnection.Open() +3756
   OracleInternal.EntityFramework.EFOracleProviderServices.GetDbProviderManifestToken(DbConnection connection) +274
   System.Data.Common.DbProviderServices.GetProviderManifestToken(DbConnection connection) +91

[ProviderIncompatibleException: Der Anbieter hat keine ProviderManifestToken-Zeichenfolge zurückgegeben.]
   System.Data.Common.DbProviderServices.GetProviderManifestToken(DbConnection connection) +10947809
   System.Data.Entity.ModelConfiguration.Utilities.DbProviderServicesExtensions.GetProviderManifestTokenChecked(DbProviderServices providerServices, DbConnection connection) +48

[ProviderIncompatibleException: An error occurred while getting provider information from the database. This can be caused by Entity Framework using an incorrect connection string. Check the inner exceptions for details and ensure that the connection string is correct.]
   System.Data.Entity.ModelConfiguration.Utilities.DbProviderServicesExtensions.GetProviderManifestTokenChecked(DbProviderServices providerServices, DbConnection connection) +242
   System.Data.Entity.DbModelBuilder.Build(DbConnection providerConnection) +82
   System.Data.Entity.Internal.LazyInternalContext.CreateModel(LazyInternalContext internalContext) +88
   System.Data.Entity.Internal.RetryLazy`2.GetValue(TInput input) +248
   System.Data.Entity.Internal.LazyInternalContext.InitializeContext() +524
   System.Data.Entity.Internal.InternalContext.GetEntitySetAndBaseTypeForType(Type entityType) +26
   System.Data.Entity.Internal.Linq.InternalSet`1.Initialize() +71
   System.Data.Entity.Internal.Linq.InternalSet`1.GetEnumerator() +21
   System.Collections.Generic.List`1..ctor(IEnumerable`1 collection) +446
   System.Linq.Enumerable.ToList(IEnumerable`1 source) +80

Zunächst war es eine Herausforderung, statt den „normalen“ Oracle.DataAccess-Klassen die Oracle.ManagedDataAccess-Klassen zum Laufen zu bringen. Hier haben uns zwei Artikel geholfen:

Wir haben dann zum Lösen des eigentlichen Fehlers lange rumgesucht, viel ausprobiert und schließlich beim Weblog-Artikel „SOLVED: Windows Identity Foundation – “The system cannot find the file specified”“ fündig geworden.

Die Lösung bestand dann schlicht darin, im Anwendungspool (App Pool) für die Anwendung die Option „Benutzerprofil laden“ auf „True“ zu stellen:

IIS App Pool - Benutzerprofil laden

Danach lief alles wie gewünscht.

How to solve „An app on your PC needs the following Windows feature: .NET Framework 3.5 (includes .NET 2.0 and 3.0)“ on Windows 8

Are you a software developer and ever got this message when testing your .NET 2.0 WinForms application on Windows 8?

An app on your PC needs the following Windows feature: .NET Framework 3.5 (includes .NET 2.0 and 3.0)

An app on your PC needs the following Windows feature: .NET Framework 3.5 (includes .NET 2.0 and 3.0)

Even though your application would run on .NET 4, too?

Here is my story on why this happended to me and how to solve.

Why this happens

When you run a .NET application, Windows 8 seems to check whether the required .NET Framework is available. If it is not available, the above message is being displayed.

How to solve it

Windows 8 cannot know whether your application, compiled for .NET 2, also runs on .NET 4. It has the following knowledge:

  • The .NET Framework you compiled your application for
  • The processor bitness you compiled your application for (i.e. „x86“ or „Any CPU“)

If you want Windows 8 to be aware of anything other, you have to tell. To change the .NET Framework version, I created a configuration file with the same name as the application, in the same folder.

E.g. if your application is „my-app.exe“, your configuration file has to be „my-app.exe.config“. I use the following content of the file:

<?xml version="1.0"?>
<configuration>
    <startup useLegacyV2RuntimeActivationPolicy="true">
        <supportedRuntime version="v2.0.50727"/>
        <supportedRuntime version="v4.0"/>
    </startup>
</configuration>

I.e. you have to tell the supported runtime.

This is sometimes not enough, though. In my scenario, I had the application compiled as „Any CPU“ and configured for V2.0.50727 and v4.0 but still the above Windows message appeared.

My mistake was that on the test system, I only had a 32 bit V2.0.50727, not 64-bit, although the Windows itself was 64-bit. (I don’t know whether a 64-bit version of the .NET Framework exists at all)

So to solve this, I re-compiled the application to „x86“ and then, everything worked perfectly.

(Please correct me if any of my technical assumptions above are wrong)