 RSS-Feed
|
|
 RSS-Feed - Einträge
|
|
|
|
|
|
|
|
… oder; wenn du schnell sein willst musst du dich selbst darum kümmern. Im vorherigen Artikel wurden bisher nur Standardserialiserer und ihre Formate miteinander verglichen. Die Ergebnisse waren schon recht ordentlich und die Erkenntnisse aus den Vergleichen sehr lehrreich. Vor allem der Vergleich XML vs. JSON animierte mich dazu, die gewonnenen Erkenntnisse weiter zu führen. Meines Erachtens nach, resultiert der Vorteil von JSON gegenüber XML in der deutlich schlankeren Definition der erzeugten Daten. Während XML vom Klassennamen über die Namen der Enthaltenen Eigenschaften bis zum Typ des Inhalts von Kollektionen alles schreibt, beschränkt sich JSON auf den Namen der Eigenschaften und ihren Inhalt. Wenn ich diesen Gedankengang konsequent weiterführe, kann ich in einem angepassten Serialisierer selbst auf die Namen der Eigenschaften verzichten und statt dessen mittels Indexer auf den jeweiligen Wert des serialisierten Objekts zugreifen. Als nächste Konsequenz kann die komplette Typbehandlung beim De/Serialisieren entfallen, da der zu de/serialiserende Typ bekannt ist und nur dieser verwendet wird. Aber nun der Reihe nach. Der Anlass für die Vergleiche der verschiedenen Serialisierer, war eine Anforderung aus einem aktuellen Projekt über das ich in diesem Artikel bereits geschrieben haben: “Eine Kollektion eines bekannten Typs in einem geschlossenen System schnell und effizient zu serialisieren und deserialisieren.” Solange ich einen der Standardserialisierer verwende, werde ich immer die Typbehandlung des jeweiligen Serialisierer in Kauf nehmen müssen, da der Serialisierer mit beinahe jedem gängigen .NET Objekt umgehen muss. Warum also nicht die Serialisierung auf das notwendige beschränken? Das Notwendige ist per Definition alleinig der Inhalt der öffentlichen Eigenschaften. Wie könnte so eine serialisierte Zeichenfolge aussehen, die den Inhalt der öffentlichen Eigenschaften darstellt? Da eine Kollektion serialisiert werden soll, muss als erstes zwischen den einzelnen Einträgen der Auflistung getrennt werden. Als nächstes muss eine Trennung zwischen den einzelnen Eigenschaften eines Eintrags her. Am einfachsten erschien mir ein Muster nach folgendem Beispiel: [#|#|#…] Die eckigen Klammern umschließen eine Klasse, also einen Eintrag in der Auflistung. Mit einem oder-Operator (|) wird zwischen den einzelnen Eigenschaften abgegrenzt. Zur Darstellung eines Array oder einer anderen Auflistung habe ich mich für folgendes Muster entschieden: (n,n,n…) Kombiniert ergibt sich für die Klasse TestNode, aus diesem Artikel, das endgültige Muster: [#|#|#|(n,n,n…)] Zum erzeugen der Zeichenfolge, habe ich mich für eine Überladung der ToString-Methode entschlossen, die eine Zeichenfolge als Format erwartet. internal string ToString(string format)
{
return string.Format(
CultureInfo.InvariantCulture,
"[{0}|{1}|{2}|({3})]",
this.Name,
this.MessagingUrl,
this.Token,
string.Join(
",",
(this.Ranges
.Select(i => i.ToString(CultureInfo.InvariantCulture)))
.ToArray()));
}
Für diesen Benchmark wird der Parameter format noch nicht ausgewertet. Später könnten hier Werte wie etwa “C” für Custom oder “J” für JSON angegeben und entsprechend verarbeitet werden.
Die eigentliche Serialisierung in einem CustomFormatter ist eher trivial. Es wird lediglich die Auflistung durchlaufen, für jedes Element die überladene ToString(string)-Methode aufgerufen und die zurückgegebene Zeichenfolge in einen StreamWriter geschrieben.
public void Serialize(HttpResponse response, List<TestNode> data)
{
response.ContentType = "application/json";
using (var writer = new StreamWriter(response.OutputStream))
{
foreach (var entry in data)
{
writer.Write(entry.ToString(null));
}
}
}
Die Deserialisierung ist ähnlich einfach. Der empfangene Stream wird in einen StreamReader gelesen und die enthaltene Zeichenfolge, dem vorher beschriebenen Muster entsprechend, aufgeteilt und die erzeugten Fragmente jeweils einer neuen Instanz der Klasse TestNode bzw. den Eigenschaften zugewiesen.
public List<TestNode> Deserialize(Stream responseStream)
{
var list = new List<TestNode>();
var rawSeparator = new char[] { '[', ']' };
var propertySeparator = new char[] { '|' };
var rangeSeparator = new char[] { '(', ')', ',' };
using (var reader = new StreamReader(responseStream))
{
var rawData = reader.ReadToEnd()
.Split(rawSeparator, StringSplitOptions.RemoveEmptyEntries);
foreach (var entry in rawData)
{
var properties = entry.Split(
propertySeparator,
StringSplitOptions.RemoveEmptyEntries);
if (properties.Length < 1)
{
continue;
}
var ranges = properties[3]
.Split(rangeSeparator, StringSplitOptions.RemoveEmptyEntries)
.Select(s => int.Parse(s, CultureInfo.InvariantCulture));
var node = new TestNode
{
Name = properties[0],
MessagingUrl = properties[1],
Token = properties[2],
Ranges = new List<int>(ranges)
};
list.Add(node);
}
}
return list;
}
Die Erzeugung der temporären Variablen ranges, ist nur der Übersicht halber im Code enthalten. Das Aufteilen und parsen der Zeichenfolge kann auch im Konstruktor von List<T> für die Eigenschaft Ranges erfolgen.
Nach dem die Methodik stand wollte ich natürlich wissen, ob auch der erwartete Effekt eintrat bzw. eine verbesserte Leitung gegenüber den bisher verwendeten Serialisierern messbar ist.
Der Benchmark wurde um die Messung der Deserialisierung erweitert. So kann ein besseres Gesamtbild des jeweiligen Serialisierers wiedergegeben werden.
Die Werte der Spalten Response, Deseria. und Complete sind jeweils in Millisekunden dargestellt. Count gibt die Anzahl der Elemente in der übertragenen Auflistung wieder und Transfered die Anzahl der übertragenen Bytes.
Ich habe kurzerhand die beschriebene Methodik als CustomFormatter mit dem Kürzel custom dem Benchmark hinzugefügt und dieses laufen lassen.
Was soll ich sagen; die Zahlen sprechen für sich.
Format
Count
Comp
Response
Deseria.
Complete
Transfered
bin
1
1
gzip
1
1
1
1
968
559
xml
1
1
gzip
1
 
1
423
364
jsondata
1
1
gzip
1
 
1
219
269
jsonweb
1
1
gzip
2
 
2
148
246
jsonnet
1
1
gzip
2
5
 
2
5
148
246
custom
1
1
gzip
1
 
1
101
209
Format
Count
Comp
Response
Deseria.
Complete
Transfered
bin
10
10
gzip
1
1
 
1
1
2.581
1.372
xml
10
10
gzip
4
1
1
1
3.494
947
jsondata
10
10
gzip
1
1
 
1
2.300
813
jsonweb
10
10
gzip
1
1
 
2
1.590
773
jsonnet
10
10
gzip
2
1
1
3
1
1.590
773
custom
10
10
gzip
2
 
2
1.129
723
Format
Count
Comp
Response
Deseria.
Complete
Transfered
bin
100
100
gzip
3
2
29
2
32
4
18.611
8.831
xml
100
100
gzip
1
2
1
1
2
3
33.649
6.312
jsondata
100
100
gzip
1
1
2
3
3
4
23.005
5.713
jsonweb
100
100
gzip
1
2
6
6
7
8
15.905
5.397
jsonnet
100
100
gzip
1
1
2
2
3
3
15.905
5.397
custom
100
100
gzip
1
1
1
1
2
11.304
5.125
Format
Count
Comp
Response
Deseria.
Complete
Transfered
bin
1.000
1.000
gzip
11
20
42
26
53
46
177.927
80.243
xml
1.000
1.000
gzip
5
15
8
10
13
25
329.784
59.224
jsondata
1.000
1.000
gzip
6
13
23
26
29
39
229.068
53.906
jsonweb
1.000
1.000
gzip
11
18
61
65
72
83
158.068
50.626
jsonnet
1.000
1.000
gzip
5
11
19
21
24
32
158.068
50.626
custom
1.000
1.000
gzip
2
8
7
5
9
13
112.067
47.624
Format
Count
Comp
Response
Deseria.
Complete
Transfered
bin
10.000
10.000
gzip
125
210
528
215
653
425
1.770.091
793.997
xml
10.000
10.000
gzip
47
148
114
114
161
262
3.284.950
587.571
jsondata
10.000
10.000
gzip
53
143
377
267
430
410
2.287.996
535.119
jsonweb
10.000
10.000
gzip
113
177
651
711
764
888
1.577.996
502.584
jsonnet
10.000
10.000
gzip
47
112
211
254
258
366
1.577.996
502.584
custom
10.000
10.000
gzip
27
77
48
72
75
149
1.117.995
472.300
Format
Count
Comp
Response
Deseria.
Complete
Transfered
bin
100.000
100.000
gzip
1.389
2.255
11.064
8.115
12.453
10.370
17.701.551
7.887.661
xml
100.000
100.000
gzip
457
1.424
916
1.161
1.373
2.585
32.890.892
5.876.977
jsondata
100.000
100.000
gzip
523
1.300
2.594
2.772
3.117
4.072
22.887.368
5.349.086
jsonweb
100.000
100.000
gzip
1.116
1.781
6.676
6.692
7.792
8.743
15.787.368
5.023.391
jsonnet
100.000
100.000
gzip
462
1.163
2.124
2.261
2.568
3.424
15.787.368
5.023.391
custom
100.000
100.000
gzip
221
842
518
761
739
1.603
11.187.367
4.721.865
Wie obige Zahlen zeigen, schlägt die kleinere Datenmenge sofort zu Buche. Das Fehlen jeglicher Typbehandlung ist in der Responsetime und der sehr kurzen Zeit für die Deserialisierung ebenfalls deutlich zu sehen.
Die vorgestellte Methodik ist beileibe noch kein Produktivcode. Sie soll lediglich zeigen, dass auch einfache Ansätze zu guten Ergebnissen führen können.
Der Beispielcode ist als Projekt SerializeTesting bei Bitbucket gehostet. Erweiterungen um eigene Ideen sind ausdrücklich erwünscht.
Fazit:
Es muss nicht immer ein Serialisierer mit der kompletten Funktionalität verwendet werden.
Wie bereits der Vergleich der gängigen Serialisierer untereinander gezeigt hat, kostet Overhead einfach Zeit. Ob nun das erzeugte Format oder die interne Verarbeitung der zu de/serialisierenden Objekte spielt dabei keine Rolle. Wenn konsequent auf jeglichen Overhead verzichtet wird, ist das daraus resultierende Ergebnis immer schlank und effizient. Wie weit die Entschlackung getrieben wird, ist jedem selbst überlassen.
Technorati-Tags: binary | xml | json | serialization | c# | asp.net
|
|
03.01.2012 - 14:22:30
|
|
http://feedproxy.google.com/~r/klaus_b/~3/MSX0O23B1_Y/post.aspx
|
|
|
|
|
|
|
|
|
In einem Kommentar zum vorherigen Artikel hatte Albert Weinert angeregt, auch JSON in den Vergleich mit einzubeziehen. Da JSON, JavaScript und Konsorten wirklich nicht meine Welt sind, hat kurzer Hand Albert den Vergleich um drei verschiedene JSON Serialisierer ergänzt. Wer “den Albert” kennt, weis dass er keine halben Sachen macht. Ich schickte im ein kleines Testprojekt und erhielt ein komplettes Benchmark Programm zurück. Im Verlauf der Kommunikation brachte mir Albert auch JSON etwas näher und korrigierte damit meinen Blickwinkel. JSON bedeutet zwar namentlich JavaScript Object Notation und in der Wikipedia steht in der Erläuterung dazu: Jedes gültige JSON-Dokument soll ein gültiges JavaScript sein … Dies bedeutet aber nicht unbedingt, das JSON nur für JavaScript verwendet werden kann. Vielmehr stellt das JSON Format ein schlankes Textabbild des serialisierten Objekts ohne großen Overhead dar. Die gängigen JSON Serialisierer bieten alle sehr einfach zu handhabende Methoden zum Serialisieren und Deserialisieren von Objekten. Zum direkten Vergleich der Effektivität verschiedener Methoden und Formate der Serialisierung, sind jetzt insgesamt fünf Serialisierer am Start. Die im Benchmark verwendeten Formatkürzel sowie die entsprechenden Produkte sind wie folgt aufgeschlüsselt. bin:           BinaryFormatter xml:          XmlSerializer jsondata:  DataContractJsonSerializer jsonweb:  JavaScriptSerializer jsonnet:    Json.NET von James Newton-King. Um einheitliche Testbedingungen zu schaffen, wird für jeden Durchgang die benötigte Anzahl an Datensätzen einmal erzeugt und für alle Serialisierer verwendet. Jeder Serialisierer wird zweimal, einmal mit und einmal ohne GZip Komprimierung, verwendet und die benötigte Zeit in Millisekunden sowie die übertragene Datenmenge in Bytes erfasst. Es werden fünf Durchgänge mit 10, 100, 1.000, 10.000 und 100.000 Elementen ausgewertet. Format Count Compression Milliseconds Bytes Transfered bin 10 10 gzip 1 2 2.609 1.390 xml 10 10 gzip 1 2 3.638 972 jsondata 10 10 gzip 2 3 2.318 844 jsonweb 10 10 gzip 2 1.608 801 jsonnet 10 10 gzip 1 1.608 801 Format Count Compression Milliseconds Bytes Transfered bin 100 100 gzip 2 3 18.559 8.832 xml 100 100 gzip 1 2 33.344 6.243 jsondata 100 100 gzip 1 3 22.934 5.717 jsonweb 100 100 gzip 8 5 15.834 5.382 jsonnet 100 100 gzip 1 2 15.834 5.382 Format Count Compression Milliseconds Bytes Transfered bin 1.000 1.000 gzip 16 21 177.939 80.238 xml 1.000 1.000 gzip 9 22 329.675 58.287 jsondata 1.000 1.000 gzip 9 14 228.905 53.838 jsonweb 1.000 1.000 gzip 17 22 157.905 50.209 jsonnet 1.000 1.000 gzip 7 43 157.905 50.209 Format Count Compression Milliseconds Bytes Transfered bin 10.000 10.000 gzip 125 214 1.772.619 795.178 xml 10.000 10.000 gzip 69 185 3.298.716 580.353 jsondata 10.000 10.000 gzip 62 130 2.290.386 536.307 jsonweb 10.000 10.000 gzip 116 183 1.580.386 500.012 jsonnet 10.000 10.000 gzip 51 116 1.580.386 500.012 Format Count Compression Milliseconds Bytes Transfered bin 100.000 100.000 gzip 1.441 2.248 17.700.411 7.883.815 xml 100.000 100.000 gzip 883 1.584 32.885.009 5.782.222 jsondata 100.000 100.000 gzip 582 1.328 22.886.615 5.349.126 jsonweb 100.000 100.000 gzip 1.151 1.833 15.786.615 4.987.282 jsonnet 100.000 100.000 gzip 570 1.156 15.786.615 4.987.282 Wie in obigen Tabellen ersichtlich, setzen sich die beiden JSON Serialisierer JavaScriptSerializer und Json.NET in der Datengröße deutlich ab. Interessant auch die Beobachtung, dass beide Serialisierer die exakt gleiche Datenmenge übertragen. Json.NET scheint, im Vergleich zum JavaScriptSerializer, effizientere Methoden zur Serialisierung zu verwenden, da er die Daten zum Teil doppelt so schnell liefert. Interessierte können sich das Projekt gerne für eigene Tests herunterladen. SerializeTesting.zip Fazit: Wer, wie ich, bis dato JSON bei der Serialisierung immer übersehen hat, sollte dies schleunigst ändern. Man sollte einfach das JS für JavaScript in JSON nicht überbewerten. Beherzigt man diesen Rat, hat man ein schlankes und schnelles Format zur Serialisierung der meisten .NET Objekte. An dieser Stelle möchte ich mich noch einmal besonders bei Alber Weinert für die tatkräftige Unterstützung und das zurechtrücken meiner Sichtweise bedanken. Technorati-Tags: binary | xml | json | serialization | c# | asp.net
|
|
30.12.2011 - 15:21:03
|
|
http://feedproxy.google.com/~r/klaus_b/~3/nqJ3vCbal58/post.aspx
|
|
|
|
|
|
|
|
|
...aber in welchem Format? Mein erster Kandidat - und heimlicher Favorit - war hier die binäre Serialisierung mit dem BinaryFormatter. Für einen Vergleich habe ich den XmlSerializer herangezogen, wobei ich Anfangs die binäre Serialisierung als klaren Favoriten ansah. Bei lokaler Verwendung, zum speichern von Kollektionen wie etwa List<T> oder Collection<T> in einer Datei, zeigte der BinaryFormatter immer sehr gute Ergebnisse. Die hohe Datendichte bei vergleichsweise kleiner Dateigröße ist einer seiner großen Vorteile gegenüber der Serialisierung in eine Datei im XML-Format. Warum sollte dieser Vorteil nicht auch beim Transport über HTTP zum tragen kommen? Nun aber der Reihe nach. In einem aktuellem Projekt ist eine der Anforderungen, eine Auflistung von Informationen über einzelne Knoten eines Netzwerks bereitzustellen. Diese Auflistung ändert sich mit der Verfügbarkeit einzelner Knoten und soll an einem zentralen Ort, für alle Knoten zugänglich, bereitgestellt werden. Die Funktionalität entspricht in etwa einem Boostrapping Node, oder Rendezvous Host, eines Peer-to-Peer Netzes. Wie an der Aufgabenstellung bereits ersichtlich, geht es hier um die Kommunikation zwischen Maschinen. HTTP wurde als Transportprotokoll gewählt, um Probleme mit der Firewall der beteiligten Maschinen zu vermeiden. Die Visualisierung der Daten in irgendwelchen UIs spielt keine Rolle und ist auch nicht vorgesehen. Auf die Befindlichkeiten von Benutzern braucht nicht eingegangen zu werden. Insofern kann auf die Notwendigkeit der Kompatibilität zu Formaten wie JSON udgl. verzichtet werden. Für die Testumgebung habe ich eine vereinfachte Klasse, siehe obige Grafik, verwendet, die sich auf wenige öffentliche Eigenschaften und die überschriebene Methode ToString beschränkt. Für eine Simulation und Begutachtung der Bedingungen ist dies vorerst vollkommen ausreichend. Zum bereitstellen der Kollektion wird ein IHttpHandler in einer ASP.NET-Anwendung verwendet, der die Auflistung in der benötigten Größe dynamisch erzeugt. Die Namen der Knoten werden mit dem Präfix Node_ durchnummeriert. Die MessagingUrl wird aus dem Namen erzeugt und mit einem fiktiven Handler ergänzt. Der Token wird mit der Zeichenfolgendarstellung einer Guid simuliert und die Liste der Eigenschaft Ranges wird in zufälliger Länge mit zufälligem Inhalt erzeugt. Der Handler verarbeitet die beiden Parameter format und size. Mit format wird entweder binäre oder XML serialisiert. Size gibt die Anzahl der Elemente in der Auflistung an. public void ProcessRequest(HttpContext context)
{
if (context == null)
{
return;
}
var format = context.Request.QueryString["format"];
var size = int.Parse(
context.Request.QueryString["size"],
CultureInfo.InvariantCulture);
this.data = this.data ?? SerializationHandler.CreateData(size);
context.Response.ContentEncoding = Encoding.UTF8;
switch (format)
{
case "bin":
context.Response.ContentType = "application/octet-stream";
var binFormatter = new BinaryFormatter();
binFormatter.Serialize(context.Response.OutputStream, this.data);
break;
case "xml":
default:
context.Response.ContentType = "application/xml";
var xmlFormatter = new XmlSerializer(typeof(List<TestNode>));
xmlFormatter.Serialize(context.Response.OutputStream, this.data);
break;
}
}
private static List<TestNode> CreateData(int entriesCount)
{
var list = new List<TestNode>(entriesCount);
var i = 0;
var lengthRandom = new Random();
var contenRandom = new Random();
while (i < entriesCount)
{
var node = new TestNode();
node.Name = "Node_" + i.ToString("D6", CultureInfo.InvariantCulture);
node.MessagingUrl = "http://node." + node.Name + ".net/messaging.axd";
node.Token = Guid.NewGuid().ToString();
var length = lengthRandom.Next(1, 10);
node.Ranges = new List<int>(length);
var j = 0;
while (j < length)
{
node.Ranges.Add(contenRandom.Next(1, 254));
j++;
}
list.Add(node);
i++;
}
return list;
}
Wie bereits weiter oben angesprochen, ist lediglich die Größe der übertragenen Daten von vorrangigem Interesse, da die Auflistung häufigen Änderungen unterworfen ist und oft abgerufen werden kann. Zur Bestimmung der Größe der übertragenen Daten bei der unterschiedlichen Anzahl der Einträge, verwende ich den Composer von Fiddler, der sehr einfach die Änderung des Query-String und der Request-Header zulässt.
Für einen ersten Vergleich habe ich eine Auflistung jeweils binär und als XML serialisiert mit 100, 1.000, 10.000 und 100.000 Einträgen verglichen. (Siehe folgende Grafik)
Wie erwartet, ist die übertragene Datenmenge der XML-serialisierten Auflistungen beinahe doppelt so groß wie die binär serialisierten. Auch interessant zu beobachten war die Tatsache, dass die XML-Serialisierung schneller als die binär-Serialisierung wurde, je weiter die Anzahl der Elemente stieg. Bei 1.000 Einträgen benötigte binär 92ms und XML 69ms für den Transport.
Bei 10.000 Einträgen 298ms zu 92ms und bei 100.000 Einträgen immerhin schon 1800ms zu 820ms.
Als nächste Maßnahme zog ich die Kompression der übertragenen Daten via GZip in Betracht. Die Kompression wird nicht vom IIS , sondern von einem eigenem IHttpModule vorgenommen. Damit bleibt mehr Spielraum für weitere Optimierungen.
Mit der Kompression sollte XML einen Vorsprung erfahren, da sich erfahrungsgemäß Textinhalte besser komprimieren lassen als binäre Daten.
Wie obige Grafik zeigt, lassen sich auch die binären Daten noch auf über die Hälfte ihrer ursprünglichen Größe komprimieren. XML zieht, in der reinen Größe der übertragenen Daten, mit einem Kompressionsfaktor von rund 5,8 deutlich vorbei.
Da die Kompression Zeit kostet, sind die Transportzeiten beinahe gleich. Erst bei 100.000 Einträgen macht XML mit rund 1,8s gegenüber 2,5s etwas Boden gut.
Als vorläufig letzte Maßnahme, kommt noch ein HttpResponse.Filter zum Einsatz, der überflüssige Zeilenumbrüche und Leerzeichen aus dem generierten XML entfernt. Das Prinzip eines solchen Filters, habe ich schon einmal in einem früheren Artikel beschrieben.
Der Filter brachte noch einmal eine Ersparnis von rund 3.5% der übertragenen Datenmenge.
Allerdings auf Kosten der Zeit, denn das Filtern ist relativ zeitaufwendig. Bei 100.000 Datensätzen verlängerte sich die Transportzeit von 1,8s auf stolze 3,1s und somit langsamer als die binäre Serialisierung der gleichen Datensätze.
Eine weitere Möglichkeit die Datenmenge zu reduzieren, besteht in der Minimierung des ausgegeben XML. Dabei werden die Namen der Tags durch entsprechend kurze Zeichenfolgen ersetzt. Z.B.: <MessagingUrl> durch <MU> oder <Token> durch <T>. Mit den Daten aus dem obigen Beispiel, können so noch einmal rund 8% eingespart werden.
Es darf dann nicht vergessen werden, beim Deserialisieren des XML, die Minimierung wieder rückgängig zu machen.
Fazit:
Die Einstellung zu meinem anfänglichem Favoriten hat sich geändert. Wenn eine überschaubare Menge an Daten einfach und effizient serialisiert werden sollen, ist der BinaryFormatter nach wie vor die erste Wahl.
Soll die Serialisierung maximal optimiert werden, bietet der XmlSerializer deutlich mehr Möglichkeiten ohne einen komplett eigenen Serializer entwickeln zu müssen.
Technorati-Tags: serialization | xml | binary | asp.net | c#
|
|
27.12.2011 - 12:15:10
|
|
http://feedproxy.google.com/~r/klaus_b/~3/eayEb7JtMfY/post.aspx
|
|
|
|
|
|
|
|
|
Wie ich bereits in den beiden früheren Artikeln BlogEngine.NET und 404 Fehler sowie Eine bessere 404 Fehlerbehandlung in Blogengine.NET beschrieben habe, gibt die 404-Fehlerbehandlung von BlogEngine.NET immer wieder Anlass zur Kritik. Auch in der aktuellen Version 2.0 hat sich daran kaum etwas geändert. Zwar wird per Standard in der web.config richtigerweise die Methode ResponseRewrite zur Weiterleitung auf die Fehlerseite angegeben, aber damit erschöpft es sich auch schon in einer sauberen Behandlung von 404-Fehlern. Als gröbsten Fehler sehe ich hier den zurückgelieferten Statuscode 200 OK an, wenn die Fehlerseite error.aspx angezeigt wird. Für einen Benutzer mag das keine Rolle spielen, aber die Bots von Suchmaschinen sehen das ganz anders. Da wird die angezeigte Fehlerseite statt der falsch angeforderten Seite in den Index eingetragen. Für die Suchmaschinen ist demzufolge die eigentlich nicht vorhandene Seite sehr wohl vorhanden. Dem kann leicht abgeholfen werden, in dem mit der Fehlerseite error404.aspx auch der Statuscode 404 ausgeliefert wird. Dazu einfach, am Ende der Methode Page_Load, den Statuscode 404 an die Eigenschaft StatusCode des Response-Objekts übergeben. Page.Title += Server.HtmlEncode(" - " + "Seite nicht gefunden");
Response.StatusCode = (int)HttpStatusCode.NotFound;
Diese kleine Maßnahme zeigt sofort den gewünschten Effekt. Ohne 302-Weiterleitung wird sofort der Status 404 zurückgegeben.
Eine weitere, sehr unschöne, Tatsache stellt der Versuch der Verwendung des Wertes aspxerrorpath aus dem QueryString dar. Dieser Wert wird bei der Verwendung der ResponseWrite Methode von ASP.NET nicht mehr erzeugt. Somit wurde ein sehr schönes Feature der Seite error404.aspx, die Suche nach ähnlichen Seiten wie der angegebenen, komplett ausgehebelt.
Nun, mit einem kleinen Eingriff in die Fehlerseite error404.aspx lässt sich dieser Umstand wieder beheben wie folgender Screenshot zeigt. Dabei wird bei der, aus obigem Screenshot, falsch angeforderten Seite BlogEngine.NET Update auf Version 2.0.0.69 die richtige Seite BlogEngine.NET Update auf Version 2.0.0.66 vorgeschlagen.
Da, wie bereits oben angesprochen, der Eintrag aspxerrorpath im QueryString nicht mehr zur Verfügung steht, muss der Pfad der falsch angeforderten Seite auf andere Weise ermittelt werden. Am besten eignet sich hierfür die Eigenschaft Message der zuletzt geworfenen Ausnahme. Da ASP.NET auf die Fehlerseite eines 404-Fehlers umgeleitet hat, sollte auch ein solcher von der Methode GetLastError des Server-Objekts der aktuellen Seite zurückgegeben werden. Jetzt muss nur noch der Pfad der nicht gefundenen Seite aus der Fehlernachricht extrahiert werden und der Ablauf kann erfolgen wie bisher. Um den Pfad der falsch angeforderten Seite für die gesamte Klasse zur Verfügung zu stellen, speichere ich diesen in einer klassenweiten Variablen.
Um die Änderungen ersichtlicher zu gestalten, habe ich den ursprünglichen Code nur auskommentiert. Als erstes wird die Methode Page_Load angepasst:
/// <summary>
/// Hält den Pfad der falsch angeforderten Seite
/// </summary>
/// <remarks>n/a</remarks>
private string errorPath;
protected void Page_Load(object sender, EventArgs e)
{
this.errorPath = this.Request.QueryString["aspxerrorpath"];
var ex = this.Server.GetLastError();
if (string.IsNullOrEmpty(this.errorPath) && ex != null)
{
var index = ex.Message.IndexOf("/post/");
var temp = ex.Message.Substring(index);
this.errorPath = temp.Substring(0, temp.IndexOf(" "));
}
//if (Request.QueryString["aspxerrorpath"] != null
// && Request.QueryString["aspxerrorpath"].Contains("/post/"))
if (!string.IsNullOrEmpty(this.errorPath) && this.errorPath.Contains("/post/"))
{
DirectHitSearch();
divDirectHit.Visible = true;
}
else if (Request.UrlReferrer == null)
{
divDirectHit.Visible = true;
}
else if (Request.UrlReferrer.Host == Request.Url.Host)
{
divInternalReferrer.Visible = true;
}
else if (GetSearchKey() != string.Empty)
{
SearchTerm = GetSearchTerm(GetSearchKey());
BindSearchResult();
divSearchEngine.Visible = true;
}
else if (Request.UrlReferrer != null)
{
divExternalReferrer.Visible = true;
}
Page.Title += Server.HtmlEncode(" - " + "Seite nicht gefunden");
Response.StatusCode = (int)HttpStatusCode.NotFound;
}
Damit mögliche passende Seiten vorgeschlagen werden können, muss auch die Methode DirectHitSearch eine kleine Änderung erfahren:
private void DirectHitSearch()
{
string from = this.errorPath; //Request.QueryString["aspxerrorpath"];
int index = from.LastIndexOf("/") + 1;
string title = from.Substring(index)
.Replace(".aspx", string.Empty).Replace("-", " ");
List<IPublishable> items = Search.Hits(title, false);
if (items.Count > 0)
{
LiteralControl result = new LiteralControl(
string.Format(
"<li><a href=\"{0}\">{1}</a></li>",
items[0].RelativeLink.ToString(),
items[0].Title));
phSearchResult.Controls.Add(result);
}
}
Fazit:
Dies war bestimmt nicht die letzte Anpassung von Fehlerseiten unter BlogEngine.NET.
Für Heute oder Morgen war ursprünglich der Release Kandidat für BlogEngine.NET 2.5 angekündigt. Da im Moment das Repository auf CodePlex beschädigt ist, wird sich die Veröffentlichung ein wenig verzögern.
BlogEngine.NET hat sich seit der Veröffentlichung der Version 2.0 sehr zum Positiven verändert. Es wurde und wird viel für eine breitere Akzeptanz getan. Doch solange sich solche “Feature” im Code verbergen, wird sich an der breiten Meinung: “nur für .NET Bastler” nicht viel ändern.
Technorati-Tags: blogengine.net | asp.net | 404 error | seo
|
|
19.06.2011 - 16:59:36
|
|
http://feedproxy.google.com/~r/klaus_b/~3/KEDq3aR90aM/post.aspx
|
|
|
|
|
|
|
|
|
Die unter BlogEngine.NET verwendeten Extensions, zum Erweitern des Funktionsumfangs, bieten keine der üblichen Möglichkeiten um die Textausgabe dieser Erweiterungen zu lokalisieren. Diese Erweiterungen sind im Normalfall Klassen, die mit dem Extension-Attribut versehen und im App_Code-Ordner einer BlogEngine Installation im Unterordner Extensions abgelegt werden. Eine klassenbezogene Lokalisierung ist im .NET Framework nicht vorgesehen. BlogEngine bietet lediglich die Methode Translate der Utils-Klasse um Zeichenfolgen mit Hilfe der globalen Ressourcen zu übersetzen. Um diese Option zu nutzen, müssen alle zu verwendenden Zeichenfolgen in den globalen Ressourcen der Webanwendung vorhanden sein. Also auch kein gangbarer Weg, da der Aufwand bei Aktualisierungen der globalen Ressourcen einfach zu groß währe. Ich entschied mich für die Verwendung von statischen Klassen als geschachtelte Typen innerhalb meiner Erweiterungen. Eine anwendungsweite Lösung erschient mir nicht praktikabel, da gerade Erweiterungen oft einzeln verteilt und verwendet werden. Wenn jetzt noch jedes mal eine zusätzliche Assembly mit verteilt werden soll, kann die einfache Handhabung der Erweiterungen schnell unübersichtlich und unnötig kompliziert werden. In meinem Ansatz mache ich mir die Tatsache zu nutze, dass in den Einstellungen des Blog jederzeit die verwendete Spracheinstellung abrufbar ist. Von der ausgegebene Einstellung, z.B.: de-DE, verwende ich lediglich das Sprachschema. Auf die regionalen Unterschiede zwischen de-DE und de-AT habe ich bewusst verzichtet, da ich nur Zeichenfolgen darstellen will. So kann in einem switch/case-Block, entsprechend der eingestellten Sprache, die jeweils zugehörige Zeichenfolge ermittelt werden. Da die geschachtelte Klasse als statisch deklariert ist, können die enthaltenen Eigenschaften, wie von der Resource-Klasse gewohnt, einfach direkt verwendet werden. /// <summary>
/// Konvertiert die Style-Attribute von Html-Images in
/// eine entsprechende CSS-Klasse.
/// </summary>
/// <remarks>n/a</remarks>
[Extension(
"Converts the style attributes of Html-Images to the corresponding CSS class.",
"1.5.0.0",
"<a href=\"http://blog.klaus-b.net/\">klaus_b</a>")]
public class ConvertStyleToCss
{
// Hier andere Member der Klasse
#region Methods
private static void InitialiceSettings()
{
var settings = new ExtensionSettings("ConvertStyleToCss") { IsScalar = true };
settings.AddParameter(
"leftImg",
LocalResources.LeftImg,
50,
true,
false,
ParameterType.String);
settings.AddValue("leftImg", "leftImg");
settings.AddParameter(
"rightImg",
LocalResources.RightImg,
50,
true,
false,
ParameterType.String);
settings.AddValue("rightImg", "rightImg");
settings.AddParameter(
"centerImg",
LocalResources.CenterImg,
50,
true,
false,
ParameterType.String);
settings.AddValue("centerImg", "centerImg");
settings.AddParameter(
"cleanWlwDivs",
LocalResources.CleanWlwDivs,
4,
false,
false,
ParameterType.Boolean);
settings.AddValue("cleanWlwDivs", false);
// TODO: Hilfe erzeugen und einfügen
ConvertStyleToCss.setting = ExtensionManager.InitSettings(
"ConvertStyleToCss",
settings);
}
#endregion Methods
#region Nested Types
/// <summary>
/// Stellt lokalisierte Zeichenfolgen als Ressourcen bereit.
/// </summary>
/// <remarks>Wird nur von der übergeordneten Klasse verwendet.</remarks>
private static class LocalResources
{
#region Fields
/// <summary>
/// Hält die eingestellte Kultur des Blogs.
/// </summary>
/// <remarks>n/a</remarks>
private static readonly string blogCulture = BlogSettings.Instance.Culture;
/// <summary>
/// Hält die zu verwendende Sprache des Blogs.
/// </summary>
/// <remarks>n/a</remarks>
private static readonly string culture = blogCulture.Substring(0, 2);
/// <summary>
/// Hält den zu verwendenden Hilfetext.
/// </summary>
/// <remarks>n/a</remarks>
private static string helpText;
#endregion Fields
#region Properties
internal static string CenterImg
{
get
{
switch (culture)
{
case "de":
return "Die CSS-Klasse für zentrierte Bilder:";
default:
return "CSS class for centered images:";
}
}
}
internal static string CleanWlwDivs
{
get
{
switch (culture)
{
case "de":
return "WLW DIVs bereinigen:";
default:
return "Clean WLW DIVS:";
}
}
}
internal static string HelpText
{
get
{
if (string.IsNullOrEmpty(helpText))
{
switch (culture)
{
case "de":
helpText = CreateGermanHelpText();
break;
default:
helpText = CreateEnglishHelpText();
break;
}
}
return helpText;
}
}
internal static string LeftImg
{
get
{
switch (culture)
{
case "de":
return "Die CSS-Klasse für links ausgerichtete Bilder:";
default:
return "CSS class for left aligned images:";
}
}
}
internal static string RightImg
{
get
{
switch (culture)
{
case "de":
return "Die CSS-Klasse für rechts ausgerichtete Bilder:";
default:
return "CSS class for right aligned images:";
}
}
}
#endregion Properties
#region Methods
private static string CreateEnglishHelpText()
{
var sb = new StringBuilder();
// TODO: create english help text
return sb.ToString();
}
private static string CreateGermanHelpText()
{
var sb = new StringBuilder();
// TODO: deutschen Hilfetext erzeugen
return sb.ToString();
}
#endregion Methods
}
#endregion Nested Types
}
Im Moment habe ich die switch/case-Blöcke so gestaltet, dass bei allen anderen Sprachen außer Deutsch, ein englischer Text ausgegeben wird. Da ich die Konfigurationsoberfläche meiner Erweiterungen gerne in der Sprache meines Blog darstelle, denke ich eine gangbare Lösung für Anwender meiner Erweiterungen gefunden zu haben, die andere Sprachen bevorzugen.
Fazit:
Die Lokalisierung von Oberflächen, Meldungen und anderen Textausgaben sollte eigentlich schon lange den Kinderschuhen entwachsen sein. Leider muss ich immer wieder feststellen, dass dem nicht so ist. Entweder liegt dieser Umstand an den jeweiligen Entwicklern die es nicht für nötig erachten ihre Anwendungen zu lokalisieren, oder aber die entsprechenden Technologien erscheinen zu kompliziert um in jeder Anwendung einfach verwendet werden zu können.
Technorati-Tags: blogengine.net | asp.net | c# | localization
|
|
15.06.2011 - 18:09:11
|
|
http://feedproxy.google.com/~r/klaus_b/~3/wFaHoT5NuuU/post.aspx
|
|
|
|
|
|
|
|
|
Am 07 Juni 2011 ging das erste deutschsprachige BlogEngine.NET Community Portal online. Betreiber und Verantwortlicher der Webseite ist Hans-Peter Schelian. Als Moderatoren stehen ihm Roland Schumacher aka GENiALi und meine Wenigkeit zur Seite. Die Idee, oder besser der Entschluss das Portal zu eröffnen, entstand in einem Twitter-Gespräch zwischen Hans-Peter und mir, das in etwa so ablief: SchelianHP: Kennst du eine #BlogEngine Community außer der auf #CodePlex? klaus_b0: Nein, aber wenn du eine findest gib mir bitte Bescheid SchelianHP: Da gibt es wirklich nichts klaus_b0: Hättest du Interesse eine eigene Seite aufzuziehen? SchelianHP: Wäre eine Option, habe schon mal ein anderes Forum hochgezogen… klaus_b0: Ich helfe dir gerne moderieren, administrieren udgl. SchelianHP: Das habe ich gehofft. Oder soll ich sagen, das hoffe ich doch. Das kriegen wir… klaus-b0: Wenn das Forum konkret wird, Plattform, Bereiche udgl., schick mir einfach ne DM. Es folgten noch ein paar Mails hin und her. Darin beschlossen wir auch GENiALi zu fragen, ob er Interesse hätte mit an Board zu kommen. Roland erschien mir eine gute Wahl, da er einer der wenigen deutschsprachigen Entwickler ist die sich mit BlogEngine.NET befassen und er auch schon die ein oder andere Anpassung an der Codebasis vorgenommen hat. Roland sagte zu und so stand das Basisteam fest. Hans-Peter registrierte die Domäne dotnetblogengine.de, in Anlehnung an den Original Domänennamen von Mads Kristensen dem Gründer des Open Source Projekts, und stellte den Webserver zur Verfügung. Als Plattform für die Community-Seite wurde DotNetNuke gewählt, da Hans-Peter aus dieser Richtung kommt und einiges an Erfahrung mit der Plattform mitbringt. Mir war es egal welche Plattform verwendet wird. Hauptsache .NET basierend und für Anpassungen Zugang zum Quellcode. Den Mittelpunkt des Portals wird ziemlich sicher das enthaltene Forum darstellen. In diesem Forum sollen die gängigsten Themen rund um BlogEngine.NET behandelt werden. Dabei ist der Schwerpunkt nicht auf Entwickler gerichtet, sondern auf den Endanwender. Egal ob Probleme bei der Installation auftreten, Themes, Widgets oder Erweiterungen angepasst werden sollen. Entwickler die sich schon jetzt oder erst zukünftig mit BlogEngine.NET befassen wollen, sind natürlich herzlich Willkommen. Eine FAQ befindet sich gerade in Arbeit. In ihr werden die häufigsten Fragen zur BlogEngine zusammengetragen und beantwortet. Wenn ihr schon jetzt Fragen zur BlogEngine habt die euch unter den Nägeln brennen, scheut euch nicht sie im Forum zu stellen. Wenn ihr noch kein Mitgliederkonte eingerichtet habt, hier geht’s direkt zur Registrierung. Als dringlichstes Ziel haben wir uns gesetzt, BlogEngine.NET als Blog-System bekannter und attraktiver zu machen. Mit Eröffnung der BlogEngine.NET Gallery ist von Seiten der BlogEngine Entwickler ein Schritt in die richtige Richtung getan. Die Gallery füllt sich stetig mit neuen Inhalten. Die einfache Installation der Themen, Widgets und Erweiterungen direkt in der Verwaltung eines Blog sollte uns in unserem Vorhaben unterstützen. Ein weiterer wichtiger Punkt den wir in Angriff nehmen, ist die Unterstützung bei der Portierung von bestehenden Blogs aus anderen System. Hier sehen wir noch dringenden Handlungsbedarf. Das bloße Übertragen der bestehenden Artikel und Kommentare via BlogML ist noch keine erfolgreiche Portierung. Bestehende Verknüpfungen dürfen nicht verloren gehen, sondern müssen auf die neue Linkstruktur weitergeleitet werden. Hier ist noch viel Arbeit nötig. Oft wird die schlechte Lokalisierung von BlogEngine Installationen mit Recht angeprangert. Auch hier soll Abhilfe geschaffen werden. In Sachen SEO ist die Engine nicht das was sie leisten könnte, obwohl mit kleinen Änderungen schon viel erreicht werden kann. Die Leistung von BlogEngine basierenden Blogs lässt auch oft zu wünschen übrig. Auch hier kann geholfen werden. Wir sind uns noch nicht einig ob wir eine, für den deutschen Sprachraum, überarbeitete und dahingehend angepasste Version zur Verfügung stellen sollen. Die fortlaufenden Änderungen der originalen Version würden natürlich eingepflegt. Bevor wir uns jedoch einen Haufen Arbeit auf die Schultern laden, könntet ihr uns einfach mitteilen ob überhaupt Interesse eurerseits besteht. Wenn ja, was stellt ihr euch vor? Besucht uns auf unserem Portal und teilt uns eure Meinung mit. Technorati-Tags: community | blogengine.net
|
|
14.06.2011 - 11:37:22
|
|
http://feedproxy.google.com/~r/klaus_b/~3/J4i6zGbruwI/post.aspx
|
|
|
|
|
|
|
|
|
In letzter Zeit sind mit in den Google Webmaster-Tools vermehrt Warnungen zu doppelten Titeln und doppelter Beschreibung aufgefallen. Die Ursache ist im Verhalten von BlogEngine.NET begründet, dass die URL zu einem Artikel sowohl mit angegebenem Datum als auch ohne dieses akzeptiert wird. Demzufolge wird für beide URLs, sowohl mit als auch ohne Datum, der selbe Inhalte zurückgegeben. Um BlogEngine.NET dieses Verhalten auszutreiben, ist nur ein kleiner Eingriff in die Methode RewritePost der Klasse UrlRewrite im Namensraum BlogEngine.Core.Web.HttpModules nötig. if (post == null)
{
return;
}
// HACK: um double content mit URLs ohne Datum zu vermeiden.
if (!haveDate && !url.Contains("/FEED/"))
{
context.Response.RedirectPermanent(
post.AbsoluteLink.ToString(),
true);
return;
}
context.RewritePath(
url.Contains("/FEED/")
? string.Format(
"syndication.axd?post={0}{1}",
post.Id,
GetQueryString(context))
: string.Format(
"{0}post.aspx?id={1}{2}",
Utils.ApplicationRelativeWebRoot,
post.Id,
GetQueryString(context)),
false);
Nach der Überprüfung, ob ein passender Artikel gefunden wurde, wird mit der Variablen haveDate überprüft ob kein Datum im URL vorhanden ist und auch kein Feed angefordert wurde. Sind beide Bedingungen erfüllt, wird mittels 301 auf den URL mit Datum umgeleitet. Das Vorkommen von double Content, zwecks URL mit und ohne Datum, sollte sich mit dieser Maßnahme erledigt haben.
Fazit:
Kleine Maßnahme, große Wirkung.
Traurig nur, dass man überhaupt erst zu solchen Maßnahmen greifen muss. Die Entwickler von BlogEngine.NET scheinen noch immer nicht verstehen, dass zu einem Blog-System mehr gehört als nur Artikel und Kommentare zu verarbeiten.
Technorati-Tags: blogengine.net | double content | SEO
|
|
12.06.2011 - 19:05:38
|
|
http://feedproxy.google.com/~r/klaus_b/~3/xGFoASJ7vSI/post.aspx
|
|
|
|
|
|
|
|
|
Mit BlogEngine.NET ab Version 2.0.0.62 ist die volle Unterstützung für Themes enthalten, die den Razor-Syntax verwenden wollen. Das bedeutet spätestens ab jetzt ist ASP.NET in der Version 4 angesagt, wie auch die Änderungen an den unterschiedlichen web.config Dateien unschwer erraten lassen. Neu ist auch die Unterstützung für das IIS Rewrite Modul. Die verwendete jQuery-Version wurde auf Version 1.5.2 aktualisiert. Neu hinzugekommen ist die Unterstützung mehrere Blogs unter einer Installation. Ebenfalls neu ist die Möglichkeit, die Angabe von Webseiten in den Kommentaren zu verbieten. Bevor man aber die vielen neuen Feature nutzen kann, ist erst einmal ein Datenbankupdate fällig. Das SQL-Script für dieses Datenbankupdate trägt bereits die Bezeichnung From2.0To2.5. Mit diesem Update werden folgende Änderungen vorgenommen: Diverse Fremdschlüssel entfernt. In die Tabelle be_Users eine neue Spalte BlogID eingefügt. In die Tabelle be_UserRoles eine neue Spalte Role eingefügt. In die Tabelle be_StopWords eine neue Spalte BlogID eingefügt. In die Tabelle be_Settings die neuen Spalten SettingRowId und BlogID eingefügt. In die Tabelle be_Roles eine neue Spalte BlogID eingefügt. In die Tabelle be_Rights die neuen Spalten RightRowId und BlogID eingefügt. In die Tabelle be_RightRoles die neuen Spalten RightRoleRowId und BlogID eingefügt. In der Tabelle be_Referrers die neuen Spalten ReferrerRowId und BlogID eingefügt. In der Tabelle be_Profiles eine neue Spalte BlogID eingefügt. In der Tabelle be_Posts die neuen Spalten PostRowID und BlogID eingefügt. In der Tabelle be_PostTag eine neue Spalte BlogID eingefügt. In der Tabelle be_PostNotify eine neue Spalte BlogID eingefügt. In der Tabelle be_PostComment die neuen Spalten PostCommentRowID und BlogID eingefügt. In der Tabelle be_PingService eine neue Spalte BlogID eingefügt. In der Tabelle be_Pages die neuen Spalten PageRowID und BlogID eingefügt. In der Tabelle be_DataStoreSettings die neuen Spalten DataStoreSettingRowId und BlogId eingefügt. In der Tabelle be_Categories die neuen Spalten CategoryRowID und BlogID eingefügt. Eine neue Tabelle be_Blogs erstellt. Diverse neue Indizes erstellt. Wer sich unter BlogEngine.NET mit einem eigenem Razor-Theme versuchen möchte, findet im Ordner themes mit Garland-Revisited bereits eine nette Vorlage. Wer bereits mit einem eigenem Theme arbeitet, kann davon einen Screenshot anfertigen und diesen im PNG-Format in das Verzeichnis des jeweiligen Themas unter dem Namen theme.png mit einer Breite von 480 Pixel ablegen. In der Blogverwaltung wird dieser Screenshot als Vorschau in der Themenauswahl angezeigt, Das ist kein Muss, aber ein nettes Gimmick. Die Suche nach verfügbaren Themen für BlogEngine.NET wurde durch eine eigene Gallery stark vereinfacht. In den Einstellungen zu den Themen einfach auf den Link Gallery klicken und eine Auswahl an verfügbaren Themen wird direkt in der Einstellungsseite angezeigt. Sollte ein Thema gefallen, einfach auf Install klicken und Dank der Verwendung von NuGet wird das gewählte Thema auf dem Server installiert. Ähnlich einfach wurde die Installation verfügbarer Erweiterungen und Widgets. Im Dashboard prangt einem jetzt sofort eine Auswahl verfügbarer Erweiterungen und Widgets entgegen. Hinter dem Dienst, sowohl für die Themen als auch für die Widgets und Erweiterungen, steht die blogenginge.net gallery. Wer seine Erweiterungen, Widgets oder Themen zur Verfügung stellen will, kann sie in der Gallery veröffentlichen. Wer seinen Besuchern wie bisher erlauben will, ihre Webseiten in den Kommentaren anzugeben, sollte nicht vergessen unter Einstellungen/Kommentare den Hacken bei Enable Website in Comments zu setzen. Diese Einstellung ist standardmäßig deaktiviert. Die bisher eingetragenen Verknüpfungen zu Webseiten bleiben von dieser Einstellung unberührt, aber bei allen neuen Kommentaren wird die Verknüpfung zur Webseite nicht gespeichert, wenn diese Option nicht gesetzt wird. Trotz der vielen Neuerungen sind viele, schon ältere, Fehler nicht gefixt. So funktioniert z.B.: die Übersichtsliste im Archiv noch immer nicht. Hier müsste nur der Anker korrekt gesetzt werden. Auch neue Fehler sind hinzugekommen. Wer ein Fehler-Logging aktiv hat, dem wird folgende Fehlermeldung auffallen: Error loading compiled extensions from assembly BlogEngine.Core, Version=2.0.0.66, Culture=neutral, PublicKeyToken=fed3cbd6fd4e62d0: Unable to load one or more of the requested types. Retrieve the LoaderExceptions property for more information. Diese Fehlermeldung ist etwas irreführen, denn sie bezieht sich nur im weiteren Sinn auf einen Fehler beim Laden der Erweiterungen. Vielmehr wird hier ein Problem mit der Zugriffssicherheit abgeleiteter Typen bemängelt, welches erst bei Verwendung der Assembly Blogengine.Core unter der .NET Version 4 auftritt. Im einzelnen sind alle Klassen im Namensraum BlogEngine.Core.Compilation.Design betroffen. Hier schafft das setzten des Attribut SecurityRules mit dem Wert Level1 der SecurityRuleSet-Enumeration auf die Assembly Abhilfe. Dazu einfach in der AssemblyInfo.cs folgende Zeile einfügen: [assembly: SecurityRules(SecurityRuleSet.Level1)]
Von da an, werden die Sicherheitsregeln wieder wie unter .NET Version 2 auf die Assembly angewendet.
Ein weiterer, aus meiner Sicht, Fehler ist der etwas gedankenlose Umgang mit einem ungeprüften Ergebnis einer String.IndexOf Abfrage in der Methode RewriteDefault der Klasse UrlRewrite. Da wird im else-Block ungeprüft das Ergebnis an eine String.SubString Methode übergeben. Wenn hier die überprüfte Zeichenfolge default.aspx nicht gefunden wird, wird der Wert –1 an die String.SubString Methode übergeben, was in einer IndexOutOfRangeException endet. Hier hilft als Hack, bis zum Fix, ein Ternär-Operator:
else
{
string newUrl = url.Replace("Default.aspx", "default.aspx"); // fixes a casing oddity on Mono
// HACK: ternary operator um eine IndexOutOfRangeExcption zu vermeiden
int defaultStart = url.IndexOf("default.aspx") == -1 ? 0 : url.IndexOf("default.aspx");
newUrl = Utils.ApplicationRelativeWebRoot + url.Substring(defaultStart);
context.RewritePath(newUrl);
}
FAZIT:
Die BlogEngine mausert sich immer mehr zu einer richtigen Blog-Anwendung.
Vielen Anwenderwünschen wird langsam Rechnung getragen, wie etwa die einfachere Installation von Themen, Widgets oder Erweiterung. Ich will hoffen, dass sich durch Eröffnung der blogengine.net gallery auch die Anzahl ansprechender Themen langsam erhöht. Dies war bisher immer einer der Hauptargumente um die BlogEngine.NET nicht zu verwenden.
Technorati-Tags: blogengine.net | asp.net | c#
|
|
09.06.2011 - 14:10:17
|
|
http://feedproxy.google.com/~r/klaus_b/~3/9LpDuAlreIY/post.aspx
|
|
|
|
|
|
|
|
|
|
 Unser RSS-Feed
Natürlich haben wir auch einen eigenen RSS-Feed den Du gerne abonnieren kannst ...
|
|
 Suche im Verzeichnis
RSS Verzeichnis nach Blog Feeds oder Tags durchsuchen:
|
|
 Login
|
|
|
|
|
|
 Kategorien
|
|
 Top Tags im Verzeichnis
|
|
|