2014

0

Und wieder beginnt ein neues Jahr. Viele nutzen den Jahreswechsel, um sich guten Vorsätzen hinzugeben und möchten nun ein paar Sachen ändern. Bei mir ist das eigentlich nicht so, denn für mich ist der Jahreswechsel genauso gut oder schlecht wie jeder andere Tag im Jahr. Ich brauche eigentlich keine Veränderungen, denn diese hatte ich in 2013 genug. Angefangen beim eigenen Haus, über einen neuen Arbeitgeber, weiter zur Hochzeit mit meiner Traumfrau weiter zur Glücksvollendung durch die Geburt meines Sohnes Max. Damit war 2013 vermutlich das aufregendste und spannendste Jahr meines Lebens (bis jetzt).
All das hat mein Leben komplett umgekrempelt und meine Prioritäten liegen nun komplett anders als beispielsweise vor einem Jahr. Nicht jede dieser Entwicklungen macht mich glücklich, allerdings wäre es auch keine Herausforderung, wenn alles so einfach zu meistern wäre.
Daher freue ich mich sehr auf das neue Jahr, es wird nicht langweilig und garantiert mit vielen Erlebnissen und Erfahrungen gespickt sein, welche ich mir überhaupt nicht vorstellen kann.
Daher wünsche ich euch auch ein spannendes Jahr und hoffe ihr freut euch auf eure Herausforderungen und nutzt 2014 vielleicht auch für die eine oder andere Veränderung. Es muss ja nicht gleich so radikal wie bei mir sein, aber es kann sich immer lohnen!

In diesem Sinne – Prost Neujahr!

Attrice TFS Sidekicks mit Visual Studio 2013 zum laufen bekommen

0

Aktuell nutze ich Visual Studio 2013 RC in Verbindung mit dem TFS 2012. Dazu habe ich in den letzten Jahren TFS Sidekicks von Attrice lieben gelernt.
Jedoch wollte das Programm auf meinem Rechner nicht starten, die Fehlermeldung lautete “Application cannot read registry.”. Auch die Tipps aus dem Netz, dass man als Administrator starten sollte halfen nicht. Offiziell wird nur Visual Studio 2012 unterstützt, daher vermutete ich hier das Problem.

Mithilfe von meinem neuen Lieblings-Tool dotPeek kam ich auch schnell dahinter, warum er scheiterte:

        RegistryKey registryKey = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\VisualStudio\\11.0");
        if (registryKey != null)
        {
          if (registryKey.OpenSubKey("TeamFoundation\\SourceControl") != null)
            str = registryKey.GetValue("InstallDir", (object) string.Empty) as string;
        }

Da ich Version 11 (VS 2012) nicht installiert hatte, konnte er den Pfad auch nicht finden. Die Ordner halt von Hand angelegt, den Schlüssel “InstallDir” aus der 12er-Version kopiert, schon funktionierte alles. Ihr müsst auch den TeamFoundation/SourceControl – Ordner anlegen, der InstallDir-Schlüssel kommt aber unterhalb von “11.0″.

Wichtig: solltet ihr ein 64-Bit System haben, findet ihr die Ordner unterhalb von “Wow6432Node” in der Registry!

C# – Die Falle mit dem this[]-Indexer und dem Enum

0

Vor ein paar Tagen kam ein Kollege auf mich zu und meinte, er habe beim Unit-Test schreiben einen Fehler in meinem Code entdeckt. Um mir den Fehler zu zeigen, hat er mir eigens einen Unit-Test geschrieben, welcher in etwa so aussah:

// MyTypeList : List<string>
MyTypeList list = new MyTypeList {"a", "b", "c"};

if(list.ElementAt(0) == null)
{
    throw new NullReferenceException();
}

if(list[0] == null)
{
    // Hier knallt er!
    throw new NullReferenceException();
}

if(list[1] == null)
{
    throw new NullReferenceException();
}

Er knallte immer, wenn man auf list[0] zugreifen wollte. Im Debugger zeigte er mir alle Elemente an, list[1] usw. funktionierte auch. Ich stand erstmal auf dem Schlau. Da ich den Fehler nicht sofort identifizieren konnte, baute ich mir eine kleine Testapplikation, um mit einem Minimum an Code das Problem zu identifizieren.

Letzendlich lag es an einem Indexer, welchen ich selber erstellt habe. Und zwar erwartete dieser als Übergabetyp ein Enum. Dieser überschreibt zwar nicht den von List<T> geerbten Indexer this[int index], jedoch scheint der Compiler hier leichte Probleme zu machen.

Schauen wir uns mal den kompilierten Code in dotPeek an.

      MyTypeList myTypeList1 = new MyTypeList();
      myTypeList1.Add("a");
      myTypeList1.Add("b");
      myTypeList1.Add("c");
      MyTypeList myTypeList2 = myTypeList1;
      if (Enumerable.ElementAt<string>((IEnumerable<string>) myTypeList2, 0) == null)
        throw new NullReferenceException();
      if (myTypeList2[MyEnum.Value1] == null)
        throw new NullReferenceException();

Den ersten Zugriff auf den numerischen Indexer wurde auf den Enum-Indexer geändert. Eine Erklärung dazu konnte ich nicht wirklich finden, bzw. keine die ich nachvollziehen konnte. Letztendlich hat es damit zu tun, dass ein Enum im Bauch eigentlich nur ein int-Typ ist. Dies passiert immer, wenn ich den numerischen Indexer mit 0 zugreife, wohlgemerkt, nur mit 0. Alle anderen Werte funktionieren problemlos.

Wie kann ich das Problem lösen / umgehen?

Dafür gibt es mehrere Wege.

Wird der Index-Wert woher in eine Variable (z.B. innerhalb einer for-Schleife) gesetzt, kann er den korrekten Indexer auflösen.

int index = 0;
if(list[index] == null) {
...
}

Ein Cast direkt auf int hingegen funktioniert nicht (er nutzt wieder den Enum-Indexer):

if(list[(int) 0] == null) {
..
}

Lustiger Weise lässt sich die Solution nicht mehr kompilieren, sobald ich einen weiteren Enum-Indexer hinzufüge:

The call is ambiguous between the following methods or properties: 'DumpSolution.MyTypeList.this[DumpSolution.MyEnum]' and 'DumpSolution.MyTypeList.this[DumpSolution.MyEnum2]'

Die simpelste Lösung ist, einfach den Standard-Indexer zu überschreiben:

    public class MyTypeList : List<string>
    {
        public string this[MyEnum myEnum]
        {
            get { return this.FirstOrDefault(item => item == Enum.GetName(myEnum.GetType(), myEnum)); }
        }

        public string this[MyEnum2 myEnum]
        {
            get { return this.FirstOrDefault(item => item == Enum.GetName(myEnum.GetType(), myEnum)); }
        }

        public new string this[int index]
        {
            get { return base[index]; }
            set { base[index] = value; }
        }
    }

Danach funktioniert der Code oben problemlos und korrekt. Ach ja, ein anpassen des Enums wie folgt brachte auch keinen Erfolg.

enum MyEnum {
   Value1 = 1,
   Value2 = 2
}

Ich kann mir vorstellen, dass es das Problem auch bei anderen Listen gibt, ich selbst habe es aber aktuell nur bei List<T> festgestellt. Im Netz findet man einige Einträge dazu, dazu auch eine Erklärung (welche mich aber nur bedingt zufrieden stellt)

Ich habe ein kleines Beispiel, welches ihr hier herunterladen könnt.

ASP.NET MVC & Bootstrap 3.0: Client Validierung

0

Wie man die Server-Validierung für Bootstrap 3 ein wenig aufhübschen kann, habe ich ja bereits erklärt.

Nun wollte ich auch schauen, dass die Client-Variante etwas _bootstrappiger_ aussieht und bin auf folgenden Beitrag gestoßen. Sehr gut finde ich, dass hier sämtliche Standardmethoden genutzt werden und das Rad nicht neu erfunden wird. Das ganze ist nur für Bootstrap 2, daher habe ich noch ein wenig dran geschraubt.

jQuery.validator.setDefaults({
    highlight: function (element, errorClass, validClass) {
        if (element.type === 'radio') {
            this.findByName(element.name).addClass(errorClass).removeClass(validClass);
        } else {
            $(element).addClass(errorClass).removeClass(validClass);
            $(element).closest('.form-group').removeClass('has-success').addClass('has-error');
        }
    },
    unhighlight: function (element, errorClass, validClass) {
        if (element.type === 'radio') {
            this.findByName(element.name).removeClass(errorClass).addClass(validClass);
        } else {
            $(element).removeClass(errorClass).addClass(validClass);
            $(element).closest('.form-group').removeClass('has-error').addClass('has-success');
        }
    }
});

$(function() {
    $('span.field-validation-valid, span.field-validation-error').each(function () {
        $(this).addClass('help-block');
    });
});

Im Prinzip habe ich wieder nur die CSS-Klassen angepasst und aus den Kommentaren die letzte Methode gezogen, um die Bootstrap-CSS-Klasse für die Hinweise anzufügen.
Damit das ganze auch funktioniert, muss darauf geachtet werden, dass der Code nach dem Laden der jQuery-Methoden erfolgt. Dazu habe ich in der BundleConfig.cs einfach das ScriptBundle angepasst:

bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
	"~/Scripts/jquery.unobtrusive*",
	"~/Scripts/jquery.validate*").Include("~/Scripts/wys.jquery.validate.bootstrap3.js"));

Ich habe die Datei bewusst mit einem Prefix versehen, denn nur mit dem Namen “jquery.validate.bootstrap3.js” wäre die Datei vor den anderen jquery.validate-Javascript-Dateien geladen worden und hätte zu einem Javascript-Fehler geführt.

ASP.NET MVC & Bootstrap 3.0: ValidationSummary stylen und Felder markieren

2

Update: Es gab noch einen kleinen Bug im Code, den ich noch mal eben behoben habe!


Ursprünglicher Beitrag:

Ich habe mal spaßeshalber ein MVC Projekt angefangen und dort ebenfalls das vor wenigen Tagen erschienene Bootstrap 3 implementiert. Nun wollte ich die bekannte ValidationSummary von MVC ein wenig mit Bootstrap stylen, jedoch scheint dies nicht so einfach.

Bei stackoverflow (wo auch sonst) bin ich auf eine eigene Implementierung der ValidationSummary-Ausgabe speziell für Bootstrap gestoßen. Diese ist jedoch nur für die Server-seitige Validierung geeignet, und da ich mich sowieso noch nicht entschlossen hatte, welche Validierung ich nutzen möchte, habe ich kurzerhand die Client-seitige deaktiviert.

Neben der bootstrap-gestylten ValidationSummary zeigte “PeteGo” noch, wie er seine Eingabefelder für den Benutzer fehlerhaft markiert. Dies ist mir allerdings zu aufwendig, da man natürlich mit jedem neuen Feld auch auch an die Prüfung denken muss. Ja ich weiß, ich kann ja Editor-Templates nutzen, will ich aber nicht ;)

Da ich die Erweiterung von PeteGo sowieso anfassen musste, sie war nur für Bootstrap 2 geeignet, habe ich die einzelnen Fehlermeldungen auch noch einem data--Attribute versehen und markiere per jQuery die Felder als fehlerhaft.

Die Erweiterung:

using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace System.Web.Mvc
{
    public static class HtmlExtensionMethods
    {
        /// <summary>
        /// Returns an error alert that lists each model error, much like the standard ValidationSummary only with
        /// altered markup for the Twitter bootstrap styles.
        /// </summary>
        public static MvcHtmlString ValidationSummaryBootstrap(this HtmlHelper helper, bool closeable)
        {
            string errorMessage = "Please fix the errors listed below and try again.";
            string validationTitle = "Validation error";

            ModelStateDictionary modelErrors = helper.ViewContext.ViewData.ModelState;

            if (!modelErrors.Any(error => error.Value.Errors.Any()))
            {
                return new MvcHtmlString(string.Empty);
            }

            StringBuilder divContent = new StringBuilder();
            
            if (closeable)
            {
                TagBuilder button = new TagBuilder("button");
                button.AddCssClass("close");
                button.MergeAttribute("type", "button");
                button.MergeAttribute("data-dismiss", "alert");
                button.SetInnerText("x");
                divContent.Append(button);
            }

            TagBuilder validationTitleTag = new TagBuilder("strong");
            validationTitleTag.SetInnerText(validationTitle);
            divContent.Append(validationTitleTag);

            divContent.Append(" ");
            divContent.Append(errorMessage);

            StringBuilder ulContent = new StringBuilder();
            
            foreach (KeyValuePair<string, ModelState> modelError in modelErrors)
            {
                string id = modelError.Key;
                foreach(ModelError item in modelError.Value.Errors)
                {
                    TagBuilder li = new TagBuilder("li");
                    li.SetInnerText(item.ErrorMessage);
                    li.MergeAttribute("data-bootstrap-has-error", id);
                    ulContent.Append(li);
                }
            }
            TagBuilder ul = new TagBuilder("ul") {InnerHtml = ulContent.ToString()};
            divContent.Append(ul);

            TagBuilder div = new TagBuilder("div");
            div.AddCssClass("alert");
            div.AddCssClass("alert-error");
            div.AddCssClass("alert-block");
            div.InnerHtml = divContent.ToString();

            return new MvcHtmlString(div.ToString());
        }

        /// <summary>
        /// Overload allowing no arguments.
        /// </summary>
        public static MvcHtmlString ValidationSummaryBootstrap(this HtmlHelper helper)
        {
            return ValidationSummaryBootstrap(helper, true);
        }
    }
}

Und nun die paar Zeilen Javascript, welche dann die Felder entsprechend markieren:

$(function () {
    $('div.alert').each(function () {
        $(this).find('li[data-bootstrap-has-error]').each(
            function () {
                var id = $(this).attr('data-bootstrap-has-error');
                $('input[id=' + id + '],select[id=' + id + ']').parent().addClass('has-error');
            });
    });
});

Vielleicht kann es ja jemand gebrauchen :)

Ach ja, falls jemand ein paar Pros / Contras bezüglich Client-/Server-Validierung hat, kann er diese gerne in den Kommentaren hinterlassen.

nach oben