Verwendung der Client Resource Management API in einem DotNetNuke Skin

DotNetNuke  bietet ja seit der Version 6.x an das die CSS und JavaScript Resourcen (Dateien) zusammengefasst werden. Auf die Vorteile muss hier nicht im Einzelnen eingegangen werden - nur so viel: Es kann die Ladezeit einer Seite deutlich erhöhen. Bei der Erstellung von einem Skin legt man im Regelfall eine skin.css ein, die dann alle relevanten Formatierungsanweisungen beinhaltet. Wenn man nun externe CSS Dateien noch mit in das Skin einbinden möchte (oder seine eigene Struktur über mehrere Dateien verteilt) wird vielfach der CSS Befehl @Import genutzt. In diesem Fall werden die importierten Dateien nicht berücksichtigt bzw. mit in eine große Datei gepackt.  Damit weitere CSS Dateien (oder auch JS Dateien) berücksichtigt werden, muss man die DotNetNuke SkinObjects DnnCssInclude bzw. DnnJsInclude nutzen. Vor der Nutzung muss man noch die Komponente registrieren in der Skin-Datei, das passiert über den üblichen Weg: <%@ Register TagPrefix="dnn" Namespace="DotNetNuke.Web.Client.ClientResourceManagement" Assembly="DotNetNuke.Web.Client" %> Ein konkretes Beispiel für die Einbindung könnte dann so aussehen: <dnn:DnnCssInclude runat="server" FilePath="menu/menu.css" PathNameAlias="SkinPath" /> Der optionale Parameter PathNameAlias kann mit den Werte SkinPath oder SharedScripts gefüllt werden. SkinPath ist dabei der aktuelle Pfad zum verwendeten Skin und somit lassen sich alle Dateien unterhalb vom Skinverzeichnis einbinden und bei SharedScripts wird das Verzeichnis ~/Resources/Shared/Scripts/ als Basisverzeichnis genutzt.  Zusätzlich kann man noch über die Option Priority die Reihenfolge der Dateien bestimmen. Weitere Informationen findet man dazu auch im offiziellen DotNetNuke Wiki.

ASP.NET erzeugt permanent eine neue SessionId

Bei der Verwendung von Session bzw. genauer der SessionId als Kennzeichner für z.B. eine eindeutigen Benutzer (Browser) über seinen Lebenszyklus, muss man die Eigenart bzw. die Behandlung von Sessions innerhalb von ASP.NET kennen. Bei der Verwendung von cookie-based session state wird von ASP.NET bei jedem Request eine neue SessionId erzeugt, bis der erste Wert in der Session gespeichert wurde.  Das ist sehr wichtig zu wissen, denn sonst ist die SessionId nicht als eindeutiger Kennzeichner zu benutzen. (Wie sinnvoll das generell ist, sei jetzt hier mal nicht berücksichtigt). Es reicht also z.B. in der global.ascx folgenden Code hinzufügen: void Session_Start(object sender, EventArgs e) { HttpContext.Current.Session.Add("SomeValue", string.Empty); } Damit hat die Session immer eine Wert und behält somit die SessionId bis die Session ausläuft. Weitere Informationen findet man dazu auch hier.

HttpWebRequest: Den basic http authentication header erzwingen

Um in .NET http web request zu realisieren ist die Klasse HttpWebRequest aus dem .NET Framework ist sehr komfortabel. Diese unterstützt auch die Möglichkeit Authentication Informationen im Header mitzusenden. Hier ein vereinfachtes Beispiel dafür var webRequest = (HttpWebRequest)WebRequest.Create(url); webRequest.Credentials = new NetworkCredential(userName, password); Nun ist es allerdings so, dass diese Informationen nicht direkt als http Header mitgesendet werden sondern erst auf Anfrage von Server / Service bei einem weiteren Request hinzugefügt werden. Um zu erreichen das der http basic authentcation header immer mitgesendet wird kann man folgenden Code verwenden: public void SetBasicAuthHeader(WebRequest httpRequest, String userName, String userPassword) { var authHeader = Convert.ToBase64String(Encoding.Default.GetBytes(userName + ":" + userPassword)); httpRequest.Headers["Authorization"] = "Basic " + authHeader; } Damit wird der Header direkt beim ersten Aufruf mitgesendet.

Html Element per jQuery selector mit einem Punkt in der id finden

Bei der Benennung von Html Elementen kann es sinnvoll sein diese durch den Namen (bzw. Id) in Gruppen zu fassen. Besonders wenn man in Zusammenhang mit ASP.NET MVC wenn man dort die Daten (per Http-POST) in einem Objekt als Parameter weiter verarbeiten möchten.  public class SearchOptions { public string searchterm { get; set; } public int? Pagenumber { get; set; } } Damit die Werte in dem Objekt automatisch zur Verfügung stehen muss man folgendes Html-Element für die Property Pagename erstellen: <input type="hidden" id="SearchOptions.Pagenumber" name="SearchOption.Pagenumber" value="1" /> Möchte man nun dieses Element per JavaScript - besser mit jQuery - aktualisieren, dann muss man folgenden JavaScript Code nutzen: $('#SearchOptions\\.Pagenumber').val('1') Wichtig ist dabei das der Punkt durch zwei "\" escapet werden muss. Ansonsten wird das Element nicht gefunden. Hier ist die jQuery Referenz: http://api.jquery.com/category/selectors/

DotNetNuke speichern von Zugangsdaten der Benutzer

DotNetNuke bietet drei Möglichkeiten die Passwörter von Benutzern zu speichern. Diese können im Klartext, als verschlüsselte Zeichenfolge oder als Hashwert in der Datenbank gespeichert werden. Das die erste Variante höchtens für Test- und Entwicklungsinstallationen in Frage kommt, muss wohl nicht weiter diskutiert werden. Konfiguiert wird die Behandlung von Passwörtern in der web.config direkt bei den Einstellungen des MembershipProvider:<add name="AspNetSqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider" connectionStringName="SiteSqlServer" enablePasswordRetrieval="true" enablePasswordReset="true" requiresQuestionAndAnswer="false" minRequiredPasswordLength="7" minRequiredNonalphanumericCharacters="0" requiresUniqueEmail="false" passwordFormat="Encrypted" applicationName="DotNetNuke" /> Worin besteht aber der genau Unterschied zwischen veschlüsselt und hashed?Verschlüsselte ZeichenfolgenBei diesem Modus werden die Passwörter durch einen Verschlüsselungsalgorithmus unleserlich gemacht, so das bei einem Auslesen der Tabelle, die Passwörter nicht im Klartext zur Verfügung stehen. DotNetNuke bzw. jedes DNN-Module ist in der Lage die Passwörter ohne Probleme zu lesen. Hashwerte Wenn man als Modus "Hashed" wählt, dann wird nur ein Hashwert vom Passwort abgespeichert und nicht das eigentliche Passwort. Aus dem Hashwert kann man das Passwort nicht mehr in Klartext umwandeln und somit hat auch kein Module mehr Zugriff auf die Passwörter. Das bedeutet aber auch, dass die "Passwort zusenden"-Funktion nicht mehr das Passwort dem Benutzer zur Verfügung stellen kann. Ob das eine Anwendung überhaupt machen sollte ist eine ganz andere Diskussion. Die Funktion "Passwort vergessen" funktioniert aber natürlich trotzdem, in diesem Modus wird von DotNetNuke einfach ein neues Zufallspasswort erzeugt.Welche VarianteEs gibt keine klare Empfehlung für eine der verfügbaren Methode. Für die Auswahl sollten im Vorfeld Überlegungen angestellt werden, ob man das Passwort wirklich jemals im Klartext benötigt. Aus der Perspektive eines Datenschützers wäre eigentlich nur die Möglichkeit "als Hashwert" akzeptable.

DotNetNuke Performance Optimierung

Die Performance einer Webseite ist heute aus vielen Gründen - die hier nicht weiter aufgeführt werden müssen - wichtig. DotNetNuke selber bietet eine Vielzahl von Einstellungen, die direkten Einfluss auf die Performance der Webseite haben können. Es gibt auf Codeplex ein Projekt, dass durch ein paar SQL Scripts die Einstellungen einer DotNetNuke Installation so modifiziert, das eine maximale Performance möglich ist. Natürlich muss man im Einzelfall über manche Einstellung extra entscheiden aber als Startpunkt für eine Optimierung finde ich das sehr gelungen.Das Projekt findet man unter http://dnnperformance.codeplex.com

Die web.config bei der Installation eines Modules verändern

Je nach Modul gibt es schon mal die Anforderung, dass neue Einträge in die web.config geschrieben werden müssen. Dieses kann man entweder im SourceCode erledigen oder aber die Änderungen in dem DNN Modul Manifest / Definitionsdatei (meinmodule.dnn) definieren. Verfügbar ist das ab der Version 5 von DotNetNuke. Um das zu nutzen, muss man in der Manifest-Datei folgendes hinzufügen:<component type="Config"> <config> <configFile>web.config</configFile> <install> <configuration> <nodes>             ..... </nodes> </configuration> </install> <uninstall> <configuration> <nodes /> </configuration> </uninstall> </config> </component>Wie man sieht gibt es zwei Bereiche. Der Bereich <install> wird während der Installation und der Bereich <uninstall> wird be der Deinstallation von dem DNN Modul ausgeführt.Innerhalb des Tags <nodes> können dann die entsprechenden Einträge hinzugefügt werden. Hier ein Beispiel womit ein HttpHandler hinzugefügt wird:<node path="/configuration/system.web/httpHandlers" action="update" key="path" collision="overwrite"> <add verb="*" path="myhandler.axd" validate="false" type="DNN.Module.Shop.MyHandler, DNN.Module.Shop" /> </node>Weiter Informationen findet man im Wiki auf www.dotnetnuke.com

Conditional Stylesheets oder CSS hacks

Jeder der sich schon mal mit dem Thema Webdesign beschäftigt hat, kennt die Probleme der unterschiedlichen Browser. Besonders der Internet Explorer ist ein Kandidat, bei dem man sehr schnell graue Haare bekommt kann. Meistens werden Hacks verwendet, die nur von bestimmten Versionen / Browsern erkannt bzw. akzeptiert werden und von den anderen als Fehler ignoriert. Das Problem von solchen Hacks ist unter anderem dafür sorgen das eine CSS nicht mehr validiert werden kann. Um dieses Problem zu umgehen verwenden viele Conditional Stylesheets, die dann vom jeweiligen Browser erkannt und geladen werden. Das sieht dann so aus:<link rel="stylesheet" type="text/css" media="screen" href="skin/css/style.css" /> <!--[if IE 7]><link rel="stylesheet" type="text/css" media="screen" href="skin/css/ie7.css" />< ![endif]--> <!--[if IE 6]><link rel="stylesheet" type="text/css" media="screen" href="skin/css/ie6.css" />< ![endif]-->Nun kann man für die verschiedenen Browser(versionen) die entsprecheden CSS-Definitionen sauber überschreiben. Ist doch super oder?Diese Variante ist schon deutlich besser als die Verwendung von Hacks im eigentlichen CSS aber hat den großen Nachteil das noch mehr Dateien vom Server geladen werden müssen. Eine deutlich besser Lösung sieht so aus:<!--[if lt IE 7 ]> <html class="ie6"> <![endif]--> <!--[if IE 7 ]> <html class="ie7"> <![endif]--> <!--[if IE 8 ]> <html class="ie8"> <![endif]--> <!--[if IE 9 ]> <html class="ie9"> <![endif]--> <!--[if (gt IE 9)|!(IE)]><!--> <html class=""> <!--<![endif]-->Hier wird nun in Abhängigkeit von der Browserversion das Tag HTML mit einer CSS-Klasse versehen und man kann nun ganz sauber und ohne Hacks für die div. Versionen CSS-Definitionen erstellen, dass dann z.B. so aussieht:: div.contentpane { width: 510px; } .ie6 div.contentpane { width: 500px; }Diese Technik wird z.B. auch vom bekannten http://html5boilerplate.com/ verwendet.

URI schema constant

Immer wiedermal muss man in seinem Code überprüfen welches Schema die URI (URL) besitzt. Daher sieht man oft solche Codezeilen:if (httpRequest.Uri.Scheme == "https")Ich bin absolut kein Freund von solchen Abfragen, die auf einen String-Wert vergleichen, den man selber schreiben muss. Das ist einfach viel zu Fehleranfällig und verleitet auch durch Copy & Paste und zu schnelles Tippen einfach Fehler zu machen... die Abfrage ist ja auch wirklich zu einfach um länger drüber nachzudenken.Dabei kann man sich das Leben auch einfacher machen und zumindest in meinen Augen auch den Code richtig schreiben durch die Verwendung einer Konstanten aus dem .NET Framework. Die Klasse URI besitzt nämlich schon bereits diese Konstanten!Der Code von oben sieht dann plötzlich wie folgt aus:if (httpRequest.Uri.Scheme == Uri.UriSchemeHttps)Hier einen Auszug der vordefinierten Werte:UriSchemeFtp: URI für  FTP (File Transfer Protocol).UriSchemeHttp: URI für HTTP (Hypertext Transfer Protocol) UriSchemeHttps: URI für HTTPS (Secure Hypertext Transfer Protocol).UriSchemeMailto: Gibt an, dass der URI eine E-Mail-Adresse ist und der Zugriff über SMTP (Simple Mail Transport Protocol) erfolgt.UriSchemeNetPipe: Gibt an, dass auf den URI über das von Windows Communication Foundation (WCF) verwendete NetPipe-Schema zugegriffen wird.UriSchemeNetTcp: Gibt an, dass auf den URI über das von Windows Communication Foundation (WCF) verwendete NetTcp-Schema zugegriffen wird.UriSchemeNntp: URI für eine Internetnewsgroup, auf die über NNTP (Network News Transport Protocol) zugegriffen wirdDetails gibt es hier.

ASP 0131 Unzulaessiger Pfad zum uebergeordneten Verzeichnis

Bei dem Versuch eine alte ASP (classic asp) Anwendung auf einem Windows 2008 R2 System und damit auf einem IIS 7.5 funktionsfähig zu bekommen bestand das Problem das die Seite immer nur einen HTTP Errorcode 500 zurück gibt. Einen Blick in die Logs von IIS ergab die Fehlermeldung:"|23|ASP_0131|Unzulässiger_Pfad_zum_übergeordneten_Verzeichnis"Ursache dieses Problems ist das Standardmäßig der übergeordneten ASP-Pfade für eine Website oder Anwendung bei gleichzeitiger Verwendung relativer übergeordneter Pfade in einer Include-Anweisung nicht erlaubt / deaktiviert ist. Empfohl ist die Einbindungen von <!--#include file="../dbconn.inc"-->auf<!--#include virtual="/<virtual path>/dbconn.inc"-->zu verändern. Alternativ kann man aber auch den Internet Information Server so einstellen, dass die übergeordneten ASP-Pfade erlaubt sind.Dafür muss man beim IIS auf das entsprechende Web klicken und in der rechten Seite unter ASP die Konfiguration öffnen. Dort gibt es dann die Option "Übergeordnete Pfade aktivieren" der Wert muss von False auf True umgestellt werden.

API REST Html-Helppage display Json requestsample

Für mein aktuelles API Projekt erstelle ich eigene Helppages auf denen ich auch Requestbeispiele für Xml und Json anzeige. Die Erstellung eines Request-Beispiel wird durch Xsd erstellt und das resultierende Xml wird dann durch Json.NET in Json konvertiert. Der Aufruf dazu sah wie folgt aus: var jsonSample = JsonConvert.SerializeXNode(xmlSampelRequest); Als Ergebnis wurde auch Json ausgebeben, leider aber ohne Zeilenumbrüche, was die Darstellung und Lesbarkeit doch stark reduziert. Damit der Json-String auch "vernüftig" formatiert wird, kann man bei der Serializierung noch einen Parameter setzen und damit die Formatierung erzwingen: Formatting.Indented Der Aufruf sieht dann wie folgt aus: var jsonSample = JsonConvert.SerializeXNode(xmlSampelRequest, Newtonsoft.Json.Formatting.Indented);

WCF OperationContract nicht optionale Prameter im Wsdl

Wenn man mit der WCF einen Service definiert und dabei Nachrichtenbasiert kommunizieren möchte, sieht kann die Definition z.B. so aussehen:[ServiceContract(Namespace = APICommon.DefaultSOAPNameSpace)]public interface ICartSoapService{ [OperationContract] ProcessCartResponse ProcessCart(ProcessCartRequest request);}[DataContract]public class ProcessCartRequest{ [DataMember(IsRequired = true)] public Cart Cart { get; set; }}[DataContract]public class ProcessCartResponse : BaseResponseMessage{ [DataMember(IsRequired = true)] public ProcessResultType ProcessResult { get; set; }}Die WCF erzeugt auch brav eine passendes Wsdl Datei für diese Beschreibung. Allerdings hat die "Standardausgabe" der WCF datei den Nachteil, das der Parameter "request" der Methode ProcessCart immer optionaler Parameter ist bzw. in der Wsdl Datei wird das Element mit den Attribute minOccurs="0" gekennezichnet. Da ohne den Parameter die Methode aber nicht vernüftig abgearbeitet werden kann, müsste im Wsdl eigentlich ein minOccurs="1" stehen ...also kein optionaler Parameter. Leider gibt es bei den Standardattributen der WCF keine Möglichkeit, diese Verhalten oder viel mehr die Wsdl-Generierung zu beinflussen. Trotzdem kann man durch ein eigenes Attribute das gewünschte Verhalten sehr schnell der WCF beibringen.Dafür muss man lediglich ein Attribute anlegen und die Interfaces IContractBehavior + IWsdlExportExtension mit hinzufügen. Die vollständige Implementierung sieht so aus: [AttributeUsage(AttributeTargets.Interface)] public class OperationsParametersAreRequiredAttribute : Attribute, IContractBehavior, IWsdlExportExtension { private List<RequiredOperationParameter> _requiredOperationParameters; public void AddBindingParameters( ContractDescription contractDescription, ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters) { } public void ApplyClientBehavior( ContractDescription contractDescription, ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime) { } public void ApplyDispatchBehavior( ContractDescription contractDescription, ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.DispatchRuntime dispatchRuntime) { } public void ExportContract( WsdlExporter exporter, WsdlContractConversionContext context) { if (_requiredOperationParameters == null) _requiredOperationParameters = new List<RequiredOperationParameter>(); foreach (var operation in context.Contract.Operations) { var requestMessage = operation.Messages.Where(m => m.Direction == MessageDirection.Input).FirstOrDefault(); var parameters = operation.SyncMethod.GetParameters(); Debug.Assert(parameters.Length == requestMessage.Body.Parts.Count); for (var iLoop = 0; iLoop < parameters.Length; iLoop++) { var attributes = parameters[iLoop].GetCustomAttributes( typeof(OperationParameterIsOptionalAttribute), false); if (attributes.Length == 0) { _requiredOperationParameters.Add(new RequiredOperationParameter { Namespace = requestMessage.Body.Parts[iLoop].Namespace, Name = requestMessage.Body.Parts[iLoop].Name, Message = operation.Name }); } } } } public void ExportEndpoint( WsdlExporter exporter, WsdlEndpointConversionContext context) { foreach (var requiredParamter in _requiredOperationParameters) { var schemas = exporter.GeneratedXmlSchemas.Schemas(requiredParamter.Namespace); foreach (XmlSchema schema in schemas) { var message = schema.Elements[requiredParamter.XmlName] as XmlSchemaElement; var complexType = message.ElementSchemaType as XmlSchemaComplexType; var sequence = complexType.Particle as XmlSchemaSequence; foreach (XmlSchemaElement schemaElement in sequence.Items) { if (schemaElement.Name == requiredParamter.Name) { schemaElement.MinOccurs = 1; schemaElement.MinOccursString = "1"; break; } } } } _requiredOperationParameters.Clear(); } public void Validate( ContractDescription contractDescription, ServiceEndpoint endpoint) { } internal class RequiredOperationParameter { public string Message { get; set; } public string Name { get; set; } public string Namespace { get;set;} public XmlQualifiedName XmlName { get { return new XmlQualifiedName(Message, Namespace); } } } }

WCF REST Could not load file or assembly 'System.ServiceModel.Activation'

Bei dem Versuch HTTP Basic Auth für einen REST Dienst zu implementieren (der wiederrum durch Konfiguration per serviceActivations in der .config und eigenere factory gestartet wird) bekam ich die Fehlermeldung bei der Umstellung vom Attribute aspNetCompatibilityEnabled von "false" auf "true"  das die Assembly 'System.ServiceModel.Activation' nicht gefunden werden konnte. Hier die genaue Fehlermeldung: System.IO.FileNotFoundException: Could not load file or assembly 'System.ServiceModel.Activation' or one of its dependencies. The system cannot find the file specified.File name: 'System.ServiceModel.Activation'   at System.RuntimeTypeHandle.GetTypeByName(String name, Boolean throwOnError, Boolean ignoreCase, Boolean reflectionOnly, StackCrawlMarkHandle stackMark, Boolean loadTypeFromPartialName, ObjectHandleOnStack type)   at System.RuntimeTypeHandle.GetTypeByName(String name, Boolean throwOnError, Boolean ignoreCase, Boolean reflectionOnly, StackCrawlMark& stackMark, Boolean loadTypeFromPartialName)   at System.Type.GetType(String typeName, Boolean throwOnError, Boolean ignoreCase)   at System.Web.Compilation.BuildManager.GetType(String typeName, Boolean throwOnError, Boolean ignoreCase)   at System.Web.Configuration.HandlerFactoryCache.GetTypeWithAssert(String type)   at System.Web.Configuration.HandlerFactoryCache.GetHandlerType(String type)   at System.Web.Configuration.HandlerFactoryCache..ctor(String type)   at System.Web.HttpApplication.GetFactory(String type)   at System.Web.HttpApplication.MaterializeHandlerExecutionStep.System.Web.                        HttpApplication.IExecutionStep.Execute()   at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)=== Pre-bind state information ===LOG: User = userLOG: DisplayName = System.ServiceModel.Activation (Partial)WRN: Partial binding information was supplied for an assembly:WRN: Assembly Name: System.ServiceModel.Activation | Domain ID: 2WRN: A partial bind occurs when only part of the assembly display name is provided.WRN: This might result in the binder loading an incorrect assembly.WRN: It is recommended to provide a fully specified textual identity for the assembly,WRN: that consists of the simple name, version, culture, and public key token.WRN: See whitepaper http://go.microsoft.com/fwlink/?LinkId=109270 for more information and common solutions to this issue.LOG: Appbase = file:///xxxxx/LOG: Initial PrivatePath xxxx\binCalling assembly : (Unknown).===LOG: This bind starts in default load context.LOG: Using application configuration file: xxxx\web.configLOG: Using host configuration file: C:\Windows\Microsoft.NET\Framework64\v4.0.30319\aspnet.configLOG: Using machine configuration file from C:\Windows\Microsoft.NET\Framework64\v4.0.30319\config\machine.config.LOG: Policy not being applied to reference at this time (private, custom, partial, or location-based assembly bind).LOG: Attempting download of new URL file:///C:/Windows/Microsoft.NET/Framework64/v4.0.30319/Temporary ASP.NET Files/root/3d7bd35f/452e5631/System.ServiceModel.Activation.DLL.LOG: Attempting download of new URL file:///C:/Windows/Microsoft.NET/Framework64/v4.0.30319/Temporary ASP.NET Files/root/3d7bd35f/452e5631/System.ServiceModel.Activation/System.ServiceModel.Activation.DLL.LOG: Attempting download of new URL file:///xxxx/bin/System.ServiceModel.Activation.DLL.LOG: Attempting download of new URL file:///Dxxxx/bin/System.ServiceModel.Activation/System.ServiceModel.Activation.DLL.LOG: Attempting download of new URL file:///C:/Windows/Microsoft.NET/Framework64/v4.0.30319/Temporary ASP.NET Files/root/3d7bd35f/452e5631/System.ServiceModel.Activation.EXE.LOG: Attempting download of new URL file:///C:/Windows/Microsoft.NET/Framework64/v4.0.30319/Temporary ASP.NET Files/root/3d7bd35f/452e5631/System.ServiceModel.Activation/System.ServiceModel.Activation.EXE.LOG: Attempting download of new URL file:///xxxx/bin/System.ServiceModel.Activation.EXE.LOG: Attempting download of new URL file:///xxxxx/bin/System.ServiceModel.Activation/System.ServiceModel.Activation.EXE. - Thread: 15Das Problem liegt an einem Eintrag in der web.config, denn dort hatte ich unter system.webserver -> handlers folgenden Eintrag hinzugefügt:<add name="svc" path="*.svc" verb="*" type="System.ServiceModel.Activation.ServiceHttpHandlerFactory, System.ServiceModel.Activation"/>Nach dem Ändern bzw. Ergänzen diesers Eintrages: <add name="svc" path="*.svc" verb="*" type="System.ServiceModel.Activation.ServiceHttpHandlerFactory,System.ServiceModel.Activation, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="integratedMode,runtimeVersionv4.0" />Funktioniert auch wieder die Aktivierung vom WCF basierten REST Service.

WCF Http API REST XML Custom ErrorHandler

Im Augenblick entwickel ich eine Architektur für eine WEB-API basierend auf der WCF. Als Grundlage habe ich das WCF Http Projekt (das auf Codeplex zu finde ist) genommen. Mir gefällt dort insbesondere der Ansatz der MediaTypeProcessor, womit sich die Request- und Response-Formate sehr schön beeinflussen lassen.Ein wichtier Aspekt bei jeder Architektur ist das Thema Exceptionhandling. Leider gibt es für das oben genannte Projekt noch keinen eigenen Exceptionhandler, der auch die MediaTypeProcessors nutzt, um eine Exception im angeforderten Format zurück zu geben. Das kann z.B. XML, Json aber theoretisch auch ein Bild oder Wav Dateien sein. Daher habe ich heute mal einen Exceptionhandler geschrieben, der mit dem Projekt zusammenarbeitet. Als Basis wird hierbei natürlich das Interface der WCF IErrorHandler genutzt und eine generelle Basisimplementierung aus dem Projekt.Ich bin noch nicht 100% glücklich mit der Lösung aber im Augenblick funktioniert das so ganz gut. Werde die Implementierung auch auf Codeplex posten und hoffe dort vielleicht weitern Input zu finden. Aber auch per E-Mail freue ich mich über konstruktive Beiträge!Here we go: public class RESTMessageErrorHandler : HttpMessageErrorHandler, IErrorHandler { // Public Methods #region HandleError public override bool HandleError( Exception error) { Logging.Error("API Exception", error); return true; } #endregion // Protected Methods #region ProvideResponse protected override void ProvideResponse( Exception exception, Microsoft.Http.HttpResponseMessage response) { APIBaseException apiException = null; if (exception is APIBaseException) apiException = exception as APIBaseException; else apiException = new APIBaseException(System.Net.HttpStatusCode.InternalServerError,  "An error has occured processing your request."); var supportedMediaTypes = new List<string>(); var httpMessageProperty = OperationContext.Current. IncomingMessageProperties[HttpMessageProperty.Name] as HttpMessageProperty; var httpRequest = httpMessageProperty.Request as HttpRequestMessage; var contentType = httpRequest.Headers.ContentType; var uriMatch = httpRequest.Properties.First( p => p.GetType() == typeof(UriTemplateMatch)) as UriTemplateMatch; var endpoint = OperationContext.Current.Host.Description.Endpoints.Find( OperationContext.Current.EndpointDispatcher.EndpointAddress.Uri); var dispatchOperation = OperationContext.Current.EndpointDispatcher.DispatchRuntime. Operations.Where(op => op.Name == uriMatch.Data).First(); var operationDescription = endpoint.Contract.Operations.Find(dispatchOperation.Name); //get the contenttype of the request var httpBehavoir = endpoint.Behaviors.Find<HttpEndpointBehavior>(); var processors = httpBehavoir.GetResponseProcessors( operationDescription.ToHttpOperationDescription()).ToList<Processor>(); //Fallback for empty contenttype if (string.IsNullOrEmpty(contentType)) { foreach (var processor in processors) { var mediaTypeProcessor = processor as MediaTypeProcessor; if (mediaTypeProcessor == null) continue; supportedMediaTypes.AddRange(mediaTypeProcessor.SupportedMediaTypes); } contentType = ContentNegotiationHelper.GetBestMatch(httpRequest.Headers.Accept.ToString(),  supportedMediaTypes).MediaType; if (string.IsNullOrEmpty(contentType)) contentType = "text/plain"; } //set http-header and status code response.Headers.ContentType = contentType; response.StatusCode = apiException.Status; //search processor for the output-serialization foreach (var processor in processors) { var mediaTypeProcessor = processor as MediaTypeProcessor; if (mediaTypeProcessor == null) continue; if (mediaTypeProcessor.SupportedMediaTypes.Contains<string>(contentType)) { response.Content = HttpContent.Create(s => mediaTypeProcessor. WriteToStream(new APIExceptionContract(apiException), s, httpRequest)); break; } } //if no processor found use plain text if (response.Content == null) response.Content = HttpContent.Create(apiException.Description); } #endregion }

HttpRequestValidationException 0x80004005 A potentially dangerous Request.Form value was detected from the client

Bei der Umstellung eines ASP.NET Projektes auf das Framework 4.0 wurde bei bestimmten Eingabedaten immer der Fehler geworfen:System.Web.HttpRequestValidationException (0x80004005): A potentially dangerous Request.Form value was detected from the client (_dataTextBox="...bitkarte (<print template="pay...").   at System.Web.HttpRequest.ValidateString(String value, String collectionKey, RequestValidationSource requestCollection)   at System.Web.HttpRequest.ValidateNameValueCollection(NameValueCollection nvc, RequestValidationSource requestCollection)   at System.Web.HttpRequest.get_Form()Dieses konnte man unter ASP.NET 2.0 durch ein @pagedirektive unterdrücken bzw. die Filterung ausschalten. Bei ASP.NET 4.0 ist dieses per @pagedirektive aber nicht mehr per Default möglich, es gibt aber die Möglichkeit die RequestValidierung per Eintrag in die web.config wieder in den Modus "ASP.NET 2.0" zu versetzen, damit das Verhalten gleich bleibt. Dafür ergänzt man die web.config wie folgt:<httpRuntime requestValidationMode="2.0" /> Weiter Informationen gibt es in der MSDN.

HTTP Error 404.11 IIS7 verarbeitet keine URLs mit +

Bei dem IIS 7.5 hatte ich gerade das Problem, dass eine Url die Whitespaces als "+" codiert (auch wenn es nichht optimal ist aber leider nicht zu ändern aktuell) immer mit dem Fehler http Statuscode 404.11 und der Nachricht The request filtering module is configured to deny a request that contains a double escape sequence. beendet wurde. Der Grund für die Ablenhung der Url ist, dass "+" als ein gefährliches Zeichen eingestuft wird. Hierbei handelt es sich also um eine Sicherheitseinstellung.Wenn man nun solche Urls aber weiter verarbeiten möchte, dann kann man diesen Requestfilter einfach deaktivieren über folgenden Aufruf:%windir%System32\inetsrv>appcmd set config -section:system.webServer/security/requestfiltering -allowDoubleEscaping:trueWeitere Informationen zum RequestFiltering gibt es auch hier IIS Security RequestFiltering.

Install ASP.NET 1.1 with IIS7 on Windows 2008

In einer Evaluierungsphase habe ich gerade mal probiert eine alte ASP.Net 1.1 Anwendung auf einem Windows Server 2008 zu installieren. Per Default wird allerdings ASP.NET 1.1 nicht mehr auf einem Windows 2008 Server unterstützt, so das ein wenig manuelle Arbeit notwendig ist.Als erstes muss man die "IIS Metabase Compatibility" installieren und das geht durch die Schritte: "Start" -> "Server Manager" ->  "Manage Roles" -> "Web Server (IIS)" -> "Add Role Services".  Der zweite Schritt ist die Installation vom .Net Framework 1.1. Hier die Downloadlinks:.NET Framework Version 1.1 Redistributable Package.NET Framework Version 1.1 Service Pack 1ASP.NET Security Update for .NET Framework 1.1 SP1Wenn man das Setup ausführt, dann bekommt man vom Windows Server 2008 den Hinweis das es möglicherweise Kompatibilitätsprobleme geben könnte. Diese Meldung einfach mit "Ausführen" / "run programm" überspringen.Anschließend muss das alte .Net Framework noch im IIS registriert werden. Dafür kann man folgenden Behfel ausführen:%windir%\Microsoft.NET\Framework\v1.1.4322\aspnet_regiis -enableEin Änderung in der machine.config (unter %windir%\Microsoft.NET\Framework\v1.1.4322\config\machine.config) muss noch gemacht werden. Vor dem schließenden Tag </configSection> muss folgendes hinzhugefügt werden:<section name="system.webServer" type="System.Configuration.IgnoreSectionHandler,    System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />Einen neue ApplicationPool - der unter .Net 1.1 läuft - kann man nun mit folgendem Befehl anlegen:%windir%\system32\inetsrv\appcmd add apppool /name:"Pool1-1"  /managedRuntimeVersion:"v1.1" Bei einem 64bit Betriebsystem muss man nun noch ein Verzeichnis erstellen:md %windir%\Microsoft.net\Framework64\v1.1.4322\config undcopy %windir%\Microsoft.net\Framework\v1.1.4322\Config\machine.config %windir%\Microsoft.net\Framework64\v1.1.4322\config\machine.config

WCF URL Rewriting entfernen der Endung "svc"

Bei dem Standardverhalten, wenn ein WCF Service über den IIS veröffentlicht wird ist, dass der Endpunkt die Datei von Service ist mit der Dateiendung ".svc". Dieses sieht aber - gerade im Bezug auf ein REST-basiertes System nicht besonders elegant aus. Bei dem Einsatz von IIS 7.0 ist es sehr einfach die Dateiendung zu entfernen, ohne dabei viel Code zu schreiben. Für das URL Rewriting gibt eine Erweiterung, die man dem IIS 7.0 hinzufügen kann. Dadurch ist es sehr schön möglich z.B. über Regular Expressions ein URL Rewriting durchzuführen. Nach der Installation dieser Erweiterung muss nur noch folgende Regel definiert werden:1. Name: Entfernen der Dateiendung svc2. Request URL: Matches the pattern3. Using: Regular Expressions4. Pattern: ^([0-9a-zA-Z\-]+)/([0-9a-zA-Z\-\.\/\(\)]+) 5. Ignore Cases: true6. Action Type: Rewrite7. Rewrite URL: {R:1}.svc/{R:2}8. Append Querystring: trueFertig ;-)Per Eintrag in der web.config sieht das dann alternativ so aus:  <system.webServer>    <modules runAllManagedModulesForAllRequests="true" />        <rewrite>            <rules>                <rule name="Remove Svc Extension">                    <match url="^([0-9a-zA-Z\-]+)/([0-9a-zA-Z\-\.\/\(\)]+)" />                    <action type="Rewrite" url="{R:1}.svc/{R:2}" />                </rule>            </rules>        </rewrite>  </system.webServer>

WCF WSDL replace http://tempuri.org

Jeder Webservice sollte einen eindeutigen Namespace verwenden, um im Web auf jeden Fall eindeutig zu sein. Als Standardnamespace wird von ASP.NET Webservices (und auch von der WCF) folgender Namespace verwendet: http://tempuri.orgDieses sollte als erstes modifiziert werden, damit man erst gar nicht mit einem solchen Namespace online geht.Das Problem bei der WCF ist nun leider, dass der Namespace an drei verschiedenen Stellen angegeben werden muss, damit auch im WSDL der gewünschte Namespace durchgängig verwendet wird.Als erstes muss man dem Namespace beim der Servicebeschreibung dem [ServiceContract] angeben:[ServiceContract(Namespace = "http://dotnetnukeblog.de")] public interface IPostServiceDie zweite Stelle ist bei der Serviceimplementierung:[ServiceBehavior(Namespace = "http://dotnetnukeblog.de")] class PostService : IPostService Die letzte Stelle ist bei der Konfiguration des Endpoints mit dem Attribute bindingNamespace:<endpoint binding="basicHttpBinding" bindingNamespace="http://dotnetnukeblog.de".... Erst wenn an allen drei Stellen der Namespace gesetzt wurde, wird dieser auch durchgängig bei der WSDL Erzeugung genutzt.

WCF, IIS and 404.3 Errors

Nachdem ich an einem jungfräulichen Rechner sitze und gerade einen WCF Service debuggen wolte, bekam ich ständig vom IIS den Fehlercode 404.3. Schnell habe ich herausgefunden das die verwendete Endung ".svc" nicht einem Handler zugeordnet ist. Um dieses aber nicht manuel machen zu müssen gibt es bei der WCF ein Tool, dass die Registrierung übernimmt. Das Tool findet man unter %Windows%\Microsoft.Net\Framework\v3.0\Windows Communication Foundation\ und heißt ServiceModelReg.exe. Das muss mit dem Parameter "-i" aufgerufen werden und anschließend funktioniert auch das Hosting im IIS.

asp.net viewstate analyzer

Es gibt immer wieder mal Fälle, wo man sich gerne den von ASP.NET erzeugten Viewstate anschauen möchte, um zu sehen, welche Kompontenten dort etwas reinschreiben, zur Optimierung, etc. Für das Pflichttool (zumindest für Webentwickler) Fiddler gibt es genau für diese Aufgabe eine sehr schöne Erweiterung: Den ASP.NET ViewState Helper Damit kann man sich den decodierten Viewstate innerhalb vom Fiddler anschauen und sehr schön erkennen, was dort zum Client übertragen wurde.Diese Erweiterung ist kostefrei und sehr einfach zu installieren. Dafür muss man lediglich die Software runterladen und anschließend die Datei in das Unterverzeichnis "Inspectors" kopieren. Schon hat man nach dem nächsten Start von Fiddler einen neuen Tab-Reiter mit dem decodierten Viewstate.

Internet Explorer zeigt eine leere Seite / IE blank page

Beim Testen eines Redesign hatte ich das Problem das die neue Seite in allen Browsern angezeigt wurde, nur nicht im Internet-Explorer (auf jeden Fall im IE 7 und 8). Im IE sah ich eine leere Seite / blank page.Durch den Einsatz vom vom HTTP Debugging Tool Fiddler konnte ich aber sehen das der komplette Inhalt zum Browser übertragen wurde, habe ich mir den Quellcode anzeigen lassen, war auch alles in Ordnung. Allerdings zeigten mir die Entwicklertools - genau wie der Browser selber - auch kein Ergebnis an.Nachdem ich mir dann den Quellcode etwas näher angeschaut habe, musste ich festestellen, dass der Code unsauber war. Im Head-Bereich habe ich das Standard-Tag title verwendet aber leider sah das so aus:<title>Meine tolle Seite<title>Das Tag wurde leider nicht geschlossen. Das scheint dem IE überhaupt nicht zu schmecken und verursacht, dass die Seite komplett nicht angezeigt wird. Nachdem ich den Fehler behoben habe und das title-Tag ordnungsgemäß geschlossen hatte, wurde auch die Seite endlich im IE angezeigt.

jQuery autocomplete und aufruf eines asmx webservice per get

Beschäftige mich aktuell intensiver mit dem System Umbraco. Dabei versuch ich einen Datentyp für die Administration zu entwicklen, der per AJAX Daten aus einer Datenbank lädt. Ein Datentyp in Umbraco ist z.B. ein Textfeld, Checkbox oder ähnliches um im Adminbereich das System mit Inhalten zu befüllen.Bei meiner Suche bin ich dann auf ein jQuery-Plugin namens autocomplete aufmerksam geworden. Damit kann man eine Textbox via JavaScript mit einer autocomplete-Funktion erweitern. Da ich die Daten aber nicht per JSON direkt mit zum Client übertragen wollte, habe ich dafür einen asmx-Webservice geschrieben. Die Einbindung via jQuery ist denkbar einfach und sieht in C# z.B. so aus: private void _BuildAutoCompletedScript() { StringBuilder clientScript = new StringBuilder(); clientScript.Append(" <script type='text/javascript'>"); clientScript.Append(" $(document).ready(function() { "); clientScript.AppendFormat(" $(\"#{0}\").autocomplete( ",                                           this.autoCompleteTextbox.ClientID); clientScript.Append(" \"/umbraco/webservices/api/mywebserive.asmx/mymethod\", "); clientScript.Append(" { delay:10, minChars:2,                                       matchContains:1, cacheLength:10, autoFill:true } "); clientScript.Append(" ); "); clientScript.Append(" }); "); clientScript.Append(" </script>"); this.Page.ClientScript.RegisterClientScriptBlock(this.GetType(), this.ClientID +                                        "_autocompleteData", clientScript.ToString()); this.Page.ClientScript.RegisterClientScriptInclude("jquery.autocomplete",                                 "/umbraco_client/Application/JQuery/jquery.autocomplete.js");Auf der Serverseite wird dann eine Web-Servicemethode definiert: [WebService(Namespace = "uri:umbraco-irgendwas")] [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] [System.Web.Script.Services.ScriptService] public class mywebservice : System.Web.Services.WebService { [WebMethod] public string mymethod(string q, int limit) { return "Hello World" + q; } }Die Parameter q und limit wird vom Plugin automatisch beim Aufruf als Parameter übergeben. Dabei ist q der Inhalt aus dem Textfeld und limit ist die maximale Anzahl an Datensätzen, die zurück geliefert werden sollen.So weit so einfach, doch leider habe ich beim Aufruf immer einen http-Fehlercode 500 bekommen. Die Lösung habe ich noch einiger Recherche dann gefunden.Die web.config muss um folgendes ergänzt werden:<webServices>   <protocols>     <add name="HttpGet"/>     <add name="HttpPost"/>     <add name="HttpSoap"/>   </protocols> </webServices>sonst kann der Web-Service nicht via GET aufgerufen werden.

FCKEditor Anzahl der Zeichen ermitteln und darstellen

Auch wenn der FCKEditor mittlerweile vielfach durch den CKEditor ersetzt wird, wird in älteren Projekten dieser immer noch eingesetzt. Vielfach gibt es den Wunsch das man beim Schreiben von Texten die Anzahl der Zeichen sieht, damit man z.B. in Teaserboxen nicht zu viel Inhalt unterbringt.Vor einiger Zeit habe ich ein Plugin für den FCKEditor gefunden der so etwas ähnliches macht. Das Plugin zählte die Zeichen und nach einem eingestellten Maximalwert, wird die Eingabe verweigert. Für meine gewünschten Fall war das aber zu viel, da lediglich die Anzahl der Zeichen angezeigt werden sollen. Da es auch noch ein paar Probleme mit dem Plugin gab, habe ich mich heute hingesetzt und dieses etwas umgeschrieben. Den Download gibt es am Ende des Beitrages.Die Einbindung ist sehr simple:Den Ordern als Unterordner von Verzeichnis plugins kopieren. Den Ordner Plugin findet man bei DotNetnuke normlerweise unter ({webroot}\Providers\HtmlEditorProviders\Fck\FCKeditor\editor\plugins\)Dann muss man die Konfigurationsdatei vom Editor anpassen und folgende Zeile hinzufügen:FCKConfig.Plugins.Add( 'CharsCounter', '' ) ;Ebenfalls in der Konfiguraationsdatei muss dann ein Button bzw. eine "Toolbar" hinzugefügt werden. Das ist sehr einfach und man muss lediglich das entsprechende Toolbarset erweitern um den Eintrag CharsCounter. Hier ein Beispiel:FCKConfig.ToolbarSets["Basic"] = [     ['Bold','Italic','-','OrderedList','UnorderedList','-','Link','Unlink','-','About'],    ['CharsCounter'] ] ; Die Konfigurationsdatei vom Editor befindet sich unter {webroot}Providers\HtmlEditorProviders\Fck\Custom.Dann wird in der Toolbar vom Editor die Anzahl der Zeichen angezeigt.Den benötigte JavaScript-Code gibt es hier als Download:FCKEditor_CharsCounter_Plugin.zip (1,81 KB)

Open-Source e-mail templating engine

Auf der Suche nach einer guten Lösung um E-Mails via Template versenden zu können habe ich heute ein interessantes Open-Source Projekt gefunden. Town Crier ist eine smarte Lösung, um nicht selber ständig so etwas per "string.replace" machen zu müssen - bzw. sich selber mit dem Gedanken auseinander setzen zu müssen eine Template-Engine in .NET zu entwicklen. Den .NET Source-Code für dieses Projekt kann hier im GitHub-Repository runterladen! Entwickelt wurde die Engine in C# und steht unte der "Lesser GNU Public Licence".Eine kurze Einführung findet man hier.