.NET-Software-Entwicklung - DotNetNuke - Business-Develoment
# Tuesday, March 15, 2011

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);


Tuesday, March 15, 2011 11:13:49 AM (W. Europe Standard Time, UTC+01:00)  #    - Trackback
API | ASP.NET | WCF
# Friday, March 11, 2011

Automapper custom TypeConverter Exceptionhandling

In einem Projekt verwende ich aktuell die Komponente AutoMapper (automapper.codeplex.com), im zwischen den externen Datacontracts und den internen Entities zu mappen. Teilweise habe ich dafür auch eigenen TypeConverter entwickelt. Wenn man in diesem TypeConverter eine eigene Exception auslöst, dann erhält man beim aufrufenden Code immer eine AutoMapperMappingException.

Da ich in dem Projekt ein globales ErrorHandling habe (WCF bzw. Implementierung vom IErrorHandler), möchte ich aber die konkrete Exception gerne aus dem TypeConverter in mein Errorhandling weitergeben.

Damit das funktioniert baut man sich am besten einen Wrapper für den Aufruf vom Automapper, um dort zu entscheiden, welche Exception man weitergeben möchte. Die eigene Exception wird als InnerException vom AutoMapperMappingException mitgeliefiert. Durch überprüfung der InnerException kann man als feststellen, wie die aktuelle Exception zu bahandeln ist.

Hier ein Beispiel:
    public class AutoMapperWrapper
    {
        public static U Map<T, U>(
            T source)
        {
            try
            {
                return Mapper.Map<T, U>(source);
            }
            catch (Exception e)
            {
                while (e is AutoMapperMappingException)
                    e = e.InnerException;

                if (e == null)
                    throw;
                else
                    throw e;
            }
        }
    }




Friday, March 11, 2011 1:44:49 PM (W. Europe Standard Time, UTC+01:00)  #    - Trackback
.NET | WCF
# Wednesday, March 02, 2011

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); }
}
}
}





Wednesday, March 02, 2011 2:53:16 PM (W. Europe Standard Time, UTC+01:00)  #    - Trackback
.NET 4.0 | ASP.NET | WCF
# Wednesday, January 19, 2011

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 = user
LOG: DisplayName = System.ServiceModel.Activation
 (Partial)
WRN: Partial binding information was supplied for an assembly:
WRN: Assembly Name: System.ServiceModel.Activation | Domain ID: 2
WRN: 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\bin
Calling assembly : (Unknown).
===
LOG: This bind starts in default load context.
LOG: Using application configuration file: xxxx\web.config
LOG: Using host configuration file: C:\Windows\Microsoft.NET\Framework64\v4.0.30319\aspnet.config
LOG: 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: 15

Das 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.


Wednesday, January 19, 2011 11:44:55 AM (W. Europe Standard Time, UTC+01:00)  #    - Trackback
.NET 4.0 | ASP.NET | IIS | WCF
# Friday, January 07, 2011

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 }



Friday, January 07, 2011 2:59:38 PM (W. Europe Standard Time, UTC+01:00)  #    - Trackback
.NET 4.0 | ASP.NET | WCF

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.


Friday, January 07, 2011 9:44:20 AM (W. Europe Standard Time, UTC+01:00)  #    - Trackback
.NET 2.0 | ASP.NET | .NET 4.0
# Wednesday, January 05, 2011

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 nicht 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 Ablenung 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:true

Weitere Informationen zum RequestFiltering gibt es auch hier IIS Security RequestFiltering.



Wednesday, January 05, 2011 6:17:50 PM (W. Europe Standard Time, UTC+01:00)  #    - Trackback
ASP.NET | IIS
# Tuesday, January 04, 2011

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 1
ASP.NET Security Update for .NET Framework 1.1 SP1

Wenn 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 -enable

Ein Ä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
und
copy %windir%\Microsoft.net\Framework\v1.1.4322\Config\machine.config %windir%\Microsoft.net\Framework64\v1.1.4322\config\machine.config


Tuesday, January 04, 2011 1:54:47 PM (W. Europe Standard Time, UTC+01:00)  #    - Trackback
.NET | ASP.NET | IIS
# Thursday, December 30, 2010

IIS Manager Error: The process cannot access the file because it is being used by another process. (Exception from HRESULT: 0×80070020)

Heute habe ich versucht bei mir lokal ein SSL Zertifikat zu installieren. So etwas habe ich nicht zum ersten mal gemacht aber heute wollte es einfach nicht funktionieren. Immer wenn ich den IIS entsprechend konfiguriert hatte (Zertifikat importiert, Binding bzw. Hostheader erstellt, etc.) kam beim Versuch den IIS zu starten immer die Meldung:

IIS Manager Error: The process cannot access the file because it is being used by another process. (Exception from HRESULT: 0×80070020)

Nach der Analyse habe ich dann festgestellt, dass ein Prozess bereits den Port 443 nutzt und daher der IIS mit einem https-Binding natürlich nicht mehr richtig starten konnte. Herausfinden welcher Port aktuell genutzt wird kann man im übrigen über "netstart -ano" im Cmd-Prompt. Der Befehl "Tasklist" hat mir dann den notwendigen Hinweis gegeben. Skype ist der "Bösewicht" der den Port 443 nutzt. Dieses kann man aber per Konfiguration ausschalten.

Danach funktioniert auch der Start vom IIS mit einem htts-Binding




Thursday, December 30, 2010 11:31:54 AM (W. Europe Standard Time, UTC+01:00)  #    - Trackback
Allgemein | IIS
# Tuesday, October 26, 2010

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 svc
2. Request URL: Matches the pattern
3. Using: Regular Expressions
4. Pattern: ^([0-9a-zA-Z\-]+)/([0-9a-zA-Z\-\.\/\(\)]+)
5. Ignore Cases: true
6. Action Type: Rewrite
7. Rewrite URL: {R:1}.svc/{R:2}
8. Append Querystring: true

Fertig ;-)

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>


Tuesday, October 26, 2010 2:21:09 PM (W. Europe Daylight Time, UTC+02:00)  #    - Trackback
.NET | ASP.NET | IIS | WCF

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.org
Dieses 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 IPostService




Die 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.



Tuesday, October 26, 2010 11:48:45 AM (W. Europe Daylight Time, UTC+02:00)  #    - Trackback
ASP.NET | WCF
# Wednesday, October 20, 2010

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.


Wednesday, October 20, 2010 3:21:55 PM (W. Europe Daylight Time, UTC+02:00)  #    - Trackback
.NET | ASP.NET | WCF
# Tuesday, September 28, 2010

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.


Tuesday, September 28, 2010 2:53:12 PM (W. Europe Daylight Time, UTC+02:00)  #    - Trackback
ASP.NET
# Thursday, September 23, 2010

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.


Thursday, September 23, 2010 8:05:00 PM (W. Europe Daylight Time, UTC+02:00)  #    - Trackback
ASP.NET
# Wednesday, September 22, 2010

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.





Wednesday, September 22, 2010 7:42:49 PM (W. Europe Daylight Time, UTC+02:00)  #    - Trackback
AJAX | ASP.NET
# Tuesday, September 14, 2010

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)
Tuesday, September 14, 2010 4:22:50 PM (W. Europe Daylight Time, UTC+02:00)  #    - Trackback
ASP.NET | DotNetNuke | FCK Editor
# Tuesday, July 20, 2010

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.


Tuesday, July 20, 2010 2:54:01 PM (W. Europe Daylight Time, UTC+02:00)  #    - Trackback
.NET | ASP.NET | Opensource
# Saturday, June 19, 2010

DotNetNuke export Users with Passwords

Bei einem Ur-Alt Portal (DotNetNuke 3.2.2) das noch hobbymäßig betreut habe, steht nun der Wechsel zu einer anderen Plattform an. Da der Benutzer aber kein neues Passwort bekommen sollten, müssen die existierenden Passwörter aus DNN exportiert werden. Die Passwörter wurden über das ASP.NET Membership Model verwaltet und die Passwörter sind nicht als Klartext sondern verschlüsselt in der Datenbank vorhanden. Ein simpler Export der Daten nutzt also an dieser Stelle nichts.

Daher habe ich mich heute Abend hingesetzt und eine Quick and Dirty-Lösung entickelt, die es erlaubt einen Webservice aufzurufen, der dann wiederum eine CSV-Datei auf dem Server erstellt.
Warum dann einen Webservice? Nun, zunächst hatte ich mir das etwas anderes gedacht - bin dann aber aus lauter Faulheit dazu übergegangen die Daten direkt lokal zu speichen.

Von Quellcode her nicht wirklich optimal und wohl alles andere als CCD-konform .. aber es funktionierte für meinen einmaligen Zweck. Sollte das noch öferts anstehen, muss man über eine allgemeingültigere Lösung nachdenken und auch die Export-Funktion anpassen.
Obwohl der Code nicht gerade ne Refernz ist, möchte ich diesen veröffentlichen und vielleicht hilft es ja dem einen odere anderen ebefalls die Passwörter seiner DotNetNuke User zu entschlüsseln.

An alle Kritiker des Datenschutzes: Ja das habe ich auch anbemängelt, allerdings wollte das mein Gegenüber so habe. Bitteschön ;-)

UserWebService.zip (7,65 KB)



Saturday, June 19, 2010 12:24:29 AM (W. Europe Daylight Time, UTC+02:00)  #    - Trackback
.NET | ASP.NET | DotNetNuke | Opensource
# Wednesday, April 28, 2010

Seitentitel im Breadcrumb Skinobject

Im Breadcrumb Skinobject von DotNetNuke wird per Default immer der Seitenname angezeigt. Es kann aber durchaus Fälle geben, bei denen es sinnvoll ist nicht den Seitennamen sondern den Seitentitel anzeigen zu lassen. Der Seitennamen ist die Bezeichnung im Menü und der Seitentitel ist der Browsertitel.

Wenn man nun anstelle vom Seitenamen den Seitentitel nutzen möchte, dann kann man dieses per Konfiguration der Komponente ganz einfach erreichen. Hier gibt es die Eigenschaft "UseTitle" und wenn diese mit dem Wert "true" belegt wird, beeinflusst das die Darstellung.

Hier als Beispiel:

<dnn:BREADCRUMB runat="server" id="dnnBREADCRUMB"  CssClass="Breadcrumb" RootLevel="0" Separator="&nbsp;>&nbsp;" UseTitle="True" />


Wednesday, April 28, 2010 9:58:40 AM (W. Europe Daylight Time, UTC+02:00)  #    - Trackback
.NET | ASP.NET | DotNetNuke
# Saturday, April 24, 2010

C# URL Shorter for TinyUrl or bit.ly

Das Socialmedia Publisher Projekt wurde in der zwischenzeit um eine Anbindung an einen URL Shorter Dienst erweitert. Damit man für die unterschiedlichen Socialmedia Plattformen (Twitter, Facebook, etc.) möglichst flexible bleibt, wurde bei der Implementierung das auch in DotNetNuke sehr oft verwendete Provider Pattern verwendet. Der Vorteil dabei ist, dass erst zur Ausführungszeit entschieden wird, welche konkrete Implementierung verwendet wird. Somit ist es also möglich für Facebook z.B. TinyURL zu verwenden und bei Twitter bit.ly.

Die dafür benötigte abstracte Klasse sieht so aus:

   public abstract class URLShorterProvider
    {

        #region [ Shared/Static Methods ]

        private static URLShorterProvider _urlShorterProvider = null;

        public static URLShorterProvider Instance(string typeName)
        {

            URLShorterProvider urlShorterProvider = null;
            CacheManager uscProviderCache = null;
            try
            {
                uscProviderCache = CacheFactory.GetCacheManager
("URLShorterProviderCache"); } catch { } if (uscProviderCache != null) urlShorterProvider = (URLShorterProvider)uscProviderCache
.GetData ("USC" + typeName); if (urlShorterProvider == null) { urlShorterProvider = (URLShorterProvider)TC.Framework.
ProviderPattern.ProviderBase.InitInstance(typeName); if (uscProviderCache != null) uscProviderCache.Add("USC" + typeName, urlShorterProvider,
CacheItemPriority.High, null, new NeverExpired()); } return urlShorterProvider; } #endregion #region [ abstract methods ] public abstract string ShortUrl(string url); #endregion
    }


Genutzt wird hier das singelton Pattern in Kombination mit einem Caching.

Die eigentliche Implementierung für TinyURL ist sehr einfach gehalten und sieht wie folgt aus:

    public class TinyURL : URLShorterProvider
    {
        ///Die maximale Länge der URL, bevor diese gekürtzt wird.
        private const int MAX_URL_LENGTH = 26;
        
        public override string ShortUrl(string url)
        {
            try
            {
                if (url.Length < MAX_URL_LENGTH)
                    return url;

                return new System.Net.WebClient().DownloadString
("http://tinyurl.com/api-create.php?url=" +
System.Web.HttpUtility.UrlEncode(url)); } catch(Exception exc) { Diagnostics.ExceptionHelper.HandleException(exc); return ""; } } }

Mit dieser Methode kann man nun sehr einfach und ohne großen Aufwand neue URLShoter-Dienste anbinden und per Konfiguration diese Dienste dann zuordnen.


Saturday, April 24, 2010 12:47:38 PM (W. Europe Daylight Time, UTC+02:00)  #    - Trackback
.NET | ASP.NET